#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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 700 additions and 60 deletions

View File

@ -3,8 +3,32 @@ 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. 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.
Syntax 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.
If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json..
.. code-block:: json
"DownstreamHeaderTransform": {
"Uncle": "Bob"
},
In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute.
If you want to return the Butterfly APM trace id then do something like the following..
.. code-block:: json
"DownstreamHeaderTransform": {
"AnyKey": "{TraceId}"
},
Find and Replace
^^^^^^^^^^^^^^^^
In order to transform a header first we specify the header key and then the type of transform we want e.g. In order to transform a header first we specify the header key and then the type of transform we want e.g.
@ -43,6 +67,7 @@ Ocelot allows placeholders that can be used in header transformation.
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
Handling 302 Redirects Handling 302 Redirects
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^

View File

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

View File

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

View File

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

View File

@ -4,8 +4,12 @@ namespace Ocelot.Configuration.Creator
{ {
public class HeaderTransformations 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; Upstream = upstream;
Downstream = downstream; Downstream = downstream;
} }
@ -13,5 +17,18 @@ namespace Ocelot.Configuration.Creator
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 System.Collections.Generic;
using Ocelot.Configuration.Creator;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.Configuration namespace Ocelot.Configuration
@ -32,8 +33,10 @@ namespace Ocelot.Configuration
AuthenticationOptions authenticationOptions, AuthenticationOptions authenticationOptions,
PathTemplate downstreamPathTemplate, PathTemplate downstreamPathTemplate,
string reRouteKey, string reRouteKey,
List<string> delegatingHandlers) List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream)
{ {
AddHeadersToDownstream = addHeadersToDownstream;
DelegatingHandlers = delegatingHandlers; DelegatingHandlers = delegatingHandlers;
Key = key; Key = key;
UpstreamPathTemplate = upstreamPathTemplate; UpstreamPathTemplate = upstreamPathTemplate;
@ -90,5 +93,6 @@ namespace Ocelot.Configuration
public PathTemplate DownstreamPathTemplate { get; private set; } public PathTemplate DownstreamPathTemplate { get; private set; }
public string ReRouteKey { get; private set; } public string ReRouteKey { get; private set; }
public List<string> DelegatingHandlers {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.Linq;
using System.Net.Http; using System.Net.Http;
using Butterfly.Client.AspNetCore; using Butterfly.Client.AspNetCore;
using Ocelot.Infrastructure;
public class OcelotBuilder : IOcelotBuilder 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.. // We add this here so that we can always inject something into the factory for IoC..
_services.AddSingleton<IServiceTracer, FakeServiceTracer>(); _services.AddSingleton<IServiceTracer, FakeServiceTracer>();
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>(); _services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
_services.TryAddSingleton<IPlaceholders, Placeholders>();
} }
public IOcelotAdministrationBuilder AddAdministration(string path, string secret) public IOcelotAdministrationBuilder AddAdministration(string path, string secret)

View File

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

View File

@ -4,6 +4,7 @@ using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration.Creator;
namespace Ocelot.Headers 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.Linq;
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Responses; using Ocelot.Responses;
@ -10,24 +11,14 @@ namespace Ocelot.Headers
{ {
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer 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 = placeholders;
_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}/"; public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage request)
});
}
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage httpRequestMessage)
{ {
foreach (var f in fAndRs) foreach (var f in fAndRs)
{ {
@ -35,11 +26,13 @@ namespace Ocelot.Headers
if(response.Headers.TryGetValues(f.Key, out var values)) if(response.Headers.TryGetValues(f.Key, out var values))
{ {
//check to see if it is a placeholder in the find... //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 //if it is we need to get the value of the placeholder
var find = replacePlaceholder(httpRequestMessage); //var find = replacePlaceholder(httpRequestMessage);
var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash()); var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
response.Headers.Remove(f.Key); response.Headers.Remove(f.Key);
response.Headers.Add(f.Key, replaced); response.Headers.Add(f.Key, replaced);
} }

View File

@ -4,6 +4,8 @@
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Responses; using Ocelot.Responses;
public interface IAddHeadersToRequest 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 IOcelotLogger _logger;
private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpContextRequestHeaderReplacer _preReplacer;
private readonly IHttpResponseHeaderReplacer _postReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer;
private readonly IAddHeadersToResponse _addHeaders;
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IHttpContextRequestHeaderReplacer preReplacer, IHttpContextRequestHeaderReplacer preReplacer,
IHttpResponseHeaderReplacer postReplacer) IHttpResponseHeaderReplacer postReplacer,
IAddHeadersToResponse addHeaders)
{ {
_addHeaders = addHeaders;
_next = next; _next = next;
_postReplacer = postReplacer; _postReplacer = postReplacer;
_preReplacer = preReplacer; _preReplacer = preReplacer;
@ -37,6 +40,8 @@ namespace Ocelot.Headers.Middleware
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); _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 System.Threading.Tasks;
using Butterfly.Client.Tracing; using Butterfly.Client.Tracing;
using Butterfly.OpenTracing; using Butterfly.OpenTracing;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
{ {
private readonly IServiceTracer _tracer; private readonly IServiceTracer _tracer;
private readonly IRequestScopedDataRepository _repo;
private const string prefix_spanId = "ot-spanId"; 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)); _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
InnerHandler = httpMessageHandler ?? new HttpClientHandler(); 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)); 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; IEnumerable<string> traceIdVals = null;
if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals)) if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals))
@ -33,6 +44,8 @@ namespace Ocelot.Requester
request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId); request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId);
} }
_repo.Add("TraceId", span.SpanContext.TraceId);
span.Tags.Client().Component("HttpClient") span.Tags.Client().Component("HttpClient")
.HttpMethod(request.Method.Method) .HttpMethod(request.Method.Method)
.HttpUrl(request.RequestUri.OriginalString) .HttpUrl(request.RequestUri.OriginalString)

