Feature/transform headers (#204)

* New feature that lets a user do find and replace on an upstream header

* can transform downstream and upstream headers, not sure if interface is good

* can replace location header with placeholder

* added some syntax
This commit is contained in:
Tom Pallister
2018-01-22 20:21:29 +00:00
committed by GitHub
parent 9c048ba615
commit d0eee70c46
28 changed files with 1028 additions and 9 deletions

View File

@ -36,6 +36,9 @@ namespace Ocelot.Configuration.Builder
private bool _useServiceDiscovery;
private string _serviceName;
private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace;
private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace;
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
{
_loadBalancer = loadBalancer;
@ -198,6 +201,18 @@ namespace Ocelot.Configuration.Builder
return this;
}
public ReRouteBuilder WithUpstreamHeaderFindAndReplace(List<HeaderFindAndReplace> upstreamHeaderFindAndReplace)
{
_upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace;
return this;
}
public ReRouteBuilder WithDownstreamHeaderFindAndReplace(List<HeaderFindAndReplace> downstreamHeaderFindAndReplace)
{
_downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace;
return this;
}
public ReRoute Build()
{
return new ReRoute(
@ -226,7 +241,9 @@ namespace Ocelot.Configuration.Builder
_rateLimitOptions,
_httpHandlerOptions,
_useServiceDiscovery,
_serviceName);
_serviceName,
_upstreamHeaderFindAndReplace,
_downstreamHeaderFindAndReplace);
}
}
}

View File

@ -37,6 +37,7 @@ namespace Ocelot.Configuration.Creator
private readonly IRegionCreator _regionCreator;
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IAdministrationPath _adminPath;
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
public FileOcelotConfigurationCreator(
@ -53,9 +54,11 @@ namespace Ocelot.Configuration.Creator
IRateLimitOptionsCreator rateLimitOptionsCreator,
IRegionCreator regionCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IAdministrationPath adminPath
IAdministrationPath adminPath,
IHeaderFindAndReplaceCreator headerFAndRCreator
)
{
_headerFAndRCreator = headerFAndRCreator;
_adminPath = adminPath;
_regionCreator = regionCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator;
@ -128,6 +131,8 @@ namespace Ocelot.Configuration.Creator
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute);
var hAndRs = _headerFAndRCreator.Create(fileReRoute);
var reRoute = new ReRouteBuilder()
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
@ -155,6 +160,8 @@ namespace Ocelot.Configuration.Creator
.WithHttpHandlerOptions(httpHandlerOptions)
.WithServiceName(fileReRoute.ServiceName)
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.Build();
return reRoute;

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using Ocelot.Configuration.File;
using Ocelot.Middleware;
namespace Ocelot.Configuration.Creator
{
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
{
private IBaseUrlFinder _finder;
private Dictionary<string, Func<string>> _placeholders;
public HeaderFindAndReplaceCreator(IBaseUrlFinder finder)
{
_finder = finder;
_placeholders = new Dictionary<string, Func<string>>();
_placeholders.Add("{BaseUrl}", () => {
return _finder.Find();
});
}
public HeaderTransformations Create(FileReRoute fileReRoute)
{
var upstream = new List<HeaderFindAndReplace>();
foreach(var input in fileReRoute.UpstreamHeaderTransform)
{
var hAndr = Map(input);
upstream.Add(hAndr);
}
var downstream = new List<HeaderFindAndReplace>();
foreach(var input in fileReRoute.DownstreamHeaderTransform)
{
var hAndr = Map(input);
downstream.Add(hAndr);
}
return new HeaderTransformations(upstream, downstream);
}
private HeaderFindAndReplace Map(KeyValuePair<string,string> input)
{
var findAndReplace = input.Value.Split(",");
var replace = findAndReplace[1].TrimStart();
var startOfPlaceholder = replace.IndexOf("{");
if(startOfPlaceholder > -1)
{
var endOfPlaceholder = replace.IndexOf("}", startOfPlaceholder);
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
if(_placeholders.ContainsKey(placeholder))
{
var value = _placeholders[placeholder].Invoke();
replace = replace.Replace(placeholder, value);
}
}
var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0);
return hAndr;
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.Creator
{
public class HeaderTransformations
{
public HeaderTransformations(List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> downstream)
{
Upstream = upstream;
Downstream = downstream;
}
public List<HeaderFindAndReplace> Upstream {get;private set;}
public List<HeaderFindAndReplace> Downstream {get;private set;}
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IHeaderFindAndReplaceCreator
{
HeaderTransformations Create(FileReRoute fileReRoute);
}
}

View File

@ -11,17 +11,21 @@ namespace Ocelot.Configuration.File
AddClaimsToRequest = new Dictionary<string, string>();
RouteClaimsRequirement = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
DownstreamHeaderTransform = new Dictionary<string, string>();
FileCacheOptions = new FileCacheOptions();
QoSOptions = new FileQoSOptions();
RateLimitOptions = new FileRateLimitRule();
AuthenticationOptions = new FileAuthenticationOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
UpstreamHeaderTransform = new Dictionary<string, string>();
}
public string DownstreamPathTemplate { get; set; }
public string UpstreamPathTemplate { get; set; }
public List<string> UpstreamHttpMethod { get; set; }
public Dictionary<string, string> AddHeadersToRequest { get; set; }
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
public Dictionary<string, string> AddClaimsToRequest { get; set; }
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
public Dictionary<string, string> AddQueriesToRequest { get; set; }

View File

@ -0,0 +1,20 @@
namespace Ocelot.Configuration
{
public class HeaderFindAndReplace
{
public HeaderFindAndReplace(string key, string find, string replace, int index)
{
Key = key;
Find = find;
Replace = replace;
Index = index;
}
public string Key {get;}
public string Find {get;}
public string Replace {get;}
// only index 0 for now..
public int Index {get;}
}
}

View File

@ -32,8 +32,12 @@ namespace Ocelot.Configuration
RateLimitOptions ratelimitOptions,
HttpHandlerOptions httpHandlerOptions,
bool useServiceDiscovery,
string serviceName)
string serviceName,
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace)
{
DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace;
UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace;
ServiceName = serviceName;
UseServiceDiscovery = useServiceDiscovery;
ReRouteKey = reRouteKey;
@ -91,5 +95,8 @@ namespace Ocelot.Configuration
public HttpHandlerOptions HttpHandlerOptions { get; private set; }
public bool UseServiceDiscovery {get;private set;}
public string ServiceName {get;private set;}
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;}
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace {get;private set;}
}
}

