Skip to content

Commit

Permalink
Support adding custom plaintext headers to downstream requests (#314)
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixBoers authored and TomPallister committed Apr 14, 2018
1 parent b46ef19 commit fa09e4c
Show file tree
Hide file tree
Showing 14 changed files with 376 additions and 183 deletions.
26 changes: 21 additions & 5 deletions docs/features/headerstransformation.rst
Original file line number Diff line number Diff line change
@@ -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 <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 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 <https://github.com/TomPallister/Ocelot/issues/280>`_. I have only implemented
for responses but could add for requests in the future.
This feature was requested in `GitHub #280 <https://github.com/TomPallister/Ocelot/issues/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
Expand Down Expand Up @@ -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.

Expand Down
11 changes: 10 additions & 1 deletion src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ public class DownstreamReRouteBuilder
private string _key;
private List<string> _delegatingHandlers;
private List<AddHeader> _addHeadersToDownstream;
private List<AddHeader> _addHeadersToUpstream;

public DownstreamReRouteBuilder()
{
_downstreamAddresses = new List<DownstreamHostAndPort>();
_delegatingHandlers = new List<string>();
_addHeadersToDownstream = new List<AddHeader>();
_addHeadersToUpstream = new List<AddHeader>();
}

public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
Expand Down Expand Up @@ -233,6 +235,12 @@ public DownstreamReRouteBuilder WithAddHeadersToDownstream(List<AddHeader> addHe
return this;
}

public DownstreamReRouteBuilder WithAddHeadersToUpstream(List<AddHeader> addHeadersToUpstream)
{
_addHeadersToUpstream = addHeadersToUpstream;
return this;
}

public DownstreamReRoute Build()
{
return new DownstreamReRoute(
Expand Down Expand Up @@ -263,7 +271,8 @@ public DownstreamReRoute Build()
new PathTemplate(_downstreamPathTemplate),
_reRouteKey,
_delegatingHandlers,
_addHeadersToDownstream);
_addHeadersToDownstream,
_addHeadersToUpstream);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 14 additions & 6 deletions src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,31 @@ public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFact
public HeaderTransformations Create(FileReRoute fileReRoute)
{
var upstream = new List<HeaderFindAndReplace>();
var addHeadersToUpstream = new List<AddHeader>();

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<HeaderFindAndReplace>();
var addHeadersToDownstream = new List<AddHeader>();

foreach(var input in fileReRoute.DownstreamHeaderTransform)
{
if(input.Value.Contains(","))
Expand All @@ -59,7 +67,7 @@ public HeaderTransformations Create(FileReRoute fileReRoute)
}
}

return new HeaderTransformations(upstream, downstream, addHeadersToDownstream);
return new HeaderTransformations(upstream, downstream, addHeadersToDownstream, addHeadersToUpstream);
}

private Response<HeaderFindAndReplace> Map(KeyValuePair<string,string> input)
Expand Down
7 changes: 5 additions & 2 deletions src/Ocelot/Configuration/Creator/HeaderTransformations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ public class HeaderTransformations
public HeaderTransformations(
List<HeaderFindAndReplace> upstream,
List<HeaderFindAndReplace> downstream,
List<AddHeader> addHeader)
List<AddHeader> addHeaderToDownstream,
List<AddHeader> addHeaderToUpstream)
{
AddHeadersToDownstream = addHeader;
AddHeadersToDownstream = addHeaderToDownstream;
AddHeadersToUpstream = addHeaderToUpstream;
Upstream = upstream;
Downstream = downstream;
}
Expand All @@ -19,5 +21,6 @@ public HeaderTransformations(
public List<HeaderFindAndReplace> Downstream { get; }

public List<AddHeader> AddHeadersToDownstream { get; }
public List<AddHeader> AddHeadersToUpstream { get; }
}
}
5 changes: 4 additions & 1 deletion src/Ocelot/Configuration/DownstreamReRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public DownstreamReRoute(
PathTemplate downstreamPathTemplate,
string reRouteKey,
List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream)
List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream)
{
AddHeadersToDownstream = addHeadersToDownstream;
DelegatingHandlers = delegatingHandlers;
Expand Down Expand Up @@ -64,6 +65,7 @@ public DownstreamReRoute(
AuthenticationOptions = authenticationOptions;
DownstreamPathTemplate = downstreamPathTemplate;
ReRouteKey = reRouteKey;
AddHeadersToUpstream = addHeadersToUpstream;
}

public string Key { get; private set; }
Expand Down Expand Up @@ -94,5 +96,6 @@ public DownstreamReRoute(
public string ReRouteKey { get; private set; }
public List<string> DelegatingHandlers {get;private set;}
public List<AddHeader> AddHeadersToDownstream {get;private set;}
public List<AddHeader> AddHeadersToUpstream { get; private set; }
}
}
17 changes: 16 additions & 1 deletion src/Ocelot/Headers/AddHeadersToRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -41,5 +42,19 @@ public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings,

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);
}
}
}
}
}
5 changes: 4 additions & 1 deletion src/Ocelot/Headers/IAddHeadersToRequest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Ocelot.Headers
using Microsoft.AspNetCore.Http;

namespace Ocelot.Headers
{
using System.Collections.Generic;
using System.Net.Http;
Expand All @@ -12,5 +14,6 @@
public interface IAddHeadersToRequest
{
Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest);
void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpHeadersTransformationMiddleware>())
{
_addHeaders = addHeaders;
_addHeadersToResponse = addHeadersToResponse;
_addHeadersToRequest = addHeadersToRequest;
_next = next;
_postReplacer = postReplacer;
_preReplacer = preReplacer;
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,7 @@ private void ThenTheReRoutesAre(List<ReRoute> 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");
}
}

Expand Down Expand Up @@ -911,7 +912,7 @@ private void ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly()

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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<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();
}

Expand All @@ -180,11 +217,17 @@ private void GivenTheBaseUrlErrors()
_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].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)
{
Expand Down
Loading

0 comments on commit fa09e4c

Please sign in to comment.