#280 Add headers to response (#286)

* #280 can now add response headers inc trace id, now need to consolidate the header place holder stuff

* #280 changed port for linux tests

* #280 lots of hacking around to handle errors and consolidate placeholders into one class
This commit is contained in:
Tom Pallister
2018-03-18 14:58:39 +00:00
committed by GitHub
parent 978b0a43a0
commit b51df71d7b
31 changed files with 700 additions and 60 deletions

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Values;
using System.Linq;
using Ocelot.Configuration.Creator;
namespace Ocelot.Configuration.Builder
{
@ -37,11 +38,13 @@ namespace Ocelot.Configuration.Builder
private string _upstreamHost;
private string _key;
private List<string> _delegatingHandlers;
private List<AddHeader> _addHeadersToDownstream;
public DownstreamReRouteBuilder()
{
_downstreamAddresses = new List<DownstreamHostAndPort>();
_delegatingHandlers = new List<string>();
_addHeadersToDownstream = new List<AddHeader>();
}
public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
@ -224,6 +227,12 @@ namespace Ocelot.Configuration.Builder
return this;
}
public DownstreamReRouteBuilder WithAddHeadersToDownstream(List<AddHeader> addHeadersToDownstream)
{
_addHeadersToDownstream = addHeadersToDownstream;
return this;
}
public DownstreamReRoute Build()
{
return new DownstreamReRoute(
@ -253,7 +262,8 @@ namespace Ocelot.Configuration.Builder
_authenticationOptions,
new PathTemplate(_downstreamPathTemplate),
_reRouteKey,
_delegatingHandlers);
_delegatingHandlers,
_addHeadersToDownstream);
}
}
}

View File

@ -213,6 +213,7 @@ namespace Ocelot.Configuration.Creator
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.WithUpstreamHost(fileReRoute.UpstreamHost)
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
.Build();
return reRoute;

View File

@ -1,20 +1,22 @@
using System;
using System.Collections.Generic;
using Ocelot.Configuration.File;
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
namespace Ocelot.Configuration.Creator
{
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
{
private IBaseUrlFinder _finder;
private readonly Dictionary<string, Func<string>> _placeholders;
private IPlaceholders _placeholders;
private IOcelotLogger _logger;
public HeaderFindAndReplaceCreator(IBaseUrlFinder finder)
public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory)
{
_finder = finder;
_placeholders = new Dictionary<string, Func<string>>();
_placeholders.Add("{BaseUrl}", () => _finder.Find());
_logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();;
_placeholders = placeholders;
}
public HeaderTransformations Create(FileReRoute fileReRoute)
@ -24,21 +26,43 @@ namespace Ocelot.Configuration.Creator
foreach(var input in fileReRoute.UpstreamHeaderTransform)
{
var hAndr = Map(input);
upstream.Add(hAndr);
if(!hAndr.IsError)
{
upstream.Add(hAndr.Data);
}
else
{
_logger.LogError($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}");
}
}
var downstream = new List<HeaderFindAndReplace>();
var addHeadersToDownstream = new List<AddHeader>();
foreach(var input in fileReRoute.DownstreamHeaderTransform)
{
var hAndr = Map(input);
downstream.Add(hAndr);
if(input.Value.Contains(","))
{
var hAndr = Map(input);
if(!hAndr.IsError)
{
downstream.Add(hAndr.Data);
}
else
{
_logger.LogError($"Unable to add DownstreamHeaderTransform {input.Key}: {input.Value}");
}
}
else
{
addHeadersToDownstream.Add(new AddHeader(input.Key, input.Value));
}
}
return new HeaderTransformations(upstream, downstream);
return new HeaderTransformations(upstream, downstream, addHeadersToDownstream);
}
private HeaderFindAndReplace Map(KeyValuePair<string,string> input)
private Response<HeaderFindAndReplace> Map(KeyValuePair<string,string> input)
{
var findAndReplace = input.Value.Split(",");
@ -51,16 +75,19 @@ namespace Ocelot.Configuration.Creator
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
if(_placeholders.ContainsKey(placeholder))
var value = _placeholders.Get(placeholder);
if(value.IsError)
{
var value = _placeholders[placeholder].Invoke();
replace = replace.Replace(placeholder, value);
return new ErrorResponse<HeaderFindAndReplace>(value.Errors);
}
replace = replace.Replace(placeholder, value.Data);
}
var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0);
return hAndr;
return new OkResponse<HeaderFindAndReplace>(hAndr);
}
}
}

View File