View File

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

View File

@ -70,7 +70,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51888, Port = 51388,
} }
}, },
UpstreamPathTemplate = "/api002/values", UpstreamPathTemplate = "/api002/values",
@ -92,7 +92,7 @@ namespace Ocelot.AcceptanceTests
var butterflyUrl = "http://localhost:9618"; var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl)) .And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
.And(x => GivenFakeButterfly(butterflyUrl)) .And(x => GivenFakeButterfly(butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
@ -109,6 +109,60 @@ namespace Ocelot.AcceptanceTests
commandOnAllStateMachines.ShouldBeTrue(); commandOnAllStateMachines.ShouldBeTrue();
} }
[Fact]
public void should_return_tracing_header()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51387,
}
},
UpstreamPathTemplate = "/api001/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
},
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 3,
DurationOfBreak = 10,
TimeoutValue = 5000
},
DownstreamHeaderTransform = new Dictionary<string, string>()
{
{"Trace-Id", "{TraceId}"},
{"Tom", "Laura"}
}
}
}
};
var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => GivenFakeButterfly(butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id"))
.And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura"))
.BDDfy();
}
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
{ {
_serviceOneBuilder = new WebHostBuilder() _serviceOneBuilder = new WebHostBuilder()

View File

@ -318,6 +318,12 @@ namespace Ocelot.AcceptanceTests
header.First().ShouldBe(value); header.First().ShouldBe(value);
} }
public void ThenTheTraceHeaderIsSet(string key)
{
var header = _response.Headers.GetValues(key);
header.First().ShouldNotBeNullOrEmpty();
}
public void GivenOcelotIsRunningUsingJsonSerializedCache() public void GivenOcelotIsRunningUsingJsonSerializedCache()
{ {
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();

View File

@ -34,7 +34,7 @@ namespace Ocelot.UnitTests.Configuration
_fileConfig = new FileConfiguration(); _fileConfig = new FileConfiguration();
_config = new Mock<IConsulPollerConfiguration>(); _config = new Mock<IConsulPollerConfiguration>();
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig)); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
_config.Setup(x => x.Delay).Returns(10); _config.Setup(x => x.Delay).Returns(100);
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
} }