View File

@ -75,6 +75,9 @@ namespace Ocelot.DependencyInjection
//add ocelot services...
_services.Configure<FileConfiguration>(configurationRoot);
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();

View File

@ -13,5 +13,6 @@ namespace Ocelot.DownstreamRouteFinder
}
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
public ReRoute ReRoute { get; private set; }
public object UpstreamHeadersFindAndReplace {get;private set;}
}
}

View File

@ -37,7 +37,7 @@ namespace Ocelot.Errors.Middleware
public async Task Invoke(HttpContext context)
{
try
{
{
await TrySetGlobalRequestId(context);
_logger.LogDebug("ocelot pipeline started");

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Headers
{
public class HttpContextRequestHeaderReplacer : IHttpContextRequestHeaderReplacer
{
public Response Replace(HttpContext context, List<HeaderFindAndReplace> fAndRs)
{
foreach (var f in fAndRs)
{
if(context.Request.Headers.TryGetValue(f.Key, out var values))
{
var replaced = values[f.Index].Replace(f.Find, f.Replace);
context.Request.Headers.Remove(f.Key);
context.Request.Headers.Add(f.Key, replaced);
}
}
return new OkResponse();
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Headers
{
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
{
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs)
{
foreach (var f in fAndRs)
{
if(response.Headers.TryGetValues(f.Key, out var values))
{
var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace);
response.Headers.Remove(f.Key);
response.Headers.Add(f.Key, replaced);
}
}
return new OkResponse();
}
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Headers
{
public interface IHttpContextRequestHeaderReplacer
{
Response Replace(HttpContext context, List<HeaderFindAndReplace> fAndRs);
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Headers
{
public interface IHttpResponseHeaderReplacer
{
Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs);
}
}

View File

@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
namespace Ocelot.Headers.Middleware
{
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IOcelotLogger _logger;
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
private readonly IHttpResponseHeaderReplacer _postReplacer;
public HttpHeadersTransformationMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository requestScopedDataRepository,
IHttpContextRequestHeaderReplacer preReplacer,
IHttpResponseHeaderReplacer postReplacer)
: base(requestScopedDataRepository)
{
_next = next;
_postReplacer = postReplacer;
_preReplacer = preReplacer;
_logger = loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>();
}
public async Task Invoke(HttpContext context)
{
var preFAndRs = this.DownstreamRoute.ReRoute.UpstreamHeadersFindAndReplace;
_preReplacer.Replace(context, preFAndRs);
await _next.Invoke(context);
var postFAndRs = this.DownstreamRoute.ReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(HttpResponseMessage, postFAndRs);
}
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Builder;
namespace Ocelot.Headers.Middleware
{
public static class HttpHeadersTransformationMiddlewareExtensions
{
public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HttpHeadersTransformationMiddleware>();
}
}
}

View File

@ -86,12 +86,15 @@ namespace Ocelot.Middleware
// This is registered first so it can catch any errors and issue an appropriate response
builder.UseResponderMiddleware();
// Initialises downstream request
builder.UseDownstreamRequestInitialiser();
// Then we get the downstream route information
builder.UseDownstreamRouteFinderMiddleware();
// Now we have the ds route we can transform headers and stuff?
builder.UseHttpHeadersTransformationMiddleware();
// Initialises downstream request
builder.UseDownstreamRequestInitialiser();
// We check whether the request is ratelimit, and if there is no continue processing
builder.UseRateLimiting();

View File

@ -14,7 +14,7 @@ namespace Ocelot.Request.Builder
bool useCookieContainer,
bool allowAutoRedirect)
{
return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, useCookieContainer, allowAutoRedirect));
return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer));
}
}
}