@ -4,14 +4,31 @@ namespace Ocelot.Configuration.Creator
{
public class HeaderTransformations
{
public HeaderTransformations(List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> downstream)
public HeaderTransformations(
List<HeaderFindAndReplace> upstream,
List<HeaderFindAndReplace> downstream,
List<AddHeader> addHeader)
{
AddHeadersToDownstream = addHeader;
Upstream = upstream;
Downstream = downstream;
}
public List<HeaderFindAndReplace> Upstream {get;private set;}
public List<HeaderFindAndReplace> Upstream { get; private set; }
public List<HeaderFindAndReplace> Downstream {get;private set;}
public List<HeaderFindAndReplace> Downstream { get; private set; }
public List<AddHeader> AddHeadersToDownstream {get;private set;}
}
public class AddHeader
{
public AddHeader(string key, string value)
{
this.Key = key;
this.Value = value;
}
public string Key { get; private set; }
public string Value { get; private set; }
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using Ocelot.Values;
namespace Ocelot.Configuration
@ -32,8 +33,10 @@ namespace Ocelot.Configuration
AuthenticationOptions authenticationOptions,
PathTemplate downstreamPathTemplate,
string reRouteKey,
List<string> delegatingHandlers)
List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream)
{
AddHeadersToDownstream = addHeadersToDownstream;
DelegatingHandlers = delegatingHandlers;
Key = key;
UpstreamPathTemplate = upstreamPathTemplate;
@ -90,5 +93,6 @@ namespace Ocelot.Configuration
public PathTemplate DownstreamPathTemplate { get; private set; }
public string ReRouteKey { get; private set; }
public List<string> DelegatingHandlers {get;private set;}
public List<AddHeader> AddHeadersToDownstream {get;private set;}
}
}

View File

@ -52,6 +52,7 @@ namespace Ocelot.DependencyInjection
using System.Linq;
using System.Net.Http;
using Butterfly.Client.AspNetCore;
using Ocelot.Infrastructure;
public class OcelotBuilder : IOcelotBuilder
{
@ -149,6 +150,8 @@ namespace Ocelot.DependencyInjection
// We add this here so that we can always inject something into the factory for IoC..
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
_services.TryAddSingleton<IPlaceholders, Placeholders>();
}
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)

View File

@ -34,6 +34,7 @@
RateLimitOptionsError,
PathTemplateDoesntStartWithForwardSlash,
FileValidationFailedError,
UnableToFindDelegatingHandlerProviderError
UnableToFindDelegatingHandlerProviderError,
CouldNotFindPlaceholderError
}
}

View File

@ -4,6 +4,7 @@ using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses;
using System.Net.Http;
using Ocelot.Configuration.Creator;
namespace Ocelot.Headers
{

View File

@ -0,0 +1,44 @@
namespace Ocelot.Headers
{
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
public class AddHeadersToResponse : IAddHeadersToResponse
{
private IPlaceholders _placeholders;
private IOcelotLogger _logger;
public AddHeadersToResponse(IPlaceholders placeholders, IOcelotLoggerFactory factory)
{
_logger = factory.CreateLogger<AddHeadersToResponse>();
_placeholders = placeholders;
}
public void Add(List<AddHeader> addHeaders, HttpResponseMessage response)
{
foreach(var add in addHeaders)
{
if(add.Value.StartsWith('{') && add.Value.EndsWith('}'))
{
var value = _placeholders.Get(add.Value);
if(value.IsError)
{
_logger.LogError($"Unable to add header to response {add.Key}: {add.Value}");
continue;
}
response.Headers.TryAddWithoutValidation(add.Key, value.Data);
}
else
{
response.Headers.TryAddWithoutValidation(add.Key, add.Value);
}
}
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Responses;
@ -10,24 +11,14 @@ namespace Ocelot.Headers
{
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
{
private Dictionary<string, Func<HttpRequestMessage, string>> _placeholders;
private IPlaceholders _placeholders;
public HttpResponseHeaderReplacer()
public HttpResponseHeaderReplacer(IPlaceholders placeholders)
{
_placeholders = new Dictionary<string, Func<HttpRequestMessage, string>>();
_placeholders.Add("{DownstreamBaseUrl}", x => {
var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}";
if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443)
{
downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}";
}
return $"{downstreamUrl}/";
});
_placeholders = placeholders;
}
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage httpRequestMessage)
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage request)
{
foreach (var f in fAndRs)
{
@ -35,11 +26,13 @@ namespace Ocelot.Headers
if(response.Headers.TryGetValues(f.Key, out var values))
{
//check to see if it is a placeholder in the find...
if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder))
var placeholderValue = _placeholders.Get(f.Find, request);
if(!placeholderValue.IsError)
{
//if it is we need to get the value of the placeholder
var find = replacePlaceholder(httpRequestMessage);
var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash());
//var find = replacePlaceholder(httpRequestMessage);
var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
response.Headers.Remove(f.Key);
response.Headers.Add(f.Key, replaced);
}

View File

@ -4,6 +4,8 @@
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Responses;
public interface IAddHeadersToRequest

View File

@ -0,0 +1,11 @@
namespace Ocelot.Headers
{
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration.Creator;
public interface IAddHeadersToResponse
{
void Add(List<AddHeader> addHeaders, HttpResponseMessage response);
}
}

View File

@ -13,12 +13,15 @@ namespace Ocelot.Headers.Middleware
private readonly IOcelotLogger _logger;
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
private readonly IHttpResponseHeaderReplacer _postReplacer;
private readonly IAddHeadersToResponse _addHeaders;
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IHttpContextRequestHeaderReplacer preReplacer,
IHttpResponseHeaderReplacer postReplacer)
IHttpResponseHeaderReplacer postReplacer,
IAddHeadersToResponse addHeaders)
{
_addHeaders = addHeaders;
_next = next;
_postReplacer = postReplacer;
_preReplacer = preReplacer;
@ -37,6 +40,8 @@ namespace Ocelot.Headers.Middleware
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
_addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
}
}
}