View File

@ -827,6 +827,7 @@ namespace Ocelot.UnitTests.Configuration
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count); result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
} }
} }
@ -909,7 +910,7 @@ namespace Ocelot.UnitTests.Configuration
private void GivenTheHeaderFindAndReplaceCreatorReturns() private void GivenTheHeaderFindAndReplaceCreatorReturns()
{ {
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>())); _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>(), new List<AddHeader>()));
} }
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration) private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)

View File

@ -5,7 +5,11 @@ using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.UnitTests.Responder;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -17,12 +21,17 @@ namespace Ocelot.UnitTests.Configuration
private HeaderFindAndReplaceCreator _creator; private HeaderFindAndReplaceCreator _creator;
private FileReRoute _reRoute; private FileReRoute _reRoute;
private HeaderTransformations _result; private HeaderTransformations _result;
private Mock<IBaseUrlFinder> _finder; private Mock<IPlaceholders> _placeholders;
private Mock<IOcelotLoggerFactory> _factory;
private Mock<IOcelotLogger> _logger;
public HeaderFindAndReplaceCreatorTests() public HeaderFindAndReplaceCreatorTests()
{ {
_finder = new Mock<IBaseUrlFinder>(); _logger = new Mock<IOcelotLogger>();
_creator = new HeaderFindAndReplaceCreator(_finder.Object); _factory = new Mock<IOcelotLoggerFactory>();
_factory.Setup(x => x.CreateLogger<HeaderFindAndReplaceCreator>()).Returns(_logger.Object);
_placeholders = new Mock<IPlaceholders>();
_creator = new HeaderFindAndReplaceCreator(_placeholders.Object, _factory.Object);
} }
[Fact] [Fact]
@ -84,6 +93,40 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_log_errors_and_not_add_headers()
{
var reRoute = new FileReRoute
{
DownstreamHeaderTransform = new Dictionary<string, string>
{
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
},
UpstreamHeaderTransform = new Dictionary<string, string>
{
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
}
};
var expected = new List<HeaderFindAndReplace>
{
};
this.Given(x => GivenTheReRoute(reRoute))
.And(x => GivenTheBaseUrlErrors())
.When(x => WhenICreate())
.Then(x => ThenTheFollowingDownstreamIsReturned(expected))
.And(x => ThenTheFollowingUpstreamIsReturned(expected))
.And(x => ThenTheLoggerIsCalledCorrectly("Unable to add DownstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}"))
.And(x => ThenTheLoggerIsCalledCorrectly("Unable to add UpstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}"))
.BDDfy();
}
private void ThenTheLoggerIsCalledCorrectly(string message)
{
_logger.Verify(x => x.LogError(message), Times.Once);
}
[Fact] [Fact]
public void should_use_base_url_partial_placeholder() public void should_use_base_url_partial_placeholder()
{ {
@ -107,9 +150,41 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_add_trace_id_header()
{
var reRoute = new FileReRoute
{
DownstreamHeaderTransform = new Dictionary<string, string>
{
{"Trace-Id", "{TraceId}"},
}
};
var expected = new AddHeader("Trace-Id", "{TraceId}");
this.Given(x => GivenTheReRoute(reRoute))
.And(x => GivenTheBaseUrlIs("http://ocelot.com/"))
.When(x => WhenICreate())
.Then(x => ThenTheFollowingAddHeaderIsReturned(expected))
.BDDfy();
}
private void GivenTheBaseUrlIs(string baseUrl) private void GivenTheBaseUrlIs(string baseUrl)
{ {
_finder.Setup(x => x.Find()).Returns(baseUrl); _placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new OkResponse<string>(baseUrl));
}
private void GivenTheBaseUrlErrors()
{
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError()));
}
private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader)
{
_result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key);
_result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value);
} }
private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream) private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream)

