mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
Added request id functionality and general refactoring..also turned out i wasnt returning headers....sigh
This commit is contained in:
parent
5082cc6c05
commit
56bf4014bd
@ -20,6 +20,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private Dictionary<string, string> _routeClaimRequirement;
|
private Dictionary<string, string> _routeClaimRequirement;
|
||||||
private bool _isAuthorised;
|
private bool _isAuthorised;
|
||||||
private List<ClaimToThing> _claimToQueries;
|
private List<ClaimToThing> _claimToQueries;
|
||||||
|
private string _requestIdHeaderKey;
|
||||||
|
|
||||||
public ReRouteBuilder()
|
public ReRouteBuilder()
|
||||||
{
|
{
|
||||||
@ -96,6 +97,12 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithRequestIdKey(string input)
|
||||||
|
{
|
||||||
|
_requestIdHeaderKey = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ReRouteBuilder WithClaimsToHeaders(List<ClaimToThing> input)
|
public ReRouteBuilder WithClaimsToHeaders(List<ClaimToThing> input)
|
||||||
{
|
{
|
||||||
_configHeaderExtractorProperties = input;
|
_configHeaderExtractorProperties = input;
|
||||||
@ -122,7 +129,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
|
|
||||||
public ReRoute Build()
|
public ReRoute Build()
|
||||||
{
|
{
|
||||||
return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries);
|
return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,12 +113,17 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
|
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
|
||||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||||
authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries
|
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
||||||
|
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||||
|
reRoute.RequestIdKey
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod,
|
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
|
||||||
upstreamTemplate, isAuthenticated, null, new List<ClaimToThing>(), new List<ClaimToThing>(), reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>());
|
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||||
|
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
||||||
|
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
||||||
|
reRoute.RequestIdKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ClaimToThing> GetAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
|
private List<ClaimToThing> GetAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
|
||||||
|
@ -4,7 +4,7 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
public class ReRoute
|
public class ReRoute
|
||||||
{
|
{
|
||||||
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties, List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries)
|
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties, List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries, string requestIdKey)
|
||||||
{
|
{
|
||||||
DownstreamTemplate = downstreamTemplate;
|
DownstreamTemplate = downstreamTemplate;
|
||||||
UpstreamTemplate = upstreamTemplate;
|
UpstreamTemplate = upstreamTemplate;
|
||||||
@ -14,6 +14,7 @@ namespace Ocelot.Configuration
|
|||||||
AuthenticationOptions = authenticationOptions;
|
AuthenticationOptions = authenticationOptions;
|
||||||
RouteClaimsRequirement = routeClaimsRequirement;
|
RouteClaimsRequirement = routeClaimsRequirement;
|
||||||
IsAuthorised = isAuthorised;
|
IsAuthorised = isAuthorised;
|
||||||
|
RequestIdKey = requestIdKey;
|
||||||
ClaimsToQueries = claimsToQueries
|
ClaimsToQueries = claimsToQueries
|
||||||
?? new List<ClaimToThing>();
|
?? new List<ClaimToThing>();
|
||||||
ClaimsToClaims = claimsToClaims
|
ClaimsToClaims = claimsToClaims
|
||||||
@ -33,6 +34,6 @@ namespace Ocelot.Configuration
|
|||||||
public List<ClaimToThing> ClaimsToHeaders { get; private set; }
|
public List<ClaimToThing> ClaimsToHeaders { get; private set; }
|
||||||
public List<ClaimToThing> ClaimsToClaims { get; private set; }
|
public List<ClaimToThing> ClaimsToClaims { get; private set; }
|
||||||
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
|
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
|
||||||
|
public string RequestIdKey { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,5 +20,6 @@ namespace Ocelot.Configuration.Yaml
|
|||||||
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
||||||
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
||||||
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
||||||
|
public string RequestIdKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Authentication.Handler.Creator;
|
using Ocelot.Authentication.Handler.Creator;
|
||||||
@ -16,6 +15,7 @@ using Ocelot.DownstreamRouteFinder.Finder;
|
|||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||||
using Ocelot.Headers;
|
using Ocelot.Headers;
|
||||||
|
using Ocelot.Infrastructure.Claims.Parser;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.QueryStrings;
|
using Ocelot.QueryStrings;
|
||||||
using Ocelot.Request.Builder;
|
using Ocelot.Request.Builder;
|
||||||
@ -24,8 +24,6 @@ using Ocelot.Responder;
|
|||||||
|
|
||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
using Infrastructure.Claims.Parser;
|
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddOcelotYamlConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
public static IServiceCollection AddOcelotYamlConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
||||||
@ -48,6 +46,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
|
|
||||||
// ocelot services.
|
// ocelot services.
|
||||||
|
services.AddSingleton<IRemoveHeaders, RemoveHeaders>();
|
||||||
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
|
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
|
||||||
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
||||||
services.AddSingleton<IAuthoriser, ClaimsAuthoriser>();
|
services.AddSingleton<IAuthoriser, ClaimsAuthoriser>();
|
||||||
|
@ -10,7 +10,6 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
|||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IDownstreamRouteFinder _downstreamRouteFinder;
|
private readonly IDownstreamRouteFinder _downstreamRouteFinder;
|
||||||
private readonly IRequestScopedDataRepository _requestScopedDataRepository;
|
|
||||||
|
|
||||||
public DownstreamRouteFinderMiddleware(RequestDelegate next,
|
public DownstreamRouteFinderMiddleware(RequestDelegate next,
|
||||||
IDownstreamRouteFinder downstreamRouteFinder,
|
IDownstreamRouteFinder downstreamRouteFinder,
|
||||||
@ -19,7 +18,6 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
|||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_downstreamRouteFinder = downstreamRouteFinder;
|
_downstreamRouteFinder = downstreamRouteFinder;
|
||||||
_requestScopedDataRepository = requestScopedDataRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
|
10
src/Ocelot/Headers/IRemoveHeaders.cs
Normal file
10
src/Ocelot/Headers/IRemoveHeaders.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
public interface IRemoveHeaders
|
||||||
|
{
|
||||||
|
Response Remove(HttpResponseHeaders headers);
|
||||||
|
}
|
||||||
|
}
|
30
src/Ocelot/Headers/RemoveHeaders.cs
Normal file
30
src/Ocelot/Headers/RemoveHeaders.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
public class RemoveHeaders : IRemoveHeaders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Some webservers return headers that cannot be forwarded to the client
|
||||||
|
/// in a given context such as transfer encoding chunked when ASP.NET is not
|
||||||
|
/// returning the response in this manner
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<string> _unsupportedHeaders = new List<string>
|
||||||
|
{
|
||||||
|
"Transfer-Encoding"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public Response Remove(HttpResponseHeaders headers)
|
||||||
|
{
|
||||||
|
foreach (var unsupported in _unsupportedHeaders)
|
||||||
|
{
|
||||||
|
headers.Remove(unsupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,8 @@ namespace Ocelot.Middleware
|
|||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ExceptionHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
|
public ExceptionHandlerMiddleware(RequestDelegate next,
|
||||||
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
|
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
|
||||||
@ -24,20 +25,25 @@ namespace Ocelot.Middleware
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var message =
|
var message = CreateMessage(context, e);
|
||||||
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
|
|
||||||
|
|
||||||
if (e.InnerException != null)
|
|
||||||
{
|
|
||||||
message = $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogError(new EventId(1, "Ocelot Global Error"), message, e);
|
_logger.LogError(new EventId(1, "Ocelot Global Error"), message, e);
|
||||||
|
|
||||||
context.Response.StatusCode = 500;
|
context.Response.StatusCode = 500;
|
||||||
context.Response.ContentType = "application/json";
|
context.Response.ContentType = "application/json";
|
||||||
await context.Response.WriteAsync("Internal Server Error");
|
await context.Response.WriteAsync("Internal Server Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string CreateMessage(HttpContext context, Exception e)
|
||||||
|
{
|
||||||
|
var message =
|
||||||
|
$"RequestId: {context.TraceIdentifier}, Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
|
||||||
|
|
||||||
|
if (e.InnerException != null)
|
||||||
|
{
|
||||||
|
message =
|
||||||
|
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using Ocelot.Headers.Middleware;
|
|||||||
using Ocelot.QueryStrings.Middleware;
|
using Ocelot.QueryStrings.Middleware;
|
||||||
using Ocelot.Request.Middleware;
|
using Ocelot.Request.Middleware;
|
||||||
using Ocelot.Requester.Middleware;
|
using Ocelot.Requester.Middleware;
|
||||||
|
using Ocelot.RequestId.Middleware;
|
||||||
using Ocelot.Responder.Middleware;
|
using Ocelot.Responder.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.Middleware
|
namespace Ocelot.Middleware
|
||||||
@ -49,6 +50,9 @@ namespace Ocelot.Middleware
|
|||||||
// Then we get the downstream route information
|
// Then we get the downstream route information
|
||||||
builder.UseDownstreamRouteFinderMiddleware();
|
builder.UseDownstreamRouteFinderMiddleware();
|
||||||
|
|
||||||
|
// Now we can look for the requestId
|
||||||
|
builder.UseRequestIdMiddleware();
|
||||||
|
|
||||||
// Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
|
// Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
|
||||||
builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware);
|
builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware);
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
@ -10,8 +12,15 @@ namespace Ocelot.Request.Builder
|
|||||||
{
|
{
|
||||||
public class HttpRequestBuilder : IRequestBuilder
|
public class HttpRequestBuilder : IRequestBuilder
|
||||||
{
|
{
|
||||||
public async Task<Response<Request>> Build(string httpMethod, string downstreamUrl, Stream content, IHeaderDictionary headers,
|
public async Task<Response<Request>> Build(
|
||||||
IRequestCookieCollection cookies, Microsoft.AspNetCore.Http.QueryString queryString, string contentType)
|
string httpMethod,
|
||||||
|
string downstreamUrl,
|
||||||
|
Stream content,
|
||||||
|
IHeaderDictionary headers,
|
||||||
|
IRequestCookieCollection cookies,
|
||||||
|
QueryString queryString,
|
||||||
|
string contentType,
|
||||||
|
RequestId.RequestId requestId)
|
||||||
{
|
{
|
||||||
var method = new HttpMethod(httpMethod);
|
var method = new HttpMethod(httpMethod);
|
||||||
|
|
||||||
@ -21,7 +30,7 @@ namespace Ocelot.Request.Builder
|
|||||||
|
|
||||||
if (content != null)
|
if (content != null)
|
||||||
{
|
{
|
||||||
httpRequestMessage.Content = new ByteArrayContent(ToByteArray(content));
|
httpRequestMessage.Content = new ByteArrayContent(await ToByteArray(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(contentType))
|
if (!string.IsNullOrEmpty(contentType))
|
||||||
@ -45,6 +54,11 @@ namespace Ocelot.Request.Builder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RequestKeyIsNotNull(requestId) && !RequestIdInHeaders(requestId, httpRequestMessage.Headers))
|
||||||
|
{
|
||||||
|
ForwardRequestIdToDownstreamService(requestId, httpRequestMessage);
|
||||||
|
}
|
||||||
|
|
||||||
var cookieContainer = new CookieContainer();
|
var cookieContainer = new CookieContainer();
|
||||||
|
|
||||||
//todo get rid of if
|
//todo get rid of if
|
||||||
@ -59,13 +73,34 @@ namespace Ocelot.Request.Builder
|
|||||||
return new OkResponse<Request>(new Request(httpRequestMessage, cookieContainer));
|
return new OkResponse<Request>(new Request(httpRequestMessage, cookieContainer));
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ToByteArray(Stream stream)
|
private void ForwardRequestIdToDownstreamService(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage)
|
||||||
|
{
|
||||||
|
httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RequestIdInHeaders(RequestId.RequestId requestId, HttpRequestHeaders headers)
|
||||||
|
{
|
||||||
|
IEnumerable<string> value;
|
||||||
|
if (headers.TryGetValues(requestId.RequestIdKey, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RequestKeyIsNotNull(RequestId.RequestId requestId)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(requestId?.RequestIdKey) && !string.IsNullOrEmpty(requestId.RequestIdValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[]> ToByteArray(Stream stream)
|
||||||
{
|
{
|
||||||
using (stream)
|
using (stream)
|
||||||
{
|
{
|
||||||
using (MemoryStream memStream = new MemoryStream())
|
using (var memStream = new MemoryStream())
|
||||||
{
|
{
|
||||||
stream.CopyTo(memStream);
|
await stream.CopyToAsync(memStream);
|
||||||
return memStream.ToArray();
|
return memStream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ namespace Ocelot.Request.Builder
|
|||||||
Stream content,
|
Stream content,
|
||||||
IHeaderDictionary headers,
|
IHeaderDictionary headers,
|
||||||
IRequestCookieCollection cookies,
|
IRequestCookieCollection cookies,
|
||||||
Microsoft.AspNetCore.Http.QueryString queryString,
|
QueryString queryString,
|
||||||
string contentType);
|
string contentType,
|
||||||
|
RequestId.RequestId requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,18 @@ namespace Ocelot.Request.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
var request = await _requestBuilder
|
var buildResult = await _requestBuilder
|
||||||
.Build(context.Request.Method, DownstreamUrl, context.Request.Body,
|
.Build(context.Request.Method, DownstreamUrl, context.Request.Body,
|
||||||
context.Request.Headers, context.Request.Cookies, context.Request.QueryString, context.Request.ContentType);
|
context.Request.Headers, context.Request.Cookies, context.Request.QueryString,
|
||||||
|
context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier));
|
||||||
|
|
||||||
if (request.IsError)
|
if (buildResult.IsError)
|
||||||
{
|
{
|
||||||
SetPipelineError(request.Errors);
|
SetPipelineError(buildResult.Errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetUpstreamRequestForThisRequest(request.Data);
|
SetUpstreamRequestForThisRequest(buildResult.Data);
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
}
|
}
|
||||||
|
9
src/Ocelot/RequestId/DefaultRequestIdKey.cs
Normal file
9
src/Ocelot/RequestId/DefaultRequestIdKey.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ocelot.RequestId
|
||||||
|
{
|
||||||
|
public static class DefaultRequestIdKey
|
||||||
|
{
|
||||||
|
// This is set incase anyone isnt doing this specifically with there requests.
|
||||||
|
// It will not be forwarded on to downstream services unless specfied in the config.
|
||||||
|
public const string Value = "RequestId";
|
||||||
|
}
|
||||||
|
}
|
45
src/Ocelot/RequestId/Middleware/RequestIdMiddleware.cs
Normal file
45
src/Ocelot/RequestId/Middleware/RequestIdMiddleware.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
namespace Ocelot.RequestId.Middleware
|
||||||
|
{
|
||||||
|
public class RequestIdMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
public RequestIdMiddleware(RequestDelegate next,
|
||||||
|
IRequestScopedDataRepository requestScopedDataRepository)
|
||||||
|
:base(requestScopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
SetTraceIdentifier(context);
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTraceIdentifier(HttpContext context)
|
||||||
|
{
|
||||||
|
var key = DefaultRequestIdKey.Value;
|
||||||
|
|
||||||
|
if (DownstreamRoute.ReRoute.RequestIdKey != null)
|
||||||
|
{
|
||||||
|
key = DownstreamRoute.ReRoute.RequestIdKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringValues requestId;
|
||||||
|
|
||||||
|
if (context.Request.Headers.TryGetValue(key, out requestId))
|
||||||
|
{
|
||||||
|
context.TraceIdentifier = requestId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Ocelot.RequestId.Middleware
|
||||||
|
{
|
||||||
|
public static class RequestIdMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<RequestIdMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/Ocelot/RequestId/RequestId.cs
Normal file
14
src/Ocelot/RequestId/RequestId.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Ocelot.RequestId
|
||||||
|
{
|
||||||
|
public class RequestId
|
||||||
|
{
|
||||||
|
public RequestId(string requestIdKey, string requestIdValue)
|
||||||
|
{
|
||||||
|
RequestIdKey = requestIdKey;
|
||||||
|
RequestIdValue = requestIdValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RequestIdKey { get; private set; }
|
||||||
|
public string RequestIdValue { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responder;
|
using Ocelot.Responder;
|
||||||
@ -25,7 +27,6 @@ namespace Ocelot.Requester.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
var response = await _requester.GetResponse(Request);
|
var response = await _requester.GetResponse(Request);
|
||||||
|
|
||||||
if (response.IsError)
|
if (response.IsError)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
using System.Net.Http;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Ocelot.Headers;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Responder
|
namespace Ocelot.Responder
|
||||||
@ -11,15 +15,42 @@ namespace Ocelot.Responder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HttpContextResponder : IHttpResponder
|
public class HttpContextResponder : IHttpResponder
|
||||||
{
|
{
|
||||||
|
private readonly IRemoveHeaders _removeHeaders;
|
||||||
|
|
||||||
|
public HttpContextResponder(IRemoveHeaders removeHeaders)
|
||||||
|
{
|
||||||
|
_removeHeaders = removeHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
public async Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
context.Response.OnStarting(x =>
|
_removeHeaders.Remove(response.Headers);
|
||||||
|
|
||||||
|
foreach (var httpResponseHeader in response.Headers)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = (int)response.StatusCode;
|
context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
context.Response.Headers.Add("Content-Length", new[] { content.Length.ToString() });
|
||||||
|
|
||||||
|
context.Response.OnStarting(state =>
|
||||||
|
{
|
||||||
|
var httpContext = (HttpContext)state;
|
||||||
|
|
||||||
|
httpContext.Response.StatusCode = (int)response.StatusCode;
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
}, context);
|
}, context);
|
||||||
|
|
||||||
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
|
using (var reader = new StreamReader(content))
|
||||||
|
{
|
||||||
|
var responseContent = reader.ReadToEnd();
|
||||||
|
await context.Response.WriteAsync(responseContent);
|
||||||
|
}
|
||||||
|
|
||||||
return new OkResponse();
|
return new OkResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
test/Ocelot.AcceptanceTests/RequestIdTests.cs
Normal file
108
test/Ocelot.AcceptanceTests/RequestIdTests.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Ocelot.Configuration.Yaml;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class RequestIdTests : IDisposable
|
||||||
|
{
|
||||||
|
private IWebHost _builder;
|
||||||
|
private readonly Steps _steps;
|
||||||
|
|
||||||
|
public RequestIdTests()
|
||||||
|
{
|
||||||
|
_steps = new Steps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_default_request_id_and_forward()
|
||||||
|
{
|
||||||
|
var yamlConfiguration = new YamlConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<YamlReRoute>
|
||||||
|
{
|
||||||
|
new YamlReRoute
|
||||||
|
{
|
||||||
|
DownstreamTemplate = "http://localhost:51879/",
|
||||||
|
UpstreamTemplate = "/",
|
||||||
|
UpstreamHttpMethod = "Get",
|
||||||
|
RequestIdKey = _steps.RequestIdKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(yamlConfiguration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheRequestIdIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_request_id_and_forward()
|
||||||
|
{
|
||||||
|
var yamlConfiguration = new YamlConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<YamlReRoute>
|
||||||
|
{
|
||||||
|
new YamlReRoute
|
||||||
|
{
|
||||||
|
DownstreamTemplate = "http://localhost:51879/",
|
||||||
|
UpstreamTemplate = "/",
|
||||||
|
UpstreamHttpMethod = "Get",
|
||||||
|
RequestIdKey = _steps.RequestIdKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(yamlConfiguration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId))
|
||||||
|
.Then(x => _steps.ThenTheRequestIdIsReturned(requestId))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string url)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
StringValues requestId;
|
||||||
|
context.Request.Headers.TryGetValue(_steps.RequestIdKey, out requestId);
|
||||||
|
context.Response.Headers.Add(_steps.RequestIdKey, requestId.First());
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builder?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
@ -26,6 +27,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
private HttpContent _postContent;
|
private HttpContent _postContent;
|
||||||
private BearerToken _token;
|
private BearerToken _token;
|
||||||
public HttpClient OcelotClient => _ocelotClient;
|
public HttpClient OcelotClient => _ocelotClient;
|
||||||
|
public string RequestIdKey = "OcRequestId";
|
||||||
|
|
||||||
public void GivenThereIsAConfiguration(YamlConfiguration yamlConfiguration)
|
public void GivenThereIsAConfiguration(YamlConfiguration yamlConfiguration)
|
||||||
{
|
{
|
||||||
@ -146,6 +148,13 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WhenIGetUrlOnTheApiGateway(string url, string requestId)
|
||||||
|
{
|
||||||
|
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId);
|
||||||
|
|
||||||
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
|
}
|
||||||
|
|
||||||
public void WhenIPostUrlOnTheApiGateway(string url)
|
public void WhenIPostUrlOnTheApiGateway(string url)
|
||||||
{
|
{
|
||||||
_response = _ocelotClient.PostAsync(url, _postContent).Result;
|
_response = _ocelotClient.PostAsync(url, _postContent).Result;
|
||||||
@ -171,5 +180,15 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient?.Dispose();
|
_ocelotClient?.Dispose();
|
||||||
_ocelotServer?.Dispose();
|
_ocelotServer?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ThenTheRequestIdIsReturned()
|
||||||
|
{
|
||||||
|
_response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ThenTheRequestIdIsReturned(string expected)
|
||||||
|
{
|
||||||
|
_response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
},
|
},
|
||||||
"Ocelot.ManualTest": {
|
"Ocelot.ManualTest": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "http://localhost:5000",
|
"launchUrl": "http://localhost:5000",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
@ -50,6 +50,10 @@ ReRoutes:
|
|||||||
# the value must be registered
|
# the value must be registered
|
||||||
RouteClaimsRequirement:
|
RouteClaimsRequirement:
|
||||||
UserType: registered
|
UserType: registered
|
||||||
|
# This tells Ocelot to look for a header and use its value as a request/correlation id.
|
||||||
|
# If it is set here then the id will be forwarded to the downstream service. If it
|
||||||
|
# does not then it will not be forwarded
|
||||||
|
RequestIdKey: OcRequestId
|
||||||
# The next re route...
|
# The next re route...
|
||||||
- DownstreamTemplate: http://jsonplaceholder.typicode.com/posts
|
- DownstreamTemplate: http://jsonplaceholder.typicode.com/posts
|
||||||
UpstreamTemplate: /posts
|
UpstreamTemplate: /posts
|
||||||
|
83
test/Ocelot.UnitTests/Errors/GobalErrorHandlerTests.cs
Normal file
83
test/Ocelot.UnitTests/Errors/GobalErrorHandlerTests.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.RequestId.Provider;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Errors
|
||||||
|
{
|
||||||
|
public class GobalErrorHandlerTests
|
||||||
|
{
|
||||||
|
private readonly Mock<ILoggerFactory> _loggerFactory;
|
||||||
|
private readonly Mock<ILogger<ExceptionHandlerMiddleware>> _logger;
|
||||||
|
private readonly Mock<IRequestIdProvider> _requestIdProvider;
|
||||||
|
private readonly string _url;
|
||||||
|
private readonly TestServer _server;
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private HttpResponseMessage _result;
|
||||||
|
|
||||||
|
public GobalErrorHandlerTests()
|
||||||
|
{
|
||||||
|
_url = "http://localhost:51879";
|
||||||
|
_logger = new Mock<ILogger<ExceptionHandlerMiddleware>>();
|
||||||
|
_loggerFactory = new Mock<ILoggerFactory>();
|
||||||
|
_requestIdProvider = new Mock<IRequestIdProvider>();
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(x =>
|
||||||
|
{
|
||||||
|
x.AddSingleton(_requestIdProvider.Object);
|
||||||
|
x.AddSingleton(_loggerFactory.Object);
|
||||||
|
})
|
||||||
|
.UseUrls(_url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(_url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseExceptionHandlerMiddleware();
|
||||||
|
|
||||||
|
app.Run(x =>
|
||||||
|
{
|
||||||
|
throw new Exception("BLAM");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_loggerFactory
|
||||||
|
.Setup(x => x.CreateLogger<ExceptionHandlerMiddleware>())
|
||||||
|
.Returns(_logger.Object);
|
||||||
|
|
||||||
|
_server = new TestServer(builder);
|
||||||
|
_client = _server.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_catch_exception_and_log()
|
||||||
|
{
|
||||||
|
this.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.And(x => x.TheLoggerIsCalledCorrectly())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TheLoggerIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_logger
|
||||||
|
.Verify(x => x.LogError(It.IsAny<EventId>(), It.IsAny<string>(), It.IsAny<Exception>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheMiddleware()
|
||||||
|
{
|
||||||
|
_result = _client.GetAsync(_url).Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
52
test/Ocelot.UnitTests/Headers/RemoveHeaders.cs
Normal file
52
test/Ocelot.UnitTests/Headers/RemoveHeaders.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Headers
|
||||||
|
{
|
||||||
|
public class RemoveHeaders
|
||||||
|
{
|
||||||
|
private HttpResponseHeaders _headers;
|
||||||
|
private readonly Ocelot.Headers.RemoveHeaders _removeHeaders;
|
||||||
|
private Response _result;
|
||||||
|
|
||||||
|
public RemoveHeaders()
|
||||||
|
{
|
||||||
|
_removeHeaders = new Ocelot.Headers.RemoveHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_remove_header()
|
||||||
|
{
|
||||||
|
var httpResponse = new HttpResponseMessage()
|
||||||
|
{
|
||||||
|
Headers = {{ "Transfer-Encoding", "chunked"}}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenAHttpContext(httpResponse.Headers))
|
||||||
|
.When(x => x.WhenIRemoveTheHeaders())
|
||||||
|
.Then(x => x.TheHeaderIsNoLongerInTheContext())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenAHttpContext(HttpResponseHeaders headers)
|
||||||
|
{
|
||||||
|
_headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIRemoveTheHeaders()
|
||||||
|
{
|
||||||
|
_result = _removeHeaders.Remove(_headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TheHeaderIsNoLongerInTheContext()
|
||||||
|
{
|
||||||
|
_result.IsError.ShouldBeFalse();
|
||||||
|
_headers.ShouldNotContain(x => x.Key == "Transfer-Encoding");
|
||||||
|
_headers.ShouldNotContain(x => x.Key == "transfer-encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
namespace Ocelot.UnitTests.Infrastructure
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Infrastructure
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@ -7,6 +8,9 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.TestHost;
|
using Microsoft.AspNetCore.TestHost;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.DownstreamRouteFinder;
|
||||||
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Request.Builder;
|
using Ocelot.Request.Builder;
|
||||||
using Ocelot.Request.Middleware;
|
using Ocelot.Request.Middleware;
|
||||||
@ -26,6 +30,7 @@ namespace Ocelot.UnitTests.Request
|
|||||||
private HttpResponseMessage _result;
|
private HttpResponseMessage _result;
|
||||||
private OkResponse<Ocelot.Request.Request> _request;
|
private OkResponse<Ocelot.Request.Request> _request;
|
||||||
private OkResponse<string> _downstreamUrl;
|
private OkResponse<string> _downstreamUrl;
|
||||||
|
private OkResponse<DownstreamRoute> _downstreamRoute;
|
||||||
|
|
||||||
public HttpRequestBuilderMiddlewareTests()
|
public HttpRequestBuilderMiddlewareTests()
|
||||||
{
|
{
|
||||||
@ -56,19 +61,34 @@ namespace Ocelot.UnitTests.Request
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void happy_path()
|
public void happy_path()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithRequestIdKey("LSRequestId").Build());
|
||||||
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
|
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
|
||||||
|
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||||
.And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer())))
|
.And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer())))
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
|
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
|
||||||
|
{
|
||||||
|
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
||||||
|
_scopedRepository
|
||||||
|
.Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
|
||||||
|
.Returns(_downstreamRoute);
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenTheRequestBuilderReturns(Ocelot.Request.Request request)
|
private void GivenTheRequestBuilderReturns(Ocelot.Request.Request request)
|
||||||
{
|
{
|
||||||
_request = new OkResponse<Ocelot.Request.Request>(request);
|
_request = new OkResponse<Ocelot.Request.Request>(request);
|
||||||
_requestBuilder
|
_requestBuilder
|
||||||
.Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(),
|
.Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(),
|
||||||
It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>()))
|
It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>()))
|
||||||
.ReturnsAsync(_request);
|
.ReturnsAsync(_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ namespace Ocelot.UnitTests.Request
|
|||||||
private string _contentType;
|
private string _contentType;
|
||||||
private readonly IRequestBuilder _requestBuilder;
|
private readonly IRequestBuilder _requestBuilder;
|
||||||
private Response<Ocelot.Request.Request> _result;
|
private Response<Ocelot.Request.Request> _result;
|
||||||
|
private Ocelot.RequestId.RequestId _requestId;
|
||||||
|
|
||||||
public RequestBuilderTests()
|
public RequestBuilderTests()
|
||||||
{
|
{
|
||||||
@ -114,6 +115,62 @@ namespace Ocelot.UnitTests.Request
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_request_id()
|
||||||
|
{
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenIHaveHttpMethod("GET"))
|
||||||
|
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
|
||||||
|
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary()))
|
||||||
|
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId)))
|
||||||
|
.When(x => x.WhenICreateARequest())
|
||||||
|
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
|
||||||
|
{
|
||||||
|
{"RequestId", requestId }
|
||||||
|
}))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_not_use_request_if_if_already_in_headers()
|
||||||
|
{
|
||||||
|
this.Given(x => x.GivenIHaveHttpMethod("GET"))
|
||||||
|
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
|
||||||
|
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary
|
||||||
|
{
|
||||||
|
{"RequestId", "534534gv54gv45g" }
|
||||||
|
}))
|
||||||
|
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString())))
|
||||||
|
.When(x => x.WhenICreateARequest())
|
||||||
|
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
|
||||||
|
{
|
||||||
|
{"RequestId", "534534gv54gv45g" }
|
||||||
|
}))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null, "blahh")]
|
||||||
|
[InlineData("", "blahh")]
|
||||||
|
[InlineData("RequestId", "")]
|
||||||
|
[InlineData("RequestId", null)]
|
||||||
|
public void should_not_use_request_id(string requestIdKey, string requestIdValue)
|
||||||
|
{
|
||||||
|
this.Given(x => x.GivenIHaveHttpMethod("GET"))
|
||||||
|
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
|
||||||
|
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary()))
|
||||||
|
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue)))
|
||||||
|
.When(x => x.WhenICreateARequest())
|
||||||
|
.And(x => x.ThenTheRequestIdIsNotInTheHeaders())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheRequestIdIs(Ocelot.RequestId.RequestId requestId)
|
||||||
|
{
|
||||||
|
_requestId = requestId;
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_use_cookies()
|
public void should_use_cookies()
|
||||||
{
|
{
|
||||||
@ -174,6 +231,11 @@ namespace Ocelot.UnitTests.Request
|
|||||||
_cookies = cookies;
|
_cookies = cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ThenTheRequestIdIsNotInTheHeaders()
|
||||||
|
{
|
||||||
|
_result.Data.HttpRequestMessage.Headers.ShouldNotContain(x => x.Key == "RequestId");
|
||||||
|
}
|
||||||
|
|
||||||
private void ThenTheCorrectHeadersAreUsed(IHeaderDictionary expected)
|
private void ThenTheCorrectHeadersAreUsed(IHeaderDictionary expected)
|
||||||
{
|
{
|
||||||
var expectedHeaders = expected.Select(x => new KeyValuePair<string, string[]>(x.Key, x.Value));
|
var expectedHeaders = expected.Select(x => new KeyValuePair<string, string[]>(x.Key, x.Value));
|
||||||
@ -219,7 +281,7 @@ namespace Ocelot.UnitTests.Request
|
|||||||
private void WhenICreateARequest()
|
private void WhenICreateARequest()
|
||||||
{
|
{
|
||||||
_result = _requestBuilder.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers,
|
_result = _requestBuilder.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers,
|
||||||
_cookies, _query, _contentType).Result;
|
_cookies, _query, _contentType, _requestId).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
132
test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs
Normal file
132
test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.DownstreamRouteFinder;
|
||||||
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.RequestId.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.RequestId
|
||||||
|
{
|
||||||
|
public class RequestIdMiddlewareTests
|
||||||
|
{
|
||||||
|
private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
|
||||||
|
private readonly string _url;
|
||||||
|
private readonly TestServer _server;
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private Response<DownstreamRoute> _downstreamRoute;
|
||||||
|
private HttpResponseMessage _result;
|
||||||
|
private string _value;
|
||||||
|
private string _key;
|
||||||
|
|
||||||
|
public RequestIdMiddlewareTests()
|
||||||
|
{
|
||||||
|
_url = "http://localhost:51879";
|
||||||
|
_scopedRepository = new Mock<IRequestScopedDataRepository>();
|
||||||
|
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(x =>
|
||||||
|
{
|
||||||
|
x.AddSingleton(_scopedRepository.Object);
|
||||||
|
})
|
||||||
|
.UseUrls(_url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(_url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseRequestIdMiddleware();
|
||||||
|
|
||||||
|
app.Run(x =>
|
||||||
|
{
|
||||||
|
x.Response.Headers.Add("LSRequestId", x.TraceIdentifier);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_server = new TestServer(builder);
|
||||||
|
_client = _server.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_add_request_id_to_repository()
|
||||||
|
{
|
||||||
|
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamTemplate("any old string")
|
||||||
|
.WithRequestIdKey("LSRequestId").Build());
|
||||||
|
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||||
|
.And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId))
|
||||||
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.Then(x => x.ThenTheTraceIdIs(requestId))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_add_trace_indentifier_to_repository()
|
||||||
|
{
|
||||||
|
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamTemplate("any old string")
|
||||||
|
.WithRequestIdKey("LSRequestId").Build());
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||||
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.Then(x => x.ThenTheTraceIdIsAnything())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheTraceIdIsAnything()
|
||||||
|
{
|
||||||
|
_result.Headers.GetValues("LSRequestId").First().ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheTraceIdIs(string expected)
|
||||||
|
{
|
||||||
|
_result.Headers.GetValues("LSRequestId").First().ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheRequestIdIsAddedToTheRequest(string key, string value)
|
||||||
|
{
|
||||||
|
_key = key;
|
||||||
|
_value = value;
|
||||||
|
_client.DefaultRequestHeaders.TryAddWithoutValidation(_key, _value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheMiddleware()
|
||||||
|
{
|
||||||
|
_result = _client.GetAsync(_url).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
|
||||||
|
{
|
||||||
|
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
||||||
|
_scopedRepository
|
||||||
|
.Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
|
||||||
|
.Returns(_downstreamRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
_server.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user