mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 07:48:16 +08:00
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:
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ocelot/Configuration/Creator/HeaderTransformations.cs
Normal file
17
src/Ocelot/Configuration/Creator/HeaderTransformations.cs
Normal 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;}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public interface IHeaderFindAndReplaceCreator
|
||||
{
|
||||
HeaderTransformations Create(FileReRoute fileReRoute);
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
20
src/Ocelot/Configuration/HeaderFindAndReplace.cs
Normal file
20
src/Ocelot/Configuration/HeaderFindAndReplace.cs
Normal 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;}
|
||||
}
|
||||
}
|
@ -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;}
|
||||
|
||||
}
|
||||
}
|
@ -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>();
|
||||
|
@ -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;}
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ namespace Ocelot.Errors.Middleware
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
{
|
||||
await TrySetGlobalRequestId(context);
|
||||
|
||||
_logger.LogDebug("ocelot pipeline started");
|
||||
|
25
src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs
Normal file
25
src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
26
src/Ocelot/Headers/HttpResponseHeaderReplacer.cs
Normal file
26
src/Ocelot/Headers/HttpResponseHeaderReplacer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs
Normal file
12
src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs
Normal 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);
|
||||
}
|
||||
}
|
12
src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs
Normal file
12
src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user