View File

@ -0,0 +1,148 @@
using Xunit;
using Shouldly;
using TestStack.BDDfy;
using Ocelot.Headers;
using System.Net.Http;
using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using System.Linq;
using Moq;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Responses;
using Ocelot.Infrastructure;
using Ocelot.UnitTests.Responder;
using System;
using Ocelot.Logging;
namespace Ocelot.UnitTests.Headers
{
public class AddHeadersToResponseTests
{
private IAddHeadersToResponse _adder;
private Mock<IPlaceholders> _placeholders;
private HttpResponseMessage _response;
private List<AddHeader> _addHeaders;
private Mock<IOcelotLoggerFactory> _factory;
private Mock<IOcelotLogger> _logger;
public AddHeadersToResponseTests()
{
_factory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_factory.Setup(x => x.CreateLogger<AddHeadersToResponse>()).Returns(_logger.Object);
_placeholders = new Mock<IPlaceholders>();
_adder = new AddHeadersToResponse(_placeholders.Object, _factory.Object);
}
[Fact]
public void should_add_header()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Laura", "Tom")
};
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.And(_ => ThenTheHeaderIsReturned("Laura", "Tom"))
.BDDfy();
}
[Fact]
public void should_add_trace_id_placeholder()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Trace-Id", "{TraceId}")
};
var traceId = "123";
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheTraceIdIs(traceId))
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId))
.BDDfy();
}
[Fact]
public void should_add_trace_id_placeholder_and_normal()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Trace-Id", "{TraceId}"),
new AddHeader("Tom", "Laura")
};
var traceId = "123";
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheTraceIdIs(traceId))
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId))
.Then(_ => ThenTheHeaderIsReturned("Tom", "Laura"))
.BDDfy();
}
[Fact]
public void should_do_nothing_and_log_error()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Trace-Id", "{TraceId}")
};
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheTraceIdErrors())
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.Then(_ => ThenTheHeaderIsNotAdded("Trace-Id"))
.And(_ => ThenTheErrorIsLogged())
.BDDfy();
}
private void ThenTheErrorIsLogged()
{
_logger.Verify(x => x.LogError("Unable to add header to response Trace-Id: {TraceId}"), Times.Once);
}
private void ThenTheHeaderIsNotAdded(string key)
{
_response.Headers.TryGetValues(key, out var values).ShouldBeFalse();
}
private void GivenTheTraceIdIs(string traceId)
{
_placeholders.Setup(x => x.Get("{TraceId}")).Returns(new OkResponse<string>(traceId));
}
private void GivenTheTraceIdErrors()
{
_placeholders.Setup(x => x.Get("{TraceId}")).Returns(new ErrorResponse<string>(new AnyError()));
}
private void ThenTheHeaderIsReturned(string key, string value)
{
var values = _response.Headers.GetValues(key);
values.First().ShouldBe(value);
}
private void WhenIAdd()
{
_adder.Add(_addHeaders, _response);
}
private void GivenAResponseMessage()
{
_response = new HttpResponseMessage();
}
private void GivenTheAddHeaders(List<AddHeader> addHeaders)
{
_addHeaders = addHeaders;
}
}
}

View File