View File

@ -0,0 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Infrastructure
{
public class CouldNotFindPlaceholderError : Error
{
public CouldNotFindPlaceholderError(string placeholder)
: base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError)
{
}
}
}

View File

@ -0,0 +1,11 @@
using System.Net.Http;
using Ocelot.Responses;
namespace Ocelot.Infrastructure
{
public interface IPlaceholders
{
Response<string> Get(string key);
Response<string> Get(string key, HttpRequestMessage request);
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Responses;
namespace Ocelot.Infrastructure
{
public class Placeholders : IPlaceholders
{
private Dictionary<string, Func<Response<string>>> _placeholders;
private Dictionary<string, Func<HttpRequestMessage, string>> _requestPlaceholders;
private readonly IBaseUrlFinder _finder;
private readonly IRequestScopedDataRepository _repo;
public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo)
{
_repo = repo;
_finder = finder;
_placeholders = new Dictionary<string, Func<Response<string>>>();
_placeholders.Add("{BaseUrl}", () => new OkResponse<string>(_finder.Find()));
_placeholders.Add("{TraceId}", () => {
var traceId = _repo.Get<string>("TraceId");
if(traceId.IsError)
{
return new ErrorResponse<string>(traceId.Errors);
}
return new OkResponse<string>(traceId.Data);
});
_requestPlaceholders = new Dictionary<string, Func<HttpRequestMessage, string>>();
_requestPlaceholders.Add("{DownstreamBaseUrl}", x => {
var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}";
if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443)
{
downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}";
}
return $"{downstreamUrl}/";
});
}
public Response<string> Get(string key)
{
if(_placeholders.ContainsKey(key))
{
var response = _placeholders[key].Invoke();
if(!response.IsError)
{
return new OkResponse<string>(response.Data);
}
}
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
}
public Response<string> Get(string key, HttpRequestMessage request)
{
if(_requestPlaceholders.ContainsKey(key))
{
return new OkResponse<string>(_requestPlaceholders[key].Invoke(request));
}
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
}
}
}

View File

@ -5,26 +5,37 @@ using System.Threading;
using System.Threading.Tasks;
using Butterfly.Client.Tracing;
using Butterfly.OpenTracing;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Requester
{
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
{
private readonly IServiceTracer _tracer;
private readonly IRequestScopedDataRepository _repo;
private const string prefix_spanId = "ot-spanId";
public OcelotHttpTracingHandler(IServiceTracer tracer, HttpMessageHandler httpMessageHandler = null)
public OcelotHttpTracingHandler(
IServiceTracer tracer,
IRequestScopedDataRepository repo,
HttpMessageHandler httpMessageHandler = null)
{
_repo = repo;
_tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
InnerHandler = httpMessageHandler ?? new HttpClientHandler();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
}
protected virtual async Task<HttpResponseMessage> TracingSendAsync(ISpan span, HttpRequestMessage request, CancellationToken cancellationToken)
protected virtual async Task<HttpResponseMessage> TracingSendAsync(
ISpan span,
HttpRequestMessage request,
CancellationToken cancellationToken)
{
IEnumerable<string> traceIdVals = null;
if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals))
@ -33,6 +44,8 @@ namespace Ocelot.Requester
request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId);
}
_repo.Add("TraceId", span.SpanContext.TraceId);
span.Tags.Client().Component("HttpClient")
.HttpMethod(request.Method.Method)
.HttpUrl(request.RequestUri.OriginalString)

View File

@ -1,20 +1,25 @@
using Butterfly.Client.Tracing;
using Butterfly.OpenTracing;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Requester
{
public class TracingHandlerFactory : ITracingHandlerFactory
{
private readonly IServiceTracer _tracer;
private readonly IRequestScopedDataRepository _repo;
public TracingHandlerFactory(IServiceTracer tracer)
public TracingHandlerFactory(
IServiceTracer tracer,
IRequestScopedDataRepository repo)
{
_repo = repo;
_tracer = tracer;
}
public ITracingHandler Get()
{
return new OcelotHttpTracingHandler(_tracer);
return new OcelotHttpTracingHandler(_tracer, _repo);
}
}