@ -26,6 +26,7 @@ namespace Ocelot.UnitTests.Headers
private HttpHeadersTransformationMiddleware _middleware; private HttpHeadersTransformationMiddleware _middleware;
private DownstreamContext _downstreamContext; private DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
private Mock<IAddHeadersToResponse> _addHeaders;
public HttpHeadersTransformationMiddlewareTests() public HttpHeadersTransformationMiddlewareTests()
{ {
@ -36,7 +37,8 @@ namespace Ocelot.UnitTests.Headers
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object); _addHeaders = new Mock<IAddHeadersToResponse>();
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object);
} }
[Fact] [Fact]
@ -49,9 +51,16 @@ namespace Ocelot.UnitTests.Headers
.When(x => WhenICallTheMiddleware()) .When(x => WhenICallTheMiddleware())
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
.And(x => ThenAddHeadersIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
private void ThenAddHeadersIsCalledCorrectly()
{
_addHeaders
.Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once);
}
private void WhenICallTheMiddleware() private void WhenICallTheMiddleware()
{ {
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();

View File

@ -7,20 +7,30 @@ using Ocelot.Configuration;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Responses; using Ocelot.Responses;
using System.Linq; using System.Linq;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
public class HttpResponseHeaderReplacerTests public class HttpResponseHeaderReplacerTests
{ {
private HttpResponseMessage _response; private HttpResponseMessage _response;
private Placeholders _placeholders;
private HttpResponseHeaderReplacer _replacer; private HttpResponseHeaderReplacer _replacer;
private List<HeaderFindAndReplace> _headerFindAndReplaces; private List<HeaderFindAndReplace> _headerFindAndReplaces;
private Response _result; private Response _result;
private HttpRequestMessage _request; private HttpRequestMessage _request;
private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo;
public HttpResponseHeaderReplacerTests() public HttpResponseHeaderReplacerTests()
{ {
_replacer = new HttpResponseHeaderReplacer(); _repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object);
_replacer = new HttpResponseHeaderReplacer(_placeholders);
} }
[Fact] [Fact]

View File

@ -0,0 +1,79 @@
using System;
using System.Net.Http;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Responses;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests.Infrastructure
{
public class PlaceholdersTests
{
private IPlaceholders _placeholders;
private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo;
public PlaceholdersTests()
{
_repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object);
}
[Fact]
public void should_return_base_url()
{
var baseUrl = "http://www.bbc.co.uk";
_finder.Setup(x => x.Find()).Returns(baseUrl);
var result = _placeholders.Get("{BaseUrl}");
result.Data.ShouldBe(baseUrl);
}
[Fact]
public void should_return_key_does_not_exist()
{
var result = _placeholders.Get("{Test}");
result.IsError.ShouldBeTrue();
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
}
[Fact]
public void should_return_downstream_base_url_when_port_is_not_80_or_443()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk");
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk/");
}
[Fact]
public void should_return_downstream_base_url_when_port_is_80_or_443()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk:123");
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk:123/");
}
[Fact]
public void should_return_key_does_not_exist_for_http_request_message()
{
var result = _placeholders.Get("{Test}", new System.Net.Http.HttpRequestMessage());
result.IsError.ShouldBeTrue();
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
}
[Fact]
public void should_return_trace_id()
{
var traceId = "123";
_repo.Setup(x => x.Get<string>("TraceId")).Returns(new OkResponse<string>(traceId));
var result = _placeholders.Get("{TraceId}");
result.Data.ShouldBe(traceId);
}
}
}

View File

@ -1,5 +1,6 @@
using Butterfly.Client.Tracing; using Butterfly.Client.Tracing;
using Moq; using Moq;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Requester; using Ocelot.Requester;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
@ -10,11 +11,13 @@ namespace Ocelot.UnitTests.Requester
{ {
private TracingHandlerFactory _factory; private TracingHandlerFactory _factory;
private Mock<IServiceTracer> _tracer; private Mock<IServiceTracer> _tracer;
private Mock<IRequestScopedDataRepository> _repo;
public TracingHandlerFactoryTests() public TracingHandlerFactoryTests()
{ {
_tracer = new Mock<IServiceTracer>(); _tracer = new Mock<IServiceTracer>();
_factory = new TracingHandlerFactory(_tracer.Object); _repo = new Mock<IRequestScopedDataRepository>();
_factory = new TracingHandlerFactory(_tracer.Object, _repo.Object);
} }
[Fact] [Fact]

View File

@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder
// If this test fails then it's because the number of error codes has changed. // If this test fails then it's because the number of error codes has changed.
// You should make the appropriate changes to the test cases here to ensure // You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion. // they cover all the error codes, and then modify this assertion.
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(33, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
} }
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)