Remove Ocelot specific Middleware to make Ocelot more compatible with kestrel middleware and get ready for YARP

This commit is contained in:
Tom Pallister
2020-05-23 15:48:51 +01:00
committed by GitHub
parent 99a15d8668
commit fe3e8bd23a
214 changed files with 9574 additions and 9919 deletions

View File

@ -1,11 +1,11 @@
namespace Ocelot.Provider.Consul
{
using Errors;
using Ocelot.Errors;
public class UnableToSetConfigInConsulError : Error
{
public UnableToSetConfigInConsulError(string s)
: base(s, OcelotErrorCode.UnknownError)
: base(s, OcelotErrorCode.UnknownError, 404)
{
}
}

View File

@ -1,12 +1,12 @@
namespace Ocelot.Provider.Polly
{
using Errors;
using Ocelot.Errors;
using System;
public class RequestTimedOutError : Error
{
public RequestTimedOutError(Exception exception)
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError, 503)
{
}
}

View File

@ -5,7 +5,7 @@
public class UnableToSaveAcceptCommand : Error
{
public UnableToSaveAcceptCommand(string message)
: base(message, OcelotErrorCode.UnknownError)
: base(message, OcelotErrorCode.UnknownError, 404)
{
}
}

View File

@ -1,52 +1,56 @@
using Microsoft.AspNetCore.Authentication;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Threading.Tasks;
namespace Ocelot.Authentication.Middleware
namespace Ocelot.Authentication.Middleware
{
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Threading.Tasks;
using Ocelot.DownstreamRouteFinder.Middleware;
public class AuthenticationMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly RequestDelegate _next;
public AuthenticationMiddleware(OcelotRequestDelegate next,
public AuthenticationMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory)
: base(loggerFactory.CreateLogger<AuthenticationMiddleware>())
{
_next = next;
}
public async Task Invoke(DownstreamContext context)
public async Task Invoke(HttpContext httpContext)
{
if (context.HttpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(context.DownstreamReRoute))
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (httpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(downstreamReRoute))
{
Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");
Logger.LogInformation($"{httpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");
var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
var result = await httpContext.AuthenticateAsync(downstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
context.HttpContext.User = result.Principal;
httpContext.User = result.Principal;
if (context.HttpContext.User.Identity.IsAuthenticated)
if (httpContext.User.Identity.IsAuthenticated)
{
Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}");
await _next.Invoke(context);
Logger.LogInformation($"Client has been authenticated for {httpContext.Request.Path}");
await _next.Invoke(httpContext);
}
else
{
var error = new UnauthenticatedError(
$"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated");
$"Request for authenticated route {httpContext.Request.Path} by {httpContext.User.Identity.Name} was unauthenticated");
Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}");
Logger.LogWarning($"Client has NOT been authenticated for {httpContext.Request.Path} and pipeline error set. {error}");
SetPipelineError(context, error);
httpContext.Items.SetError(error);
}
}
else
{
Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}");
Logger.LogInformation($"No authentication needed for {httpContext.Request.Path}");
await _next.Invoke(context);
await _next.Invoke(httpContext);
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Authentication.Middleware
{
public static class AuthenticationMiddlewareMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseAuthenticationMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<AuthenticationMiddleware>();
}
}
namespace Ocelot.Authentication.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class AuthenticationMiddlewareMiddlewareExtensions
{
public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AuthenticationMiddleware>();
}
}
}

View File

@ -1,11 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
using Ocelot.Errors;
using System.Net;
namespace Ocelot.Authorisation
{
public class ClaimValueNotAuthorisedError : Error
{
public ClaimValueNotAuthorisedError(string message)
: base(message, OcelotErrorCode.ClaimValueNotAuthorisedError)
: base(message, OcelotErrorCode.ClaimValueNotAuthorisedError, 403)
{
}
}

View File

@ -1,91 +1,90 @@
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
namespace Ocelot.Authorisation
{
using Infrastructure.Claims.Parser;
public class ClaimsAuthoriser : IClaimsAuthoriser
{
private readonly IClaimsParser _claimsParser;
public ClaimsAuthoriser(IClaimsParser claimsParser)
{
_claimsParser = claimsParser;
}
namespace Ocelot.Authorisation
{
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
public class ClaimsAuthoriser : IClaimsAuthoriser
{
private readonly IClaimsParser _claimsParser;
public ClaimsAuthoriser(IClaimsParser claimsParser)
{
_claimsParser = claimsParser;
}
public Response<bool> Authorise(
ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
)
{
foreach (var required in routeClaimsRequirement)
{
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
if (values.IsError)
{
return new ErrorResponse<bool>(values.Errors);
}
if (values.Data != null)
{
foreach (var required in routeClaimsRequirement)
{
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
if (values.IsError)
{
// dynamic claim
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
if (match.Success)
{
var variableName = match.Captures[0].Value;
var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray();
if (matchingPlaceholders.Length == 1)
{
// match
var actualValue = matchingPlaceholders[0].Value;
var authorised = values.Data.Contains(actualValue);
if (!authorised)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}"));
}
}
else
{
// config error
if (matchingPlaceholders.Length == 0)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}"));
}
else
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}"));
}
}
}
else
return new ErrorResponse<bool>(values.Errors);
}
if (values.Data != null)
{
// dynamic claim
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
if (match.Success)
{
// static claim
var authorised = values.Data.Contains(required.Value);
if (!authorised)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
}
}
}
else
{
return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}"));
}
}
return new OkResponse<bool>(true);
}
}
var variableName = match.Captures[0].Value;
var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray();
if (matchingPlaceholders.Length == 1)
{
// match
var actualValue = matchingPlaceholders[0].Value;
var authorised = values.Data.Contains(actualValue);
if (!authorised)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}"));
}
}
else
{
// config error
if (matchingPlaceholders.Length == 0)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}"));
}
else
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}"));
}
}
}
else
{
// static claim
var authorised = values.Data.Contains(required.Value);
if (!authorised)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
}
}
}
else
{
return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}"));
}
}
return new OkResponse<bool>(true);
}
}
}

View File

@ -1,108 +1,112 @@
namespace Ocelot.Authorisation.Middleware
{
using Configuration;
using Logging;
using Ocelot.Middleware;
using Responses;
using System.Threading.Tasks;
public class AuthorisationMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IClaimsAuthoriser _claimsAuthoriser;
private readonly IScopesAuthoriser _scopesAuthoriser;
public AuthorisationMiddleware(OcelotRequestDelegate next,
IClaimsAuthoriser claimsAuthoriser,
IScopesAuthoriser scopesAuthoriser,
IOcelotLoggerFactory loggerFactory)
: base(loggerFactory.CreateLogger<AuthorisationMiddleware>())
{
_next = next;
_claimsAuthoriser = claimsAuthoriser;
_scopesAuthoriser = scopesAuthoriser;
}
public async Task Invoke(DownstreamContext context)
{
if (!IsOptionsHttpMethod(context) && IsAuthenticatedRoute(context.DownstreamReRoute))
{
Logger.LogInformation("route is authenticated scopes must be checked");
var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes);
if (authorised.IsError)
{
Logger.LogWarning("error authorising user scopes");
SetPipelineError(context, authorised.Errors);
return;
}
if (IsAuthorised(authorised))
{
Logger.LogInformation("user scopes is authorised calling next authorisation checks");
}
else
{
Logger.LogWarning("user scopes is not authorised setting pipeline error");
SetPipelineError(context, new UnauthorisedError(
$"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
}
}
if (!IsOptionsHttpMethod(context) && IsAuthorisedRoute(context.DownstreamReRoute))
{
Logger.LogInformation("route is authorised");
namespace Ocelot.Authorisation.Middleware
{
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement, context.TemplatePlaceholderNameAndValues);
if (authorised.IsError)
{
Logger.LogWarning($"Error whilst authorising {context.HttpContext.User.Identity.Name}. Setting pipeline error");
SetPipelineError(context, authorised.Errors);
return;
}
if (IsAuthorised(authorised))
{
Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}.");
await _next.Invoke(context);
}
else
{
Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error");
SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
}
}
else
{
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
await _next.Invoke(context);
}
}
private static bool IsAuthorised(Response<bool> authorised)
{
return authorised.Data;
}
private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute)
{
return reRoute.IsAuthenticated;
}
private static bool IsAuthorisedRoute(DownstreamReRoute reRoute)
{
return reRoute.IsAuthorised;
public class AuthorisationMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IClaimsAuthoriser _claimsAuthoriser;
private readonly IScopesAuthoriser _scopesAuthoriser;
public AuthorisationMiddleware(RequestDelegate next,
IClaimsAuthoriser claimsAuthoriser,
IScopesAuthoriser scopesAuthoriser,
IOcelotLoggerFactory loggerFactory)
: base(loggerFactory.CreateLogger<AuthorisationMiddleware>())
{
_next = next;
_claimsAuthoriser = claimsAuthoriser;
_scopesAuthoriser = scopesAuthoriser;
}
private static bool IsOptionsHttpMethod(DownstreamContext context)
public async Task Invoke(HttpContext httpContext)
{
return context.HttpContext.Request.Method.ToUpper() == "OPTIONS";
}
}
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (!IsOptionsHttpMethod(httpContext) && IsAuthenticatedRoute(downstreamReRoute))
{
Logger.LogInformation("route is authenticated scopes must be checked");
var authorised = _scopesAuthoriser.Authorise(httpContext.User, downstreamReRoute.AuthenticationOptions.AllowedScopes);
if (authorised.IsError)
{
Logger.LogWarning("error authorising user scopes");
httpContext.Items.UpsertErrors(authorised.Errors);
return;
}
if (IsAuthorised(authorised))
{
Logger.LogInformation("user scopes is authorised calling next authorisation checks");
}
else
{
Logger.LogWarning("user scopes is not authorised setting pipeline error");
httpContext.Items.SetError(new UnauthorisedError(
$"{httpContext.User.Identity.Name} unable to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
}
}
if (!IsOptionsHttpMethod(httpContext) && IsAuthorisedRoute(downstreamReRoute))
{
Logger.LogInformation("route is authorised");
var authorised = _claimsAuthoriser.Authorise(httpContext.User, downstreamReRoute.RouteClaimsRequirement, httpContext.Items.TemplatePlaceholderNameAndValues());
if (authorised.IsError)
{
Logger.LogWarning($"Error whilst authorising {httpContext.User.Identity.Name}. Setting pipeline error");
httpContext.Items.UpsertErrors(authorised.Errors);
return;
}
if (IsAuthorised(authorised))
{
Logger.LogInformation($"{httpContext.User.Identity.Name} has succesfully been authorised for {downstreamReRoute.UpstreamPathTemplate.OriginalValue}.");
await _next.Invoke(httpContext);
}
else
{
Logger.LogWarning($"{httpContext.User.Identity.Name} is not authorised to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error");
httpContext.Items.SetError(new UnauthorisedError($"{httpContext.User.Identity.Name} is not authorised to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
}
}
else
{
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
await _next.Invoke(httpContext);
}
}
private static bool IsAuthorised(Response<bool> authorised)
{
return authorised.Data;
}
private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute)
{
return reRoute.IsAuthenticated;
}
private static bool IsAuthorisedRoute(DownstreamReRoute reRoute)
{
return reRoute.IsAuthorised;
}
private static bool IsOptionsHttpMethod(HttpContext httpContext)
{
return httpContext.Request.Method.ToUpper() == "OPTIONS";
}
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Authorisation.Middleware
{
public static class AuthorisationMiddlewareMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseAuthorisationMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<AuthorisationMiddleware>();
}
}
namespace Ocelot.Authorisation.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class AuthorisationMiddlewareMiddlewareExtensions
{
public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AuthorisationMiddleware>();
}
}
}

View File

@ -1,11 +1,11 @@
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
public class ScopeNotAuthorisedError : Error
{
public ScopeNotAuthorisedError(string message)
: base(message, OcelotErrorCode.ScopeNotAuthorisedError)
: base(message, OcelotErrorCode.ScopeNotAuthorisedError, 403)
{
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
public class UnauthorisedError : Error
{
namespace Ocelot.Authorisation
{
using Ocelot.Errors;
public class UnauthorisedError : Error
{
public UnauthorisedError(string message)
: base(message, OcelotErrorCode.UnauthorizedError)
{
}
}
: base(message, OcelotErrorCode.UnauthorizedError, 403)
{
}
}
}

View File

@ -1,11 +1,11 @@
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
public class UserDoesNotHaveClaimError : Error
{
public UserDoesNotHaveClaimError(string message)
: base(message, OcelotErrorCode.UserDoesNotHaveClaimError)
: base(message, OcelotErrorCode.UserDoesNotHaveClaimError, 403)
{
}
}

View File

@ -1,18 +1,18 @@
using Ocelot.Middleware;
using System.Text;
using System.Threading.Tasks;
namespace Ocelot.Cache
namespace Ocelot.Cache
{
using Ocelot.Request.Middleware;
using System.Text;
using System.Threading.Tasks;
public class CacheKeyGenerator : ICacheKeyGenerator
{
public string GenerateRequestCacheKey(DownstreamContext context)
public string GenerateRequestCacheKey(DownstreamRequest downstreamRequest)
{
string hashedContent = null;
StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}");
if (context.DownstreamRequest.Content != null)
StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{downstreamRequest.Method}-{downstreamRequest.OriginalString}");
if (downstreamRequest.Content != null)
{
string requestContentString = Task.Run(async () => await context.DownstreamRequest.Content.ReadAsStringAsync()).Result;
string requestContentString = Task.Run(async () => await downstreamRequest.Content.ReadAsStringAsync()).Result;
downStreamUrlKeyBuilder.Append(requestContentString);
}

View File

@ -1,9 +1,9 @@
using Ocelot.Middleware;
namespace Ocelot.Cache
namespace Ocelot.Cache
{
using Ocelot.Request.Middleware;
public interface ICacheKeyGenerator
{
string GenerateRequestCacheKey(DownstreamContext context);
string GenerateRequestCacheKey(DownstreamRequest downstreamRequest);
}
}

View File

@ -1,122 +1,129 @@
namespace Ocelot.Cache.Middleware
{
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Ocelot.Cache.Middleware
{
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
public class OutputCacheMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IOcelotCache<CachedResponse> _outputCache;
private readonly ICacheKeyGenerator _cacheGeneratot;
public OutputCacheMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IOcelotCache<CachedResponse> outputCache,
ICacheKeyGenerator cacheGeneratot)
: base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
{
_next = next;
_outputCache = outputCache;
_cacheGeneratot = cacheGeneratot;
}
public async Task Invoke(DownstreamContext context)
{
if (!context.DownstreamReRoute.IsCached)
{
await _next.Invoke(context);
return;
public class OutputCacheMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IOcelotCache<CachedResponse> _outputCache;
private readonly ICacheKeyGenerator _cacheGenerator;
public OutputCacheMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IOcelotCache<CachedResponse> outputCache,
ICacheKeyGenerator cacheGenerator)
: base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
{
_next = next;
_outputCache = outputCache;
_cacheGenerator = cacheGenerator;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (!downstreamReRoute.IsCached)
{
await _next.Invoke(httpContext);
return;
}
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
string downStreamRequestCacheKey = _cacheGeneratot.GenerateRequestCacheKey(context);
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
var cached = _outputCache.Get(downStreamRequestCacheKey, context.DownstreamReRoute.CacheOptions.Region);
if (cached != null)
{
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
var response = CreateHttpResponseMessage(cached);
SetHttpResponseMessageThisRequest(context, response);
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
return;
var downstreamRequest = httpContext.Items.DownstreamRequest();
var downstreamUrlKey = $"{downstreamRequest.Method}-{downstreamRequest.OriginalString}";
string downStreamRequestCacheKey = _cacheGenerator.GenerateRequestCacheKey(downstreamRequest);
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
var cached = _outputCache.Get(downStreamRequestCacheKey, downstreamReRoute.CacheOptions.Region);
if (cached != null)
{
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
var response = CreateHttpResponseMessage(cached);
SetHttpResponseMessageThisRequest(httpContext, response);
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
return;
}
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
await _next.Invoke(httpContext);
if (httpContext.Items.Errors().Count > 0)
{
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
return;
}
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
await _next.Invoke(context);
if (context.IsError)
{
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
return;
}
cached = await CreateCachedResponse(context.DownstreamResponse);
_outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
}
private void SetHttpResponseMessageThisRequest(DownstreamContext context,
DownstreamResponse response)
{
context.DownstreamResponse = response;
}
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
{
if (cached == null)
{
return null;
}
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
var streamContent = new StreamContent(content);
foreach (var header in cached.ContentHeaders)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase);
}
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
{
if (response == null)
{
return null;
}
var statusCode = response.StatusCode;
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
string body = null;
if (response.Content != null)
{
var content = await response.Content.ReadAsByteArrayAsync();
body = Convert.ToBase64String(content);
}
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase);
return cached;
}
}
}
var downstreamResponse = httpContext.Items.DownstreamResponse();
cached = await CreateCachedResponse(downstreamResponse);
_outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(downstreamReRoute.CacheOptions.TtlSeconds), downstreamReRoute.CacheOptions.Region);
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
}
private void SetHttpResponseMessageThisRequest(HttpContext context,
DownstreamResponse response)
{
context.Items.UpsertDownstreamResponse(response);
}
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
{
if (cached == null)
{
return null;
}
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
var streamContent = new StreamContent(content);
foreach (var header in cached.ContentHeaders)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase);
}
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
{
if (response == null)
{
return null;
}
var statusCode = response.StatusCode;
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
string body = null;
if (response.Content != null)
{
var content = await response.Content.ReadAsByteArrayAsync();
body = Convert.ToBase64String(content);
}
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase);
return cached;
}
}
}

View File

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Cache.Middleware
{
public static class OutputCacheMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseOutputCacheMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<OutputCacheMiddleware>();
}
}
namespace Ocelot.Cache.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class OutputCacheMiddlewareExtensions
{
public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<OutputCacheMiddleware>();
}
}
}

View File

@ -1,10 +1,10 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Claims.Middleware
namespace Ocelot.Claims.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class ClaimsBuilderMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder)
public static IApplicationBuilder UseClaimsToClaimsMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ClaimsToClaimsMiddleware>();
}

View File

@ -1,42 +1,46 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Claims.Middleware
{
public class ClaimsToClaimsMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IAddClaimsToRequest _addClaimsToRequest;
public ClaimsToClaimsMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IAddClaimsToRequest addClaimsToRequest)
: base(loggerFactory.CreateLogger<ClaimsToClaimsMiddleware>())
{
_next = next;
_addClaimsToRequest = addClaimsToRequest;
}
public async Task Invoke(DownstreamContext context)
{
if (context.DownstreamReRoute.ClaimsToClaims.Any())
{
Logger.LogDebug("this route has instructions to convert claims to other claims");
var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext);
if (result.IsError)
{
Logger.LogDebug("error converting claims to other claims, setting pipeline error");
SetPipelineError(context, result.Errors);
return;
}
}
await _next.Invoke(context);
}
}
}
namespace Ocelot.Claims.Middleware
{
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
public class ClaimsToClaimsMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IAddClaimsToRequest _addClaimsToRequest;
public ClaimsToClaimsMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IAddClaimsToRequest addClaimsToRequest)
: base(loggerFactory.CreateLogger<ClaimsToClaimsMiddleware>())
{
_next = next;
_addClaimsToRequest = addClaimsToRequest;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (downstreamReRoute.ClaimsToClaims.Any())
{
Logger.LogDebug("this route has instructions to convert claims to other claims");
var result = _addClaimsToRequest.SetClaimsOnContext(downstreamReRoute.ClaimsToClaims, httpContext);
if (result.IsError)
{
Logger.LogDebug("error converting claims to other claims, setting pipeline error");
httpContext.Items.UpsertErrors(result.Errors);
return;
}
}
await _next.Invoke(httpContext);
}
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Configuration.Parser
{
public class InstructionNotForClaimsError : Error
{
using Ocelot.Errors;
namespace Ocelot.Configuration.Parser
{
public class InstructionNotForClaimsError : Error
{
public InstructionNotForClaimsError()
: base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError)
{
}
}
: base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError, 404)
{
}
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Configuration.Parser
{
public class NoInstructionsError : Error
{
using Ocelot.Errors;
namespace Ocelot.Configuration.Parser
{
public class NoInstructionsError : Error
{
public NoInstructionsError(string splitToken)
: base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError)
{
}
}
: base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError, 404)
{
}
}
}

View File

@ -1,13 +1,13 @@
using Ocelot.Errors;
using System;
namespace Ocelot.Configuration.Parser
{
public class ParsingConfigurationHeaderError : Error
{
using Ocelot.Errors;
using System;
namespace Ocelot.Configuration.Parser
{
public class ParsingConfigurationHeaderError : Error
{
public ParsingConfigurationHeaderError(Exception exception)
: base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError)
{
}
}
: base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError, 404)
{
}
}
}

View File

@ -1,32 +1,32 @@
namespace Ocelot.Configuration
{
using Ocelot.Configuration.File;
using Ocelot.Values;
using System.Collections.Generic;
using System.Net.Http;
public class ReRoute
{
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
List<AggregateReRouteConfig> downstreamReRouteConfig,
namespace Ocelot.Configuration
{
using Ocelot.Configuration.File;
using Ocelot.Values;
using System.Collections.Generic;
using System.Net.Http;
public class ReRoute
{
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
List<AggregateReRouteConfig> downstreamReRouteConfig,
List<HttpMethod> upstreamHttpMethod,
UpstreamPathTemplate upstreamTemplatePattern,
string upstreamHost,
string aggregator)
{
UpstreamHost = upstreamHost;
DownstreamReRoute = downstreamReRoute;
DownstreamReRouteConfig = downstreamReRouteConfig;
UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern;
Aggregator = aggregator;
}
public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; }
public List<HttpMethod> UpstreamHttpMethod { get; private set; }
public string UpstreamHost { get; private set; }
public List<DownstreamReRoute> DownstreamReRoute { get; private set; }
public List<AggregateReRouteConfig> DownstreamReRouteConfig { get; private set; }
public string Aggregator { get; private set; }
}
string upstreamHost,
string aggregator)
{
UpstreamHost = upstreamHost;
DownstreamReRoute = downstreamReRoute;
DownstreamReRouteConfig = downstreamReRouteConfig;
UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern;
Aggregator = aggregator;
}
public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; }
public List<HttpMethod> UpstreamHttpMethod { get; private set; }
public string UpstreamHost { get; private set; }
public List<DownstreamReRoute> DownstreamReRoute { get; private set; }
public List<AggregateReRouteConfig> DownstreamReRouteConfig { get; private set; }
public string Aggregator { get; private set; }
}
}

View File

@ -1,12 +1,12 @@
namespace Ocelot.Configuration.Validator
{
using Errors;
public class FileValidationFailedError : Error
{
namespace Ocelot.Configuration.Validator
{
using Errors;
public class FileValidationFailedError : Error
{
public FileValidationFailedError(string message)
: base(message, OcelotErrorCode.FileValidationFailedError)
{
}
}
: base(message, OcelotErrorCode.FileValidationFailedError, 404)
{
}
}
}

View File

@ -1,6 +1,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Multiplexer;
using System;
using System.Net.Http;
using Ocelot.Configuration;

View File

@ -27,7 +27,7 @@ namespace Ocelot.DependencyInjection
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Multiplexer;
using Ocelot.PathManipulation;
using Ocelot.QueryStrings;
using Ocelot.RateLimit;
@ -128,7 +128,6 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
Services.AddMemoryCache();
Services.TryAddSingleton<OcelotDiagnosticListener>();
Services.TryAddSingleton<IMultiplexer, Multiplexer>();
Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
Services.TryAddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();

View File

@ -1,42 +1,50 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.DownstreamPathManipulation.Middleware
{
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Logging;
using Microsoft.AspNetCore.Http;
using Ocelot.Middleware;
using Ocelot.PathManipulation;
using Ocelot.DownstreamRouteFinder.Middleware;
namespace Ocelot.PathManipulation.Middleware
{
public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate;
public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate;
public ClaimsToDownstreamPathMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IChangeDownstreamPathTemplate changeDownstreamPathTemplate)
: base(loggerFactory.CreateLogger<ClaimsToDownstreamPathMiddleware>())
{
_next = next;
_changeDownstreamPathTemplate = changeDownstreamPathTemplate;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (downstreamReRoute.ClaimsToPath.Any())
{
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path");
public ClaimsToDownstreamPathMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IChangeDownstreamPathTemplate changeDownstreamPathTemplate)
: base(loggerFactory.CreateLogger<ClaimsToDownstreamPathMiddleware>())
{
_next = next;
_changeDownstreamPathTemplate = changeDownstreamPathTemplate;
}
var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues();
public async Task Invoke(DownstreamContext context)
{
if (context.DownstreamReRoute.ClaimsToPath.Any())
{
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path");
var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(context.DownstreamReRoute.ClaimsToPath, context.HttpContext.User.Claims,
context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
if (response.IsError)
{
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
SetPipelineError(context, response.Errors);
return;
}
}
await _next.Invoke(context);
}
}
}
var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(downstreamReRoute.ClaimsToPath, httpContext.User.Claims,
downstreamReRoute.DownstreamPathTemplate, templatePlaceholderNameAndValues);
if (response.IsError)
{
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
httpContext.Items.UpsertErrors(response.Errors);
return;
}
}
await _next.Invoke(httpContext);
}
}
}

View File

@ -1,10 +1,10 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.PathManipulation.Middleware
namespace Ocelot.DownstreamPathManipulation.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class ClaimsToDownstreamPathMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseClaimsToDownstreamPathMiddleware(this IOcelotPipelineBuilder builder)
public static IApplicationBuilder UseClaimsToDownstreamPathMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ClaimsToDownstreamPathMiddleware>();
}

View File

@ -1,18 +1,22 @@
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using System.Collections.Generic;
namespace Ocelot.DownstreamRouteFinder
{
public class DownstreamRoute
{
public DownstreamRoute(List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
{
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
ReRoute = reRoute;
}
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
public ReRoute ReRoute { get; private set; }
}
namespace Ocelot.DownstreamRouteFinder
{
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using System.Collections.Generic;
public class DownstreamRoute
{
public DownstreamRoute()
{
}
public DownstreamRoute(List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
{
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
ReRoute = reRoute;
}
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
public ReRoute ReRoute { get; private set; }
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamRouteFinder.Finder
{
public class UnableToFindDownstreamRouteError : Error
{
using Ocelot.Errors;
namespace Ocelot.DownstreamRouteFinder.Finder
{
public class UnableToFindDownstreamRouteError : Error
{
public UnableToFindDownstreamRouteError(string path, string httpVerb)
: base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError)
{
}
}
: base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError, 404)
{
}
}
}

View File

@ -1,59 +1,61 @@
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.DownstreamRouteFinder.Middleware
{
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
public class DownstreamRouteFinderMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly RequestDelegate _next;
private readonly IDownstreamRouteProviderFactory _factory;
private readonly IMultiplexer _multiplexer;
public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next,
public DownstreamRouteFinderMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IDownstreamRouteProviderFactory downstreamRouteFinder,
IMultiplexer multiplexer)
IDownstreamRouteProviderFactory downstreamRouteFinder
)
: base(loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>())
{
_multiplexer = multiplexer;
_next = next;
_factory = downstreamRouteFinder;
}
public async Task Invoke(DownstreamContext context)
public async Task Invoke(HttpContext httpContext)
{
var upstreamUrlPath = context.HttpContext.Request.Path.ToString();
var upstreamUrlPath = httpContext.Request.Path.ToString();
var upstreamQueryString = context.HttpContext.Request.QueryString.ToString();
var upstreamQueryString = httpContext.Request.QueryString.ToString();
var upstreamHost = context.HttpContext.Request.Headers["Host"];
var upstreamHost = httpContext.Request.Headers["Host"];
Logger.LogDebug($"Upstream url path is {upstreamUrlPath}");
var provider = _factory.Get(context.Configuration);
var internalConfiguration = httpContext.Items.IInternalConfiguration();
var downstreamRoute = provider.Get(upstreamUrlPath, upstreamQueryString, context.HttpContext.Request.Method, context.Configuration, upstreamHost);
var provider = _factory.Get(internalConfiguration);
if (downstreamRoute.IsError)
var response = provider.Get(upstreamUrlPath, upstreamQueryString, httpContext.Request.Method, internalConfiguration, upstreamHost);
if (response.IsError)
{
Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}");
Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {response.Errors.ToErrorString()}");
SetPipelineError(context, downstreamRoute.Errors);
httpContext.Items.UpsertErrors(response.Errors);
return;
}
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
var downstreamPathTemplates = string.Join(", ", response.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
// why set both of these on HttpContext
httpContext.Items.UpsertTemplatePlaceholderNameAndValues(response.Data.TemplatePlaceholderNameAndValues);
await _multiplexer.Multiplex(context, downstreamRoute.Data.ReRoute, _next);
httpContext.Items.UpsertDownstreamRoute(response.Data);
await _next.Invoke(httpContext);
}
}
}

View File

@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.DownstreamRouteFinder.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class DownstreamRouteFinderMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseDownstreamRouteFinderMiddleware(this IOcelotPipelineBuilder builder)
public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<DownstreamRouteFinderMiddleware>();
}

View File

@ -1,132 +1,149 @@
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Threading.Tasks;
namespace Ocelot.DownstreamUrlCreator.Middleware
{
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Microsoft.AspNetCore.Http;
using Ocelot.Request.Middleware;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Threading.Tasks;
using Ocelot.DownstreamRouteFinder.Middleware;
namespace Ocelot.DownstreamUrlCreator.Middleware
{
using System.Text.RegularExpressions;
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IDownstreamPathPlaceholderReplacer _replacer;
public DownstreamUrlCreatorMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IDownstreamPathPlaceholderReplacer replacer
)
: base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
{
_next = next;
_replacer = replacer;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IDownstreamPathPlaceholderReplacer _replacer;
var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues();
var response = _replacer
.Replace(downstreamReRoute.DownstreamPathTemplate.Value, templatePlaceholderNameAndValues);
public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IDownstreamPathPlaceholderReplacer replacer)
: base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
{
_next = next;
_replacer = replacer;
}
var downstreamRequest = httpContext.Items.DownstreamRequest();
public async Task Invoke(DownstreamContext context)
{
var response = _replacer
.Replace(context.DownstreamReRoute.DownstreamPathTemplate.Value, context.TemplatePlaceholderNameAndValues);
if (response.IsError)
if (response.IsError)
{
Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
httpContext.Items.UpsertErrors(response.Errors);
return;
}
if (!string.IsNullOrEmpty(downstreamReRoute.DownstreamScheme))
{
Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
SetPipelineError(context, response.Errors);
return;
//todo make sure this works, hopefully there is a test ;E
httpContext.Items.DownstreamRequest().Scheme = downstreamReRoute.DownstreamScheme;
}
if (!string.IsNullOrEmpty(context.DownstreamReRoute.DownstreamScheme))
{
context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
}
var internalConfiguration = httpContext.Items.IInternalConfiguration();
if (ServiceFabricRequest(context))
if (ServiceFabricRequest(internalConfiguration, downstreamReRoute))
{
var pathAndQuery = CreateServiceFabricUri(context, response);
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
context.DownstreamRequest.Query = pathAndQuery.query;
}
else
{
var dsPath = response.Data;
var pathAndQuery = CreateServiceFabricUri(downstreamRequest, downstreamReRoute, templatePlaceholderNameAndValues, response);
if (ContainsQueryString(dsPath))
//todo check this works again hope there is a test..
downstreamRequest.AbsolutePath = pathAndQuery.path;
downstreamRequest.Query = pathAndQuery.query;
}
else
{
var dsPath = response.Data;
if (ContainsQueryString(dsPath))
{
context.DownstreamRequest.AbsolutePath = GetPath(dsPath);
if (string.IsNullOrEmpty(context.DownstreamRequest.Query))
{
context.DownstreamRequest.Query = GetQueryString(dsPath);
}
else
{
context.DownstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&');
}
}
else
downstreamRequest.AbsolutePath = GetPath(dsPath);
if (string.IsNullOrEmpty(downstreamRequest.Query))
{
downstreamRequest.Query = GetQueryString(dsPath);
}
else
{
downstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&');
}
}
else
{
RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context);
context.DownstreamRequest.AbsolutePath = dsPath.Value;
}
}
Logger.LogDebug($"Downstream url is {context.DownstreamRequest}");
await _next.Invoke(context);
}
private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamContext context)
{
foreach (var nAndV in context.TemplatePlaceholderNameAndValues)
{
var name = nAndV.Name.Replace("{", "").Replace("}", "");
if (context.DownstreamRequest.Query.Contains(name) &&
context.DownstreamRequest.Query.Contains(nAndV.Value))
{
var questionMarkOrAmpersand = context.DownstreamRequest.Query.IndexOf(name, StringComparison.Ordinal);
context.DownstreamRequest.Query = context.DownstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1);
var rgx = new Regex($@"\b{name}={nAndV.Value}\b");
context.DownstreamRequest.Query = rgx.Replace(context.DownstreamRequest.Query, "");
if (!string.IsNullOrEmpty(context.DownstreamRequest.Query))
{
context.DownstreamRequest.Query = '?' + context.DownstreamRequest.Query.Substring(1);
}
}
}
}
private string GetPath(DownstreamPath dsPath)
{
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal));
}
private string GetQueryString(DownstreamPath dsPath)
{
return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal));
}
private bool ContainsQueryString(DownstreamPath dsPath)
{
return dsPath.Value.Contains("?");
}
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
{
var query = context.DownstreamRequest.Query;
var serviceName = _replacer.Replace(context.DownstreamReRoute.ServiceName, context.TemplatePlaceholderNameAndValues);
var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}";
return (pathTemplate, query);
}
private static bool ServiceFabricRequest(DownstreamContext context)
{
return context.Configuration.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && context.DownstreamReRoute.UseServiceDiscovery;
}
}
}
RemoveQueryStringParametersThatHaveBeenUsedInTemplate(downstreamRequest, templatePlaceholderNameAndValues);
downstreamRequest.AbsolutePath = dsPath.Value;
}
}
Logger.LogDebug($"Downstream url is {downstreamRequest}");
await _next.Invoke(httpContext);
}
private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamRequest downstreamRequest, List<PlaceholderNameAndValue> templatePlaceholderNameAndValues)
{
foreach (var nAndV in templatePlaceholderNameAndValues)
{
var name = nAndV.Name.Replace("{", "").Replace("}", "");
if (downstreamRequest.Query.Contains(name) &&
downstreamRequest.Query.Contains(nAndV.Value))
{
var questionMarkOrAmpersand = downstreamRequest.Query.IndexOf(name, StringComparison.Ordinal);
downstreamRequest.Query = downstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1);
var rgx = new Regex($@"\b{name}={nAndV.Value}\b");
downstreamRequest.Query = rgx.Replace(downstreamRequest.Query, "");
if (!string.IsNullOrEmpty(downstreamRequest.Query))
{
downstreamRequest.Query = '?' + downstreamRequest.Query.Substring(1);
}
}
}
}
private string GetPath(DownstreamPath dsPath)
{
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal));
}
private string GetQueryString(DownstreamPath dsPath)
{
return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal));
}
private bool ContainsQueryString(DownstreamPath dsPath)
{
return dsPath.Value.Contains("?");
}
private (string path, string query) CreateServiceFabricUri(DownstreamRequest downstreamRequest, DownstreamReRoute downstreamReRoute, List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, Response<DownstreamPath> dsPath)
{
var query = downstreamRequest.Query;
var serviceName = _replacer.Replace(downstreamReRoute.ServiceName, templatePlaceholderNameAndValues);
var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}";
return (pathTemplate, query);
}
private static bool ServiceFabricRequest(IInternalConfiguration config, DownstreamReRoute downstreamReRoute)
{
return config.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && downstreamReRoute.UseServiceDiscovery;
}
}
}

View File

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.DownstreamUrlCreator.Middleware
{
public static class DownstreamUrlCreatorMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseDownstreamUrlCreatorMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
}
}
namespace Ocelot.DownstreamUrlCreator.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class DownstreamUrlCreatorMiddlewareExtensions
{
public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
}
}
}

View File

@ -1,15 +1,19 @@
using System.Net;
namespace Ocelot.Errors
{
public abstract class Error
{
protected Error(string message, OcelotErrorCode code)
{
protected Error(string message, OcelotErrorCode code, int httpStatusCode)
{
HttpStatusCode = httpStatusCode;
Message = message;
Code = code;
}
public string Message { get; private set; }
public OcelotErrorCode Code { get; private set; }
public OcelotErrorCode Code { get; private set; }
public int HttpStatusCode { get; private set; }
public override string ToString()
{

View File

@ -1,102 +1,89 @@
namespace Ocelot.Errors.Middleware
{
using Configuration;
using Ocelot.Configuration.Repository;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Configuration;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
/// <summary>
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500.
/// </summary>
public class ExceptionHandlerMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IInternalConfigurationRepository _configRepo;
private readonly RequestDelegate _next;
private readonly IRequestScopedDataRepository _repo;
public ExceptionHandlerMiddleware(OcelotRequestDelegate next,
public ExceptionHandlerMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IInternalConfigurationRepository configRepo,
IRequestScopedDataRepository repo)
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
{
_configRepo = configRepo;
_repo = repo;
_next = next;
_repo = repo;
}
public async Task Invoke(DownstreamContext context)
public async Task Invoke(HttpContext httpContext)
{
try
{
context.HttpContext.RequestAborted.ThrowIfCancellationRequested();
httpContext.RequestAborted.ThrowIfCancellationRequested();
//try and get the global request id and set it for logs...
//should this basically be immutable per request...i guess it should!
//first thing is get config
var configuration = _configRepo.Get();
var internalConfiguration = httpContext.Items.IInternalConfiguration();
if (configuration.IsError)
{
throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
}
TrySetGlobalRequestId(context, configuration.Data);
context.Configuration = configuration.Data;
TrySetGlobalRequestId(httpContext, internalConfiguration);
Logger.LogDebug("ocelot pipeline started");
await _next.Invoke(context);
await _next.Invoke(httpContext);
}
catch (OperationCanceledException) when (context.HttpContext.RequestAborted.IsCancellationRequested)
catch (OperationCanceledException) when (httpContext.RequestAborted.IsCancellationRequested)
{
Logger.LogDebug("operation canceled");
if (!context.HttpContext.Response.HasStarted)
if (!httpContext.Response.HasStarted)
{
context.HttpContext.Response.StatusCode = 499;
httpContext.Response.StatusCode = 499;
}
}
catch (Exception e)
{
Logger.LogDebug("error calling middleware");
var message = CreateMessage(context, e);
var message = CreateMessage(httpContext, e);
Logger.LogError(message, e);
SetInternalServerErrorOnResponse(context);
SetInternalServerErrorOnResponse(httpContext);
}
Logger.LogDebug("ocelot pipeline finished");
}
private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration)
private void TrySetGlobalRequestId(HttpContext httpContext, IInternalConfiguration configuration)
{
var key = configuration.RequestId;
if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
if (!string.IsNullOrEmpty(key) && httpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
{
context.HttpContext.TraceIdentifier = upstreamRequestIds.First();
httpContext.TraceIdentifier = upstreamRequestIds.First();
}
_repo.Add("RequestId", context.HttpContext.TraceIdentifier);
_repo.Add("RequestId", httpContext.TraceIdentifier);
}
private void SetInternalServerErrorOnResponse(DownstreamContext context)
private void SetInternalServerErrorOnResponse(HttpContext httpContext)
{
if (!context.HttpContext.Response.HasStarted)
if (!httpContext.Response.HasStarted)
{
context.HttpContext.Response.StatusCode = 500;
httpContext.Response.StatusCode = 500;
}
}
private string CreateMessage(DownstreamContext context, Exception e)
private string CreateMessage(HttpContext httpContext, Exception e)
{
var message =
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
@ -107,7 +94,7 @@ namespace Ocelot.Errors.Middleware
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
}
return $"{message} RequestId: {context.HttpContext.TraceIdentifier}";
return $"{message} RequestId: {httpContext.TraceIdentifier}";
}
}
}

View File

@ -1,13 +1,13 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Errors.Middleware
{
public static class ExceptionHandlerMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseExceptionHandlerMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<ExceptionHandlerMiddleware>();
}
}
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware;
namespace Ocelot.Errors.Middleware
{
public static class ExceptionHandlerMiddlewareExtensions
{
public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExceptionHandlerMiddleware>();
}
}
}

View File

@ -1,47 +1,47 @@
namespace Ocelot.Errors
{
public enum OcelotErrorCode
{
namespace Ocelot.Errors
{
public enum OcelotErrorCode
{
UnauthenticatedError = 0,
UnknownError = 1,
DownstreampathTemplateAlreadyUsedError = 2,
UnableToFindDownstreamRouteError = 3,
CannotAddDataError = 4,
CannotFindDataError = 5,
UnableToCompleteRequestError = 6,
UnableToCreateAuthenticationHandlerError = 7,
UnsupportedAuthenticationProviderError = 8,
CannotFindClaimError = 9,
ParsingConfigurationHeaderError = 10,
NoInstructionsError = 11,
InstructionNotForClaimsError = 12,
UnauthorizedError = 13,
ClaimValueNotAuthorisedError = 14,
ScopeNotAuthorisedError = 15,
UserDoesNotHaveClaimError = 16,
DownstreamPathTemplateContainsSchemeError = 17,
DownstreamPathNullOrEmptyError = 18,
DownstreamSchemeNullOrEmptyError = 19,
DownstreamHostNullOrEmptyError = 20,
ServicesAreNullError = 21,
ServicesAreEmptyError = 22,
UnableToFindServiceDiscoveryProviderError = 23,
UnableToFindLoadBalancerError = 24,
RequestTimedOutError = 25,
UnableToFindQoSProviderError = 26,
UnmappableRequestError = 27,
RateLimitOptionsError = 28,
PathTemplateDoesntStartWithForwardSlash = 29,
FileValidationFailedError = 30,
UnableToFindDelegatingHandlerProviderError = 31,
CouldNotFindPlaceholderError = 32,
CouldNotFindAggregatorError = 33,
CannotAddPlaceholderError = 34,
CannotRemovePlaceholderError = 35,
UnknownError = 1,
DownstreampathTemplateAlreadyUsedError = 2,
UnableToFindDownstreamRouteError = 3,
CannotAddDataError = 4,
CannotFindDataError = 5,
UnableToCompleteRequestError = 6,
UnableToCreateAuthenticationHandlerError = 7,
UnsupportedAuthenticationProviderError = 8,
CannotFindClaimError = 9,
ParsingConfigurationHeaderError = 10,
NoInstructionsError = 11,
InstructionNotForClaimsError = 12,
UnauthorizedError = 13,
ClaimValueNotAuthorisedError = 14,
ScopeNotAuthorisedError = 15,
UserDoesNotHaveClaimError = 16,
DownstreamPathTemplateContainsSchemeError = 17,
DownstreamPathNullOrEmptyError = 18,
DownstreamSchemeNullOrEmptyError = 19,
DownstreamHostNullOrEmptyError = 20,
ServicesAreNullError = 21,
ServicesAreEmptyError = 22,
UnableToFindServiceDiscoveryProviderError = 23,
UnableToFindLoadBalancerError = 24,
RequestTimedOutError = 25,
UnableToFindQoSProviderError = 26,
UnmappableRequestError = 27,
RateLimitOptionsError = 28,
PathTemplateDoesntStartWithForwardSlash = 29,
FileValidationFailedError = 30,
UnableToFindDelegatingHandlerProviderError = 31,
CouldNotFindPlaceholderError = 32,
CouldNotFindAggregatorError = 33,
CannotAddPlaceholderError = 34,
CannotRemovePlaceholderError = 35,
QuotaExceededError = 36,
RequestCanceled = 37,
ConnectionToDownstreamServiceError = 38,
CouldNotFindLoadBalancerCreator = 39,
ErrorInvokingLoadBalancerCreator = 40,
}
ConnectionToDownstreamServiceError = 38,
CouldNotFindLoadBalancerCreator = 39,
ErrorInvokingLoadBalancerCreator = 40,
}
}

View File

@ -7,6 +7,8 @@ namespace Ocelot.Headers
using Ocelot.Responses;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
{
@ -17,10 +19,10 @@ namespace Ocelot.Headers
_placeholders = placeholders;
}
public Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs)
public Response Replace(HttpContext httpContext, List<HeaderFindAndReplace> fAndRs)
{
var response = context.DownstreamResponse;
var request = context.DownstreamRequest;
var response = httpContext.Items.DownstreamResponse();
var request = httpContext.Items.DownstreamRequest();
foreach (var f in fAndRs)
{

View File

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

View File

@ -1,44 +1,50 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Headers.Middleware
{
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
public class ClaimsToHeadersMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IAddHeadersToRequest _addHeadersToRequest;
public ClaimsToHeadersMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IAddHeadersToRequest addHeadersToRequest)
: base(loggerFactory.CreateLogger<ClaimsToHeadersMiddleware>())
{
_next = next;
_addHeadersToRequest = addHeadersToRequest;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (downstreamReRoute.ClaimsToHeaders.Any())
{
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers");
namespace Ocelot.Headers.Middleware
{
public class ClaimsToHeadersMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IAddHeadersToRequest _addHeadersToRequest;
public ClaimsToHeadersMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IAddHeadersToRequest addHeadersToRequest)
: base(loggerFactory.CreateLogger<ClaimsToHeadersMiddleware>())
{
_next = next;
_addHeadersToRequest = addHeadersToRequest;
}
public async Task Invoke(DownstreamContext context)
{
if (context.DownstreamReRoute.ClaimsToHeaders.Any())
{
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers");
var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest);
if (response.IsError)
{
Logger.LogWarning("Error setting headers on context, setting pipeline error");
SetPipelineError(context, response.Errors);
return;
}
Logger.LogInformation("headers have been set on context");
}
await _next.Invoke(context);
}
}
}
var downstreamRequest = httpContext.Items.DownstreamRequest();
var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(downstreamReRoute.ClaimsToHeaders, httpContext.User.Claims, downstreamRequest);
if (response.IsError)
{
Logger.LogWarning("Error setting headers on context, setting pipeline error");
httpContext.Items.UpsertErrors(response.Errors);
return;
}
Logger.LogInformation("headers have been set on context");
}
await _next.Invoke(httpContext);
}
}
}

View File

@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Headers.Middleware
{
public static class ClaimsToHeadersMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseClaimsToHeadersMiddleware(this IOcelotPipelineBuilder builder)
public static IApplicationBuilder UseClaimsToHeadersMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ClaimsToHeadersMiddleware>();
}

View File

@ -1,23 +1,26 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Threading.Tasks;
namespace Ocelot.Headers.Middleware
{
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Threading.Tasks;
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly RequestDelegate _next;
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
private readonly IHttpResponseHeaderReplacer _postReplacer;
private readonly IAddHeadersToResponse _addHeadersToResponse;
private readonly IAddHeadersToRequest _addHeadersToRequest;
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
public HttpHeadersTransformationMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IHttpContextRequestHeaderReplacer preReplacer,
IHttpResponseHeaderReplacer postReplacer,
IAddHeadersToResponse addHeadersToResponse,
IAddHeadersToRequest addHeadersToRequest)
IAddHeadersToRequest addHeadersToRequest
)
: base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>())
{
_addHeadersToResponse = addHeadersToResponse;
@ -27,27 +30,33 @@ namespace Ocelot.Headers.Middleware
_preReplacer = preReplacer;
}
public async Task Invoke(DownstreamContext context)
public async Task Invoke(HttpContext httpContext)
{
var preFAndRs = context.DownstreamReRoute.UpstreamHeadersFindAndReplace;
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
var preFAndRs = downstreamReRoute.UpstreamHeadersFindAndReplace;
//todo - this should be on httprequestmessage not httpcontext?
_preReplacer.Replace(context.HttpContext, preFAndRs);
_preReplacer.Replace(httpContext, preFAndRs);
_addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext);
_addHeadersToRequest.SetHeadersOnDownstreamRequest(downstreamReRoute.AddHeadersToUpstream, httpContext);
await _next.Invoke(context);
await _next.Invoke(httpContext);
if (context.IsError)
// todo check errors is ok
//todo put this check on the base class?
if (httpContext.Items.Errors().Count > 0)
{
return;
}
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
var postFAndRs = downstreamReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(context, postFAndRs);
_postReplacer.Replace(httpContext, postFAndRs);
_addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
var downstreamResponse = httpContext.Items.DownstreamResponse();
_addHeadersToResponse.Add(downstreamReRoute.AddHeadersToDownstream, downstreamResponse);
}
}
}

View File

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

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Infrastructure
{
public class CannotAddPlaceholderError : Error
{
public CannotAddPlaceholderError(string message)
: base(message, OcelotErrorCode.CannotAddPlaceholderError)
{
}
}
using Ocelot.Errors;
namespace Ocelot.Infrastructure
{
public class CannotAddPlaceholderError : Error
{
public CannotAddPlaceholderError(string message)
: base(message, OcelotErrorCode.CannotAddPlaceholderError, 404)
{
}
}
}

View File

@ -5,8 +5,8 @@ namespace Ocelot.Infrastructure
public class CannotRemovePlaceholderError : Error
{
public CannotRemovePlaceholderError(string message)
: base(message, OcelotErrorCode.CannotRemovePlaceholderError)
: base(message, OcelotErrorCode.CannotRemovePlaceholderError, 404)
{
}
}
}
}

View File

@ -1,12 +1,12 @@
namespace Ocelot.Infrastructure.Claims.Parser
{
using Errors;
public class CannotFindClaimError : Error
{
namespace Ocelot.Infrastructure.Claims.Parser
{
using Ocelot.Errors;
public class CannotFindClaimError : Error
{
public CannotFindClaimError(string message)
: base(message, OcelotErrorCode.CannotFindClaimError)
{
}
}
: base(message, OcelotErrorCode.CannotFindClaimError, 403)
{
}
}
}

View File

@ -5,7 +5,7 @@ namespace Ocelot.Infrastructure
public class CouldNotFindPlaceholderError : Error
{
public CouldNotFindPlaceholderError(string placeholder)
: base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError)
: base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError, 404)
{
}
}

View File

@ -1,11 +1,11 @@
using Ocelot.Errors;
namespace Ocelot.Infrastructure.RequestData
{
public class CannotAddDataError : Error
{
public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError)
{
}
}
using Ocelot.Errors;
namespace Ocelot.Infrastructure.RequestData
{
public class CannotAddDataError : Error
{
public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError, 404)
{
}
}
}

View File

@ -1,11 +1,11 @@
using Ocelot.Errors;
namespace Ocelot.Infrastructure.RequestData
{
public class CannotFindDataError : Error
{
public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError)
{
}
}
using Ocelot.Errors;
namespace Ocelot.Infrastructure.RequestData
{
public class CannotFindDataError : Error
{
public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError, 404)
{
}
}
}

View File

@ -2,11 +2,12 @@ namespace Ocelot.LoadBalancer.LoadBalancers
{
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Responses;
using Ocelot.Responses;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Values;
using Microsoft.AspNetCore.Http;
using Ocelot.Values;
public class CookieStickySessions : ILoadBalancer
{
@ -41,9 +42,9 @@ namespace Ocelot.LoadBalancer.LoadBalancers
});
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
{
var key = context.HttpContext.Request.Cookies[_key];
var key = httpContext.Request.Cookies[_key];
lock (_lock)
{
@ -61,7 +62,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
}
}
var next = await _loadBalancer.Lease(context);
var next = await _loadBalancer.Lease(httpContext);
if (next.IsError)
{

View File

@ -5,7 +5,7 @@
public class CouldNotFindLoadBalancerCreator : Error
{
public CouldNotFindLoadBalancerCreator(string message)
: base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator)
: base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator, 404)
{
}
}

View File

@ -5,7 +5,7 @@
public class ErrorInvokingLoadBalancerCreator : Error
{
public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator)
public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator, 500)
{
}
}

View File

@ -1,13 +1,13 @@
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System.Threading.Tasks;
namespace Ocelot.LoadBalancer.LoadBalancers
{
using Microsoft.AspNetCore.Http;
using Ocelot.Responses;
using Ocelot.Values;
using System.Threading.Tasks;
public interface ILoadBalancer
{
Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context);
Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext);
void Release(ServiceHostAndPort hostAndPort);
}

View File

@ -1,13 +1,14 @@
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.LoadBalancer.LoadBalancers
namespace Ocelot.LoadBalancer.LoadBalancers
{
using Microsoft.AspNetCore.Http;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class LeastConnection : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
@ -22,7 +23,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
_leases = new List<Lease>();
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
{
var services = await _services.Invoke();

View File

@ -1,13 +1,14 @@
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.LoadBalancer.LoadBalancers
namespace Ocelot.LoadBalancer.LoadBalancers
{
using Microsoft.AspNetCore.Http;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class NoLoadBalancer : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
@ -17,7 +18,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
{
var services = await _services();

View File

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
public class RoundRobin : ILoadBalancer
{
@ -19,7 +20,7 @@
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
{
var services = await _services();
lock (_lock)

View File

@ -5,8 +5,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
public class ServicesAreEmptyError : Error
{
public ServicesAreEmptyError(string message)
: base(message, OcelotErrorCode.ServicesAreEmptyError)
: base(message, OcelotErrorCode.ServicesAreEmptyError, 404)
{
}
}
}
}

View File

@ -5,8 +5,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
public class ServicesAreNullError : Error
{
public ServicesAreNullError(string message)
: base(message, OcelotErrorCode.ServicesAreNullError)
: base(message, OcelotErrorCode.ServicesAreNullError, 404)
{
}
}
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class UnableToFindLoadBalancerError : Errors.Error
{
using Ocelot.Errors;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class UnableToFindLoadBalancerError : Errors.Error
{
public UnableToFindLoadBalancerError(string message)
: base(message, OcelotErrorCode.UnableToFindLoadBalancerError)
{
}
}
: base(message, OcelotErrorCode.UnableToFindLoadBalancerError, 404)
{
}
}
}

View File

@ -1,68 +1,78 @@
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.Threading.Tasks;
namespace Ocelot.LoadBalancer.Middleware
{
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.Threading.Tasks;
public class LoadBalancingMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly ILoadBalancerHouse _loadBalancerHouse;
public LoadBalancingMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
ILoadBalancerHouse loadBalancerHouse)
: base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
{
_next = next;
_loadBalancerHouse = loadBalancerHouse;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
namespace Ocelot.LoadBalancer.Middleware
{
public class LoadBalancingMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly ILoadBalancerHouse _loadBalancerHouse;
public LoadBalancingMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
ILoadBalancerHouse loadBalancerHouse)
: base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
{
_next = next;
_loadBalancerHouse = loadBalancerHouse;
}
public async Task Invoke(DownstreamContext context)
{
var loadBalancer = _loadBalancerHouse.Get(context.DownstreamReRoute, context.Configuration.ServiceProviderConfiguration);
if (loadBalancer.IsError)
{
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
SetPipelineError(context, loadBalancer.Errors);
return;
var internalConfiguration = httpContext.Items.IInternalConfiguration();
var loadBalancer = _loadBalancerHouse.Get(downstreamReRoute, internalConfiguration.ServiceProviderConfiguration);
if (loadBalancer.IsError)
{
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
httpContext.Items.UpsertErrors(loadBalancer.Errors);
return;
}
var hostAndPort = await loadBalancer.Data.Lease(httpContext);
if (hostAndPort.IsError)
{
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
httpContext.Items.UpsertErrors(hostAndPort.Errors);
return;
}
var hostAndPort = await loadBalancer.Data.Lease(context);
if (hostAndPort.IsError)
{
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
SetPipelineError(context, hostAndPort.Errors);
return;
}
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
if (hostAndPort.Data.DownstreamPort > 0)
{
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
}
if (!string.IsNullOrEmpty(hostAndPort.Data.Scheme))
{
context.DownstreamRequest.Scheme = hostAndPort.Data.Scheme;
}
try
{
await _next.Invoke(context);
}
catch (Exception)
{
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
throw;
}
finally
{
loadBalancer.Data.Release(hostAndPort.Data);
}
}
}
}
var downstreamRequest = httpContext.Items.DownstreamRequest();
//todo check downstreamRequest is ok
downstreamRequest.Host = hostAndPort.Data.DownstreamHost;
if (hostAndPort.Data.DownstreamPort > 0)
{
downstreamRequest.Port = hostAndPort.Data.DownstreamPort;
}
if (!string.IsNullOrEmpty(hostAndPort.Data.Scheme))
{
downstreamRequest.Scheme = hostAndPort.Data.Scheme;
}
try
{
await _next.Invoke(httpContext);
}
catch (Exception)
{
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
throw;
}
finally
{
loadBalancer.Data.Release(hostAndPort.Data);
}
}
}
}

View File

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.LoadBalancer.Middleware
namespace Ocelot.LoadBalancer.Middleware
{
public static class LoadBalancingMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseLoadBalancingMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<LoadBalancingMiddleware>();
}
}
using Microsoft.AspNetCore.Builder;
public static class LoadBalancingMiddlewareExtensions
{
public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoadBalancingMiddleware>();
}
}
}

View File

@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DiagnosticAdapter;
using Ocelot.Middleware;
using System;
namespace Ocelot.Logging
namespace Ocelot.Logging
{
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DiagnosticAdapter;
using System;
public class OcelotDiagnosticListener
{
private readonly IOcelotLogger _logger;
@ -17,27 +16,6 @@ namespace Ocelot.Logging
_tracer = serviceProvider.GetService<ITracer>();
}
[DiagnosticName("Ocelot.MiddlewareException")]
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
{
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};");
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
}
[DiagnosticName("Ocelot.MiddlewareStarted")]
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
{
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
}
[DiagnosticName("Ocelot.MiddlewareFinished")]
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
{
_logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
}
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
{

View File

@ -1,23 +1,23 @@
using Microsoft.Extensions.Configuration;
namespace Ocelot.Middleware
{
public class BaseUrlFinder : IBaseUrlFinder
{
private readonly IConfiguration _config;
public BaseUrlFinder(IConfiguration config)
{
_config = config;
}
public string Find()
{
//tries to get base url out of file...
var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", "");
//falls back to memory config then finally default..
return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl;
}
}
using Microsoft.Extensions.Configuration;
namespace Ocelot.Middleware
{
public class BaseUrlFinder : IBaseUrlFinder
{
private readonly IConfiguration _config;
public BaseUrlFinder(IConfiguration config)
{
_config = config;
}
public string Find()
{
//tries to get base url out of file...
var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", "");
//falls back to memory config then finally default..
return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl;
}
}
}

View File

@ -0,0 +1,37 @@
namespace Ocelot.Middleware
{
using System.Threading.Tasks;
using Ocelot.Errors.Middleware;
using Ocelot.Configuration.Repository;
using Ocelot.Logging;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
public class ConfigurationMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IInternalConfigurationRepository _configRepo;
public ConfigurationMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IInternalConfigurationRepository configRepo)
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
{
_next = next;
_configRepo = configRepo;
}
public async Task Invoke(HttpContext httpContext)
{
//todo check the config is actually ok?
var config = _configRepo.Get();
if(config.IsError)
{
throw new System.Exception("OOOOPS this should not happen raise an issue in GitHub");
}
httpContext.Items.SetIInternalConfiguration(config.Data);
await _next.Invoke(httpContext);
}
}
}

View File

@ -1,34 +0,0 @@
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors;
using Ocelot.Request.Middleware;
using System.Collections.Generic;
namespace Ocelot.Middleware
{
public class DownstreamContext
{
public DownstreamContext(HttpContext httpContext)
{
HttpContext = httpContext;
Errors = new List<Error>();
}
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; set; }
public HttpContext HttpContext { get; }
public DownstreamReRoute DownstreamReRoute { get; set; }
public DownstreamRequest DownstreamRequest { get; set; }
public DownstreamResponse DownstreamResponse { get; set; }
public List<Error> Errors { get; }
public IInternalConfiguration Configuration { get; set; }
public bool IsError => Errors.Count > 0;
}
}

View File

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

View File

@ -0,0 +1,117 @@
namespace Ocelot.Middleware
{
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors;
using Ocelot.Request.Middleware;
using System.Collections.Generic;
public static class HttpItemsExtensions
{
public static void UpsertDownstreamRequest(this IDictionary<object, object> input, DownstreamRequest downstreamRequest)
{
input.Upsert("DownstreamRequest", downstreamRequest);
}
public static void UpsertDownstreamResponse(this IDictionary<object, object> input, DownstreamResponse downstreamResponse)
{
input.Upsert("DownstreamResponse", downstreamResponse);
}
public static void UpsertDownstreamReRoute(this IDictionary<object, object> input, DownstreamReRoute downstreamReRoute)
{
input.Upsert("DownstreamReRoute", downstreamReRoute);
}
public static void UpsertTemplatePlaceholderNameAndValues(this IDictionary<object, object> input, List<PlaceholderNameAndValue> tPNV)
{
input.Upsert("TemplatePlaceholderNameAndValues", tPNV);
}
public static void UpsertDownstreamRoute(this IDictionary<object, object> input, DownstreamRoute downstreamRoute)
{
input.Upsert("DownstreamRoute", downstreamRoute);
}
public static void UpsertErrors(this IDictionary<object, object> input, List<Error> errors)
{
input.Upsert("Errors", errors);
}
public static void SetError(this IDictionary<object, object> input, Error error)
{
var errors = new List<Error>() { error };
input.Upsert("Errors", errors);
}
public static void SetIInternalConfiguration(this IDictionary<object, object> input, IInternalConfiguration config)
{
input.Upsert("IInternalConfiguration", config);
}
public static IInternalConfiguration IInternalConfiguration(this IDictionary<object, object> input)
{
return input.Get<IInternalConfiguration>("IInternalConfiguration");
}
public static List<Error> Errors(this IDictionary<object, object> input)
{
var errors = input.Get<List<Error>>("Errors");
return errors == null ? new List<Error>() : errors;
}
public static DownstreamRoute DownstreamRoute(this IDictionary<object, object> input)
{
return input.Get<DownstreamRoute>("DownstreamRoute");
}
public static List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues(this IDictionary<object, object> input)
{
return input.Get<List<PlaceholderNameAndValue>>("TemplatePlaceholderNameAndValues");
}
public static DownstreamRequest DownstreamRequest(this IDictionary<object, object> input)
{
return input.Get<DownstreamRequest>("DownstreamRequest");
}
public static DownstreamResponse DownstreamResponse(this IDictionary<object, object> input)
{
return input.Get<DownstreamResponse>("DownstreamResponse");
}
public static DownstreamReRoute DownstreamReRoute(this IDictionary<object, object> input)
{
return input.Get<DownstreamReRoute>("DownstreamReRoute");
}
private static T Get<T>(this IDictionary<object, object> input, string key)
{
if (input.TryGetValue(key, out var value))
{
return (T)value;
}
return default(T);
}
private static void Upsert<T>(this IDictionary<object, object> input, string key, T value)
{
if (input.DoesntExist(key))
{
input.Add(key, value);
}
else
{
input.Remove(key);
input.Add(key, value);
}
}
private static bool DoesntExist(this IDictionary<object, object> input, string key)
{
return !input.ContainsKey(key);
}
}
}

View File

@ -1,10 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
{
public interface IDefinedAggregator
{
Task<DownstreamResponse> Aggregate(List<DownstreamContext> responses);
}
}

View File

@ -1,10 +0,0 @@
using Ocelot.Configuration;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
{
public interface IMultiplexer
{
Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next);
}
}

View File

@ -1,11 +0,0 @@
using Ocelot.Configuration;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
{
public interface IResponseAggregator
{
Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamResponses);
}
}

View File

@ -1,152 +0,0 @@
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
{
public class Multiplexer : IMultiplexer
{
private readonly IResponseAggregatorFactory _factory;
public Multiplexer(IResponseAggregatorFactory factory)
{
_factory = factory;
}
public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next)
{
var reRouteKeysConfigs = reRoute.DownstreamReRouteConfig;
if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any())
{
var tasks = new Task<DownstreamContext>[reRoute.DownstreamReRoute.Count];
for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++)
{
var downstreamContext = new DownstreamContext(context.HttpContext)
{
TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues,
Configuration = context.Configuration,
DownstreamReRoute = reRoute.DownstreamReRoute[i],
};
tasks[i] = Fire(downstreamContext, next);
}
await Task.WhenAll(tasks);
var contexts = new List<DownstreamContext>();
foreach (var task in tasks)
{
var finished = await task;
contexts.Add(finished);
}
await Map(reRoute, context, contexts);
}
else
{
var downstreamContextMain = new DownstreamContext(context.HttpContext)
{
TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues,
Configuration = context.Configuration,
DownstreamReRoute = reRoute.DownstreamReRoute[0],
};
var mainResponse = await Fire(downstreamContextMain, next);
if (reRoute.DownstreamReRoute.Count == 1)
{
MapNotAggregate(context, new List<DownstreamContext>() { mainResponse });
return;
}
var tasks = new List<Task<DownstreamContext>>();
if (mainResponse.DownstreamResponse == null)
{
return;
}
var content = await mainResponse.DownstreamResponse.Content.ReadAsStringAsync();
var jObject = Newtonsoft.Json.Linq.JToken.Parse(content);
for (var i = 1; i < reRoute.DownstreamReRoute.Count; i++)
{
var templatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues;
var downstreamReRoute = reRoute.DownstreamReRoute[i];
var matchAdvancedAgg = reRouteKeysConfigs.FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key);
if (matchAdvancedAgg != null)
{
var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList();
foreach (var value in values)
{
var downstreamContext = new DownstreamContext(context.HttpContext)
{
TemplatePlaceholderNameAndValues = new List<PlaceholderNameAndValue>(templatePlaceholderNameAndValues),
Configuration = context.Configuration,
DownstreamReRoute = downstreamReRoute,
};
downstreamContext.TemplatePlaceholderNameAndValues.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString()));
tasks.Add(Fire(downstreamContext, next));
}
}
else
{
var downstreamContext = new DownstreamContext(context.HttpContext)
{
TemplatePlaceholderNameAndValues = new List<PlaceholderNameAndValue>(templatePlaceholderNameAndValues),
Configuration = context.Configuration,
DownstreamReRoute = downstreamReRoute,
};
tasks.Add(Fire(downstreamContext, next));
}
}
await Task.WhenAll(tasks);
var contexts = new List<DownstreamContext>() { mainResponse };
foreach (var task in tasks)
{
var finished = await task;
contexts.Add(finished);
}
await Map(reRoute, context, contexts);
}
}
private async Task Map(ReRoute reRoute, DownstreamContext context, List<DownstreamContext> contexts)
{
if (reRoute.DownstreamReRoute.Count > 1)
{
var aggregator = _factory.Get(reRoute);
await aggregator.Aggregate(reRoute, context, contexts);
}
else
{
MapNotAggregate(context, contexts);
}
}
private void MapNotAggregate(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts)
{
//assume at least one..if this errors then it will be caught by global exception handler
var finished = downstreamContexts.First();
originalContext.Errors.AddRange(finished.Errors);
originalContext.DownstreamRequest = finished.DownstreamRequest;
originalContext.DownstreamResponse = finished.DownstreamResponse;
}
private async Task<DownstreamContext> Fire(DownstreamContext context, OcelotRequestDelegate next)
{
await next.Invoke(context);
return context;
}
}
}

View File

@ -1,33 +1,16 @@
using Ocelot.Errors;
using Ocelot.Logging;
using System.Collections.Generic;
namespace Ocelot.Middleware
{
public abstract class OcelotMiddleware
namespace Ocelot.Middleware
{
using Ocelot.Logging;
public abstract class OcelotMiddleware
{
protected OcelotMiddleware(IOcelotLogger logger)
{
Logger = logger;
MiddlewareName = this.GetType().Name;
}
public IOcelotLogger Logger { get; }
public string MiddlewareName { get; }
public void SetPipelineError(DownstreamContext context, List<Error> errors)
{
foreach (var error in errors)
{
SetPipelineError(context, error);
}
}
public void SetPipelineError(DownstreamContext context, Error error)
{
Logger.LogWarning(error.Message);
context.Errors.Add(error);
}
}
protected OcelotMiddleware(IOcelotLogger logger)
{
Logger = logger;
MiddlewareName = GetType().Name;
}
public IOcelotLogger Logger { get; }
public string MiddlewareName { get; }
}
}

View File

@ -1,6 +1,6 @@
namespace Ocelot.Middleware
{
using DependencyInjection;
using Ocelot.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
@ -11,7 +11,6 @@
using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter;
using Ocelot.Logging;
using Ocelot.Middleware.Pipeline;
using Ocelot.Responses;
using System;
using System.Diagnostics;
@ -42,37 +41,25 @@
return CreateOcelotPipeline(builder, pipelineConfiguration);
}
public static Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder app, Action<IOcelotPipelineBuilder, OcelotPipelineConfiguration> builderAction)
public static Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder app, Action<IApplicationBuilder, OcelotPipelineConfiguration> builderAction)
=> UseOcelot(app, builderAction, new OcelotPipelineConfiguration());
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder app, Action<IOcelotPipelineBuilder, OcelotPipelineConfiguration> builderAction, OcelotPipelineConfiguration configuration)
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder app, Action<IApplicationBuilder, OcelotPipelineConfiguration> builderAction, OcelotPipelineConfiguration configuration)
{
await CreateConfiguration(app); // initConfiguration
await CreateConfiguration(app);
ConfigureDiagnosticListener(app);
var ocelotPipelineBuilder = new OcelotPipelineBuilder(app.ApplicationServices);
builderAction?.Invoke(ocelotPipelineBuilder, configuration ?? new OcelotPipelineConfiguration());
builderAction?.Invoke(app, configuration ?? new OcelotPipelineConfiguration());
var ocelotDelegate = ocelotPipelineBuilder.Build();
app.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
app.Use(async (context, task) =>
{
var downstreamContext = new DownstreamContext(context);
await ocelotDelegate.Invoke(downstreamContext);
});
return app;
}
private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
var firstDelegate = pipelineBuilder.Build();
builder.BuildOcelotPipeline(pipelineConfiguration);
/*
inject first delegate into first piece of asp.net middleware..maybe not like this
@ -82,12 +69,6 @@
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
builder.Use(async (context, task) =>
{
var downstreamContext = new DownstreamContext(context);
await firstDelegate.Invoke(downstreamContext);
});
return builder;
}

View File

@ -1,9 +1,10 @@
namespace Ocelot.Middleware
{
using Ocelot.Middleware.Pipeline;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
public class OcelotPipelineConfiguration
{
@ -12,38 +13,64 @@
/// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called
/// in the Ocelot pipeline before we go to the global error handler.
/// </summary>
public Func<DownstreamContext, Func<Task>, Task> PreErrorResponderMiddleware { get; set; }
/// <value>
/// <placeholder>This is called after the global error handling middleware so any code before calling next.invoke
/// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called
/// in the Ocelot pipeline before we go to the global error handler.</placeholder>
/// </value>
public Func<HttpContext, Func<Task>, Task> PreErrorResponderMiddleware { get; set; }
/// <summary>
/// This is to allow the user to run any extra authentication before the Ocelot authentication
/// kicks in
/// </summary>
public Func<DownstreamContext, Func<Task>, Task> PreAuthenticationMiddleware { get; set; }
/// <value>
/// <placeholder>This is to allow the user to run any extra authentication before the Ocelot authentication
/// kicks in</placeholder>
/// </value>
public Func<HttpContext, Func<Task>, Task> PreAuthenticationMiddleware { get; set; }
/// <summary>
/// This allows the user to completely override the ocelot authentication middleware
/// </summary>
public Func<DownstreamContext, Func<Task>, Task> AuthenticationMiddleware { get; set; }
/// <value>
/// <placeholder>This allows the user to completely override the ocelot authentication middleware</placeholder>
/// </value>
public Func<HttpContext, Func<Task>, Task> AuthenticationMiddleware { get; set; }
/// <summary>
/// This is to allow the user to run any extra authorisation before the Ocelot authentication
/// kicks in
/// </summary>
public Func<DownstreamContext, Func<Task>, Task> PreAuthorisationMiddleware { get; set; }
/// <value>
/// <placeholder>This is to allow the user to run any extra authorisation before the Ocelot authentication
/// kicks in</placeholder>
/// </value>
public Func<HttpContext, Func<Task>, Task> PreAuthorisationMiddleware { get; set; }
/// <summary>
/// This allows the user to completely override the ocelot authorisation middleware
/// </summary>
public Func<DownstreamContext, Func<Task>, Task> AuthorisationMiddleware { get; set; }
/// <value>
/// <placeholder>This allows the user to completely override the ocelot authorisation middleware</placeholder>
/// </value>
public Func<HttpContext, Func<Task>, Task> AuthorisationMiddleware { get; set; }
/// <summary>
/// This allows the user to implement there own query string manipulation logic
/// </summary>
public Func<DownstreamContext, Func<Task>, Task> PreQueryStringBuilderMiddleware { get; set; }
/// <value>
/// <placeholder>This allows the user to implement there own query string manipulation logic</placeholder>
/// </value>
public Func<HttpContext, Func<Task>, Task> PreQueryStringBuilderMiddleware { get; set; }
/// <summary>
/// This is an extension that will branch to different pipes
/// </summary>
public List<Func<IOcelotPipelineBuilder, Func<DownstreamContext, bool>>> MapWhenOcelotPipeline { get; } = new List<Func<IOcelotPipelineBuilder, Func<DownstreamContext, bool>>>();
/// <value>
/// <placeholder>This is an extension that will branch to different pipes</placeholder>
/// </value>
// todo fix this data structure
public Dictionary<Func<HttpContext, bool>, Action<IApplicationBuilder>> MapWhenOcelotPipeline { get; } = new Dictionary<Func<HttpContext, bool>, Action<IApplicationBuilder>>();
}
}

View File

@ -1,101 +1,112 @@
using Ocelot.Authentication.Middleware;
using Ocelot.Authorisation.Middleware;
using Ocelot.Cache.Middleware;
using Ocelot.Claims.Middleware;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.DownstreamUrlCreator.Middleware;
using Ocelot.Errors.Middleware;
using Ocelot.Headers.Middleware;
using Ocelot.LoadBalancer.Middleware;
using Ocelot.PathManipulation.Middleware;
using Ocelot.QueryStrings.Middleware;
using Ocelot.RateLimit.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Requester.Middleware;
using Ocelot.RequestId.Middleware;
using Ocelot.Responder.Middleware;
using Ocelot.Security.Middleware;
using Ocelot.WebSockets.Middleware;
using System;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Pipeline
namespace Ocelot.Middleware
{
using Ocelot.QueryStrings.Middleware;
using Ocelot.RateLimit.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Requester.Middleware;
using Ocelot.RequestId.Middleware;
using Ocelot.Responder.Middleware;
using Ocelot.Security.Middleware;
using Ocelot.Authentication.Middleware;
using Ocelot.Authorisation.Middleware;
using Ocelot.Cache.Middleware;
using Ocelot.Claims.Middleware;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.DownstreamUrlCreator.Middleware;
using Ocelot.Errors.Middleware;
using Ocelot.Headers.Middleware;
using Ocelot.LoadBalancer.Middleware;
using System;
using System.Threading.Tasks;
using Ocelot.DownstreamPathManipulation.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Ocelot.WebSockets.Middleware;
using Ocelot.Multiplexer;
public static class OcelotPipelineExtensions
{
public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder,
public static RequestDelegate BuildOcelotPipeline(this IApplicationBuilder app,
OcelotPipelineConfiguration pipelineConfiguration)
{
// this sets up the downstream context and gets the config
app.UseDownstreamContextMiddleware();
// This is registered to catch any global exceptions that are not handled
// It also sets the Request Id if anything is set globally
builder.UseExceptionHandlerMiddleware();
app.UseExceptionHandlerMiddleware();
// If the request is for websockets upgrade we fork into a different pipeline
builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
app =>
app.MapWhen(httpContext => httpContext.WebSockets.IsWebSocketRequest,
wenSocketsApp =>
{
app.UseDownstreamRouteFinderMiddleware();
app.UseDownstreamRequestInitialiser();
app.UseLoadBalancingMiddleware();
app.UseDownstreamUrlCreatorMiddleware();
app.UseWebSocketsProxyMiddleware();
wenSocketsApp.UseDownstreamRouteFinderMiddleware();
wenSocketsApp.UseMultiplexingMiddleware();
wenSocketsApp.UseDownstreamRequestInitialiser();
wenSocketsApp.UseLoadBalancingMiddleware();
wenSocketsApp.UseDownstreamUrlCreatorMiddleware();
wenSocketsApp.UseWebSocketsProxyMiddleware();
});
// Allow the user to respond with absolutely anything they want.
builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);
app.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);
// This is registered first so it can catch any errors and issue an appropriate response
builder.UseResponderMiddleware();
app.UseResponderMiddleware();
// Then we get the downstream route information
builder.UseDownstreamRouteFinderMiddleware();
app.UseDownstreamRouteFinderMiddleware();
// Multiplex the request if required
app.UseMultiplexingMiddleware();
// This security module, IP whitelist blacklist, extended security mechanism
builder.UseSecurityMiddleware();
app.UseSecurityMiddleware();
//Expand other branch pipes
if (pipelineConfiguration.MapWhenOcelotPipeline != null)
{
foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
{
builder.MapWhen(pipeline);
// todo why is this asking for an app app?
app.MapWhen(pipeline.Key, pipeline.Value);
}
}
// Now we have the ds route we can transform headers and stuff?
builder.UseHttpHeadersTransformationMiddleware();
app.UseHttpHeadersTransformationMiddleware();
// Initialises downstream request
builder.UseDownstreamRequestInitialiser();
app.UseDownstreamRequestInitialiser();
// We check whether the request is ratelimit, and if there is no continue processing
builder.UseRateLimiting();
app.UseRateLimiting();
// This adds or updates the request id (initally we try and set this based on global config in the error handling middleware)
// If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten
// This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware.
builder.UseRequestIdMiddleware();
app.UseRequestIdMiddleware();
// Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);
app.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);
// Now we know where the client is going to go we can authenticate them.
// We allow the ocelot middleware to be overriden by whatever the
// user wants
if (pipelineConfiguration.AuthenticationMiddleware == null)
{
builder.UseAuthenticationMiddleware();
app.UseAuthenticationMiddleware();
}
else
{
builder.Use(pipelineConfiguration.AuthenticationMiddleware);
app.Use(pipelineConfiguration.AuthenticationMiddleware);
}
// The next thing we do is look at any claims transforms in case this is important for authorisation
builder.UseClaimsToClaimsMiddleware();
app.UseClaimsToClaimsMiddleware();
// Allow pre authorisation logic. The idea being people might want to run something custom before what is built in.
builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);
app.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);
// Now we have authenticated and done any claims transformation we
// can authorise the request
@ -103,42 +114,42 @@ namespace Ocelot.Middleware.Pipeline
// user wants
if (pipelineConfiguration.AuthorisationMiddleware == null)
{
builder.UseAuthorisationMiddleware();
app.UseAuthorisationMiddleware();
}
else
{
builder.Use(pipelineConfiguration.AuthorisationMiddleware);
app.Use(pipelineConfiguration.AuthorisationMiddleware);
}
// Now we can run the claims to headers transformation middleware
builder.UseClaimsToHeadersMiddleware();
app.UseClaimsToHeadersMiddleware();
// Allow the user to implement their own query string manipulation logic
builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);
app.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);
// Now we can run any claims to query string transformation middleware
builder.UseClaimsToQueryStringMiddleware();
app.UseClaimsToQueryStringMiddleware();
builder.UseClaimsToDownstreamPathMiddleware();
app.UseClaimsToDownstreamPathMiddleware();
// Get the load balancer for this request
builder.UseLoadBalancingMiddleware();
app.UseLoadBalancingMiddleware();
// This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
builder.UseDownstreamUrlCreatorMiddleware();
app.UseDownstreamUrlCreatorMiddleware();
// Not sure if this is the best place for this but we use the downstream url
// as the basis for our cache key.
builder.UseOutputCacheMiddleware();
app.UseOutputCacheMiddleware();
//We fire off the request and set the response on the scoped data repo
builder.UseHttpRequesterMiddleware();
app.UseHttpRequesterMiddleware();
return builder.Build();
return app.Build();
}
private static void UseIfNotNull(this IOcelotPipelineBuilder builder,
Func<DownstreamContext, Func<Task>, Task> middleware)
private static void UseIfNotNull(this IApplicationBuilder builder,
Func<HttpContext, Func<Task>, Task> middleware)
{
if (middleware != null)
{

View File

@ -1,6 +0,0 @@
using System.Threading.Tasks;
namespace Ocelot.Middleware
{
public delegate Task OcelotRequestDelegate(DownstreamContext downstreamContext);
}

View File

@ -1,19 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages
using System;
namespace Ocelot.Middleware.Pipeline
{
public interface IOcelotPipelineBuilder
{
IServiceProvider ApplicationServices { get; }
IOcelotPipelineBuilder Use(Func<OcelotRequestDelegate, OcelotRequestDelegate> middleware);
OcelotRequestDelegate Build();
IOcelotPipelineBuilder New();
}
}

View File

@ -1,14 +0,0 @@
Copyright (c) .NET Foundation and Contributors
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

View File

@ -1,44 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Pipeline
{
public class MapWhenMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly MapWhenOptions _options;
public MapWhenMiddleware(OcelotRequestDelegate next, MapWhenOptions options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_next = next;
_options = options;
}
public async Task Invoke(DownstreamContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_options.Predicate(context))
{
await _options.Branch(context);
}
else
{
await _next(context);
}
}
}
}

View File

@ -1,28 +0,0 @@
using System;
namespace Ocelot.Middleware.Pipeline
{
public class MapWhenOptions
{
private Func<DownstreamContext, bool> _predicate;
public Func<DownstreamContext, bool> Predicate
{
get
{
return _predicate;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_predicate = value;
}
}
public OcelotRequestDelegate Branch { get; set; }
}
}

View File

@ -1,57 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Pipeline
{
public class OcelotPipelineBuilder : IOcelotPipelineBuilder
{
private readonly IList<Func<OcelotRequestDelegate, OcelotRequestDelegate>> _middlewares;
public OcelotPipelineBuilder(IServiceProvider provider)
{
ApplicationServices = provider;
_middlewares = new List<Func<OcelotRequestDelegate, OcelotRequestDelegate>>();
}
public OcelotPipelineBuilder(IOcelotPipelineBuilder builder)
{
ApplicationServices = builder.ApplicationServices;
_middlewares = new List<Func<OcelotRequestDelegate, OcelotRequestDelegate>>();
}
public IServiceProvider ApplicationServices { get; }
public IOcelotPipelineBuilder Use(Func<OcelotRequestDelegate, OcelotRequestDelegate> middleware)
{
_middlewares.Add(middleware);
return this;
}
public OcelotRequestDelegate Build()
{
OcelotRequestDelegate app = context =>
{
context.HttpContext.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _middlewares.Reverse())
{
app = component(app);
}
return app;
}
public IOcelotPipelineBuilder New()
{
return new OcelotPipelineBuilder(this);
}
}
}

View File

@ -1,238 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Pipeline
{
using Predicate = Func<DownstreamContext, bool>;
public static class OcelotPipelineBuilderExtensions
{
internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";
private static readonly MethodInfo GetServiceInfo = typeof(OcelotPipelineBuilderExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
public static IOcelotPipelineBuilder UseMiddleware<TMiddleware>(this IOcelotPipelineBuilder app, params object[] args)
{
return app.UseMiddleware(typeof(TMiddleware), args);
}
public static IOcelotPipelineBuilder Use(this IOcelotPipelineBuilder app, Func<DownstreamContext, Func<Task>, Task> middleware)
{
return app.Use(next =>
{
return context =>
{
Func<Task> simpleNext = () => next(context);
return middleware(context, simpleNext);
};
});
}
public static IOcelotPipelineBuilder UseMiddleware(this IOcelotPipelineBuilder app, Type middleware, params object[] args)
{
return app.Use(next =>
{
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m =>
string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
).ToArray();
if (invokeMethods.Length > 1)
{
throw new InvalidOperationException();
}
if (invokeMethods.Length == 0)
{
throw new InvalidOperationException();
}
var methodinfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
{
throw new InvalidOperationException();
}
var parameters = methodinfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(DownstreamContext))
{
throw new InvalidOperationException();
}
var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
if (parameters.Length == 1)
{
var ocelotDelegate = (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance);
var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener));
var middlewareName = ocelotDelegate.Target.GetType().Name;
OcelotRequestDelegate wrapped = async context =>
{
try
{
Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context);
await ocelotDelegate(context);
}
catch (Exception ex)
{
WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context);
throw ex;
}
finally
{
Write(diagnosticListener, "Ocelot.MiddlewareFinished", middlewareName, context);
}
};
return wrapped;
}
var factory = Compile<object>(methodinfo, parameters);
return context =>
{
var serviceProvider = context.HttpContext.RequestServices ?? app.ApplicationServices;
if (serviceProvider == null)
{
throw new InvalidOperationException();
}
return factory(instance, context, serviceProvider);
};
});
}
private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context)
{
if (diagnosticListener != null)
{
diagnosticListener.Write(message, new { name = middlewareName, context = context });
}
}
private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context)
{
if (diagnosticListener != null)
{
diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception });
}
}
public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action<IOcelotPipelineBuilder> configuration)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build();
var options = new MapWhenOptions
{
Predicate = predicate,
Branch = branch,
};
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
}
public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Func<IOcelotPipelineBuilder, Predicate> pipelineBuilderFunc)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (pipelineBuilderFunc == null)
{
throw new ArgumentNullException(nameof(pipelineBuilderFunc));
}
var branchBuilder = app.New();
var predicate = pipelineBuilderFunc.Invoke(branchBuilder);
var branch = branchBuilder.Build();
var options = new MapWhenOptions
{
Predicate = predicate,
Branch = branch
};
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
}
private static Func<T, DownstreamContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
{
var middleware = typeof(T);
var httpContextArg = Expression.Parameter(typeof(DownstreamContext), "downstreamContext");
var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
var instanceArg = Expression.Parameter(middleware, "middleware");
var methodArguments = new Expression[parameters.Length];
methodArguments[0] = httpContextArg;
for (int i = 1; i < parameters.Length; i++)
{
var parameterType = parameters[i].ParameterType;
if (parameterType.IsByRef)
{
throw new NotSupportedException();
}
var parameterTypeExpression = new Expression[]
{
providerArg,
Expression.Constant(parameterType, typeof(Type))
};
var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
methodArguments[i] = Expression.Convert(getServiceCall, parameterType);
}
Expression middlewareInstanceArg = instanceArg;
if (methodinfo.DeclaringType != typeof(T))
{
middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType);
}
var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments);
var lambda = Expression.Lambda<Func<T, DownstreamContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
return lambda.Compile();
}
private static object GetService(IServiceProvider sp, Type type)
{
var service = sp.GetService(type);
if (service == null)
{
throw new InvalidOperationException();
}
return service;
}
}
}

View File

@ -1,11 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Middleware
{
public class UnauthenticatedError : Error
{
public UnauthenticatedError(string message) : base(message, OcelotErrorCode.UnauthenticatedError)
{
}
}
namespace Ocelot.Middleware
{
using Ocelot.Errors;
public class UnauthenticatedError : Error
{
public UnauthenticatedError(string message)
: base(message, OcelotErrorCode.UnauthenticatedError, 401)
{
}
}
}

View File

@ -1,11 +1,11 @@
using Ocelot.Errors;
namespace Ocelot.Middleware.Multiplexer
namespace Ocelot.Multiplexer
{
public class CouldNotFindAggregatorError : Error
{
public CouldNotFindAggregatorError(string aggregator)
: base($"Could not find Aggregator: {aggregator}", OcelotErrorCode.CouldNotFindAggregatorError)
: base($"Could not find Aggregator: {aggregator}", OcelotErrorCode.CouldNotFindAggregatorError, 404)
{
}
}

View File

@ -0,0 +1,12 @@
namespace Ocelot.Multiplexer
{
using Microsoft.AspNetCore.Http;
using Ocelot.Middleware;
using System.Collections.Generic;
using System.Threading.Tasks;
public interface IDefinedAggregator
{
Task<DownstreamResponse> Aggregate(List<HttpContext> responses);
}
}

View File

@ -1,7 +1,7 @@
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Middleware.Multiplexer
namespace Ocelot.Multiplexer
{
public interface IDefinedAggregatorProvider
{

View File

@ -0,0 +1,12 @@
namespace Ocelot.Multiplexer
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using System.Collections.Generic;
using System.Threading.Tasks;
public interface IResponseAggregator
{
Task Aggregate(ReRoute reRoute, HttpContext originalContext, List<HttpContext> downstreamResponses);
}
}

View File

@ -1,7 +1,7 @@
using Ocelot.Configuration;
namespace Ocelot.Middleware.Multiplexer
namespace Ocelot.Multiplexer
{
using Ocelot.Configuration;
public interface IResponseAggregatorFactory
{
IResponseAggregator Get(ReRoute reRoute);

View File

@ -1,6 +1,6 @@
using Ocelot.Configuration;
namespace Ocelot.Middleware.Multiplexer
namespace Ocelot.Multiplexer
{
public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory
{

View File

@ -0,0 +1,221 @@
namespace Ocelot.Multiplexer
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class MultiplexingMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IResponseAggregatorFactory _factory;
public MultiplexingMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IResponseAggregatorFactory factory
)
: base(loggerFactory.CreateLogger<MultiplexingMiddleware>())
{
_factory = factory;
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.WebSockets.IsWebSocketRequest)
{
//todo this is obviously stupid
httpContext.Items.UpsertDownstreamReRoute(httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute[0]);
await _next.Invoke(httpContext);
return;
}
var reRouteKeysConfigs = httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRouteConfig;
if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any())
{
var downstreamRoute = httpContext.Items.DownstreamRoute();
var tasks = new Task<HttpContext>[downstreamRoute.ReRoute.DownstreamReRoute.Count];
for (var i = 0; i < downstreamRoute.ReRoute.DownstreamReRoute.Count; i++)
{
var newHttpContext = Copy(httpContext);
newHttpContext.Items
.Add("RequestId", httpContext.Items["RequestId"]);
newHttpContext.Items
.SetIInternalConfiguration(httpContext.Items.IInternalConfiguration());
newHttpContext.Items
.UpsertTemplatePlaceholderNameAndValues(httpContext.Items.TemplatePlaceholderNameAndValues());
newHttpContext.Items
.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[i]);
tasks[i] = Fire(newHttpContext, _next);
}
await Task.WhenAll(tasks);
var contexts = new List<HttpContext>();
foreach (var task in tasks)
{
var finished = await task;
contexts.Add(finished);
}
await Map(httpContext, downstreamRoute.ReRoute, contexts);
}
else
{
httpContext.Items.UpsertDownstreamReRoute(httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute[0]);
var mainResponse = await Fire(httpContext, _next);
if (httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute.Count == 1)
{
MapNotAggregate(httpContext, new List<HttpContext>() { mainResponse });
return;
}
var tasks = new List<Task<HttpContext>>();
if (mainResponse.Items.DownstreamResponse() == null)
{
return;
}
var content = await mainResponse.Items.DownstreamResponse().Content.ReadAsStringAsync();
var jObject = Newtonsoft.Json.Linq.JToken.Parse(content);
for (var i = 1; i < httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute.Count; i++)
{
var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues();
var downstreamReRoute = httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute[i];
var matchAdvancedAgg = reRouteKeysConfigs
.FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key);
if (matchAdvancedAgg != null)
{
var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList();
foreach (var value in values)
{
var newHttpContext = Copy(httpContext);
var tPNV = httpContext.Items.TemplatePlaceholderNameAndValues();
tPNV.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString()));
newHttpContext.Items
.Add("RequestId", httpContext.Items["RequestId"]);
newHttpContext.Items
.SetIInternalConfiguration(httpContext.Items.IInternalConfiguration());
newHttpContext.Items
.UpsertTemplatePlaceholderNameAndValues(tPNV);
newHttpContext.Items
.UpsertDownstreamReRoute(downstreamReRoute);
tasks.Add(Fire(newHttpContext, _next));
}
}
else
{
var newHttpContext = Copy(httpContext);
newHttpContext.Items
.Add("RequestId", httpContext.Items["RequestId"]);
newHttpContext.Items
.SetIInternalConfiguration(httpContext.Items.IInternalConfiguration());
newHttpContext.Items
.UpsertTemplatePlaceholderNameAndValues(templatePlaceholderNameAndValues);
newHttpContext.Items
.UpsertDownstreamReRoute(downstreamReRoute);
tasks.Add(Fire(newHttpContext, _next));
}
}
await Task.WhenAll(tasks);
var contexts = new List<HttpContext>() { mainResponse };
foreach (var task in tasks)
{
var finished = await task;
contexts.Add(finished);
}
await Map(httpContext, httpContext.Items.DownstreamRoute().ReRoute, contexts);
}
}
private HttpContext Copy(HttpContext source)
{
var target = new DefaultHttpContext();
foreach (var header in source.Request.Headers)
{
target.Request.Headers.TryAdd(header.Key, header.Value);
}
target.Request.Body = source.Request.Body;
target.Request.ContentLength = source.Request.ContentLength;
target.Request.ContentType = source.Request.ContentType;
target.Request.Host = source.Request.Host;
target.Request.Method = source.Request.Method;
target.Request.Path = source.Request.Path;
target.Request.PathBase = source.Request.PathBase;
target.Request.Protocol = source.Request.Protocol;
target.Request.Query = source.Request.Query;
target.Request.QueryString = source.Request.QueryString;
target.Request.Scheme = source.Request.Scheme;
target.Request.IsHttps = source.Request.IsHttps;
target.Request.RouteValues = source.Request.RouteValues;
target.Connection.RemoteIpAddress = source.Connection.RemoteIpAddress;
target.RequestServices = source.RequestServices;
return target;
}
private async Task Map(HttpContext httpContext, ReRoute reRoute, List<HttpContext> contexts)
{
if (reRoute.DownstreamReRoute.Count > 1)
{
var aggregator = _factory.Get(reRoute);
await aggregator.Aggregate(reRoute, httpContext, contexts);
}
else
{
MapNotAggregate(httpContext, contexts);
}
}
private void MapNotAggregate(HttpContext httpContext, List<HttpContext> downstreamContexts)
{
//assume at least one..if this errors then it will be caught by global exception handler
var finished = downstreamContexts.First();
httpContext.Items.UpsertErrors(finished.Items.Errors());
httpContext.Items.UpsertDownstreamRequest(finished.Items.DownstreamRequest());
httpContext.Items.UpsertDownstreamResponse(finished.Items.DownstreamResponse());
}
private async Task<HttpContext> Fire(HttpContext httpContext, RequestDelegate next)
{
await next.Invoke(httpContext);
return httpContext;
}
}
}

View File

@ -0,0 +1,12 @@
namespace Ocelot.Multiplexer
{
using Microsoft.AspNetCore.Builder;
public static class MultiplexingMiddlewareExtensions
{
public static IApplicationBuilder UseMultiplexingMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MultiplexingMiddleware>();
}
}
}

View File

@ -1,29 +1,29 @@
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ocelot.Middleware.Multiplexer
{
public class ServiceLocatorDefinedAggregatorProvider : IDefinedAggregatorProvider
{
private readonly Dictionary<string, IDefinedAggregator> _aggregators;
public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services)
{
_aggregators = services.GetServices<IDefinedAggregator>().ToDictionary(x => x.GetType().Name);
}
public Response<IDefinedAggregator> Get(ReRoute reRoute)
{
if (_aggregators.ContainsKey(reRoute.Aggregator))
{
return new OkResponse<IDefinedAggregator>(_aggregators[reRoute.Aggregator]);
}
return new ErrorResponse<IDefinedAggregator>(new CouldNotFindAggregatorError(reRoute.Aggregator));
}
}
}
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ocelot.Multiplexer
{
public class ServiceLocatorDefinedAggregatorProvider : IDefinedAggregatorProvider
{
private readonly Dictionary<string, IDefinedAggregator> _aggregators;
public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services)
{
_aggregators = services.GetServices<IDefinedAggregator>().ToDictionary(x => x.GetType().Name);
}
public Response<IDefinedAggregator> Get(ReRoute reRoute)
{
if (_aggregators.ContainsKey(reRoute.Aggregator))
{
return new OkResponse<IDefinedAggregator>(_aggregators[reRoute.Aggregator]);
}
return new ErrorResponse<IDefinedAggregator>(new CouldNotFindAggregatorError(reRoute.Aggregator));
}
}
}

View File

@ -1,4 +1,6 @@
using Ocelot.Configuration;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Middleware;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -7,35 +9,35 @@ using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
namespace Ocelot.Multiplexer
{
public class SimpleJsonResponseAggregator : IResponseAggregator
{
public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamContexts)
public async Task Aggregate(ReRoute reRoute, HttpContext originalContext, List<HttpContext> downstreamContexts)
{
await MapAggregateContent(originalContext, downstreamContexts);
}
private static async Task MapAggregateContent(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts)
private static async Task MapAggregateContent(HttpContext originalContext, List<HttpContext> downstreamContexts)
{
var contentBuilder = new StringBuilder();
contentBuilder.Append("{");
var responseKeys = downstreamContexts.Select(s => s.DownstreamReRoute.Key).Distinct().ToList();
var responseKeys = downstreamContexts.Select(s => s.Items.DownstreamReRoute().Key).Distinct().ToList();
for (var k = 0; k < responseKeys.Count; k++)
{
var contexts = downstreamContexts.Where(w => w.DownstreamReRoute.Key == responseKeys[k]).ToList();
var contexts = downstreamContexts.Where(w => w.Items.DownstreamReRoute().Key == responseKeys[k]).ToList();
if (contexts.Count == 1)
{
if (contexts[0].IsError)
if (contexts[0].Items.Errors().Count > 0)
{
MapAggregateError(originalContext, contexts[0]);
return;
}
var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync();
var content = await contexts[0].Items.DownstreamResponse().Content.ReadAsStringAsync();
contentBuilder.Append($"\"{responseKeys[k]}\":{content}");
}
else
@ -45,13 +47,13 @@ namespace Ocelot.Middleware.Multiplexer
for (var i = 0; i < contexts.Count; i++)
{
if (contexts[i].IsError)
if (contexts[i].Items.Errors().Count > 0)
{
MapAggregateError(originalContext, contexts[i]);
return;
}
var content = await contexts[i].DownstreamResponse.Content.ReadAsStringAsync();
var content = await contexts[i].Items.DownstreamResponse().Content.ReadAsStringAsync();
if (string.IsNullOrWhiteSpace(content))
{
continue;
@ -81,13 +83,13 @@ namespace Ocelot.Middleware.Multiplexer
Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
};
originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "cannot return from aggregate..which reason phrase would you use?");
originalContext.Items.UpsertDownstreamResponse(new DownstreamResponse(stringContent, HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "cannot return from aggregate..which reason phrase would you use?"));
}
private static void MapAggregateError(DownstreamContext originalContext, DownstreamContext downstreamContext)
private static void MapAggregateError(HttpContext originalContext, HttpContext downstreamContext)
{
originalContext.Errors.AddRange(downstreamContext.Errors);
originalContext.DownstreamResponse = downstreamContext.DownstreamResponse;
originalContext.Items.UpsertErrors(downstreamContext.Items.Errors());
originalContext.Items.UpsertDownstreamResponse(downstreamContext.Items.DownstreamResponse());
}
}
}

View File

@ -1,9 +1,11 @@
using Ocelot.Configuration;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
namespace Ocelot.Multiplexer
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Middleware;
using System.Collections.Generic;
using System.Threading.Tasks;
public class UserDefinedResponseAggregator : IResponseAggregator
{
private readonly IDefinedAggregatorProvider _provider;
@ -13,7 +15,7 @@ namespace Ocelot.Middleware.Multiplexer
_provider = provider;
}
public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamResponses)
public async Task Aggregate(ReRoute reRoute, HttpContext originalContext, List<HttpContext> downstreamResponses)
{
var aggregator = _provider.Get(reRoute);
@ -22,11 +24,11 @@ namespace Ocelot.Middleware.Multiplexer
var aggregateResponse = await aggregator.Data
.Aggregate(downstreamResponses);
originalContext.DownstreamResponse = aggregateResponse;
originalContext.Items.UpsertDownstreamResponse(aggregateResponse);
}
else
{
originalContext.Errors.AddRange(aggregator.Errors);
originalContext.Items.UpsertErrors(aggregator.Errors);
}
}
}

View File

@ -24,12 +24,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.6.1" />
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="3.1.1">
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="3.1.3">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.164">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -1,42 +1,48 @@
namespace Ocelot.QueryStrings.Middleware
{
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.QueryStrings.Middleware
{
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
public class ClaimsToQueryStringMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IAddQueriesToRequest _addQueriesToRequest;
public class ClaimsToQueryStringMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IAddQueriesToRequest _addQueriesToRequest;
public ClaimsToQueryStringMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IAddQueriesToRequest addQueriesToRequest)
: base(loggerFactory.CreateLogger<ClaimsToQueryStringMiddleware>())
{
_next = next;
_addQueriesToRequest = addQueriesToRequest;
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
if (downstreamReRoute.ClaimsToQueries.Any())
{
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries");
public ClaimsToQueryStringMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IAddQueriesToRequest addQueriesToRequest)
: base(loggerFactory.CreateLogger<ClaimsToQueryStringMiddleware>())
{
_next = next;
_addQueriesToRequest = addQueriesToRequest;
}
public async Task Invoke(DownstreamContext context)
{
if (context.DownstreamReRoute.ClaimsToQueries.Any())
{
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries");
var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest);
if (response.IsError)
{
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
SetPipelineError(context, response.Errors);
return;
}
}
await _next.Invoke(context);
}
}
}
var downstreamRequest = httpContext.Items.DownstreamRequest();
var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(downstreamReRoute.ClaimsToQueries, httpContext.User.Claims, downstreamRequest);
if (response.IsError)
{
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
httpContext.Items.UpsertErrors(response.Errors);
return;
}
}
await _next.Invoke(httpContext);
}
}
}

View File

@ -1,11 +1,10 @@
namespace Ocelot.QueryStrings.Middleware
{
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
public static class ClaimsToQueryStringMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseClaimsToQueryStringMiddleware(this IOcelotPipelineBuilder builder)
public static IApplicationBuilder UseClaimsToQueryStringMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ClaimsToQueryStringMiddleware>();
}

View File

@ -1,149 +1,156 @@
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.RateLimit.Middleware
{
public class ClientRateLimitMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IRateLimitCounterHandler _counterHandler;
private readonly ClientRateLimitProcessor _processor;
public ClientRateLimitMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IRateLimitCounterHandler counterHandler)
: base(loggerFactory.CreateLogger<ClientRateLimitMiddleware>())
{
_next = next;
_counterHandler = counterHandler;
_processor = new ClientRateLimitProcessor(counterHandler);
}
public async Task Invoke(DownstreamContext context)
{
var options = context.DownstreamReRoute.RateLimitOptions;
namespace Ocelot.RateLimit.Middleware
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class ClientRateLimitMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly ClientRateLimitProcessor _processor;
public ClientRateLimitMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IRateLimitCounterHandler counterHandler)
: base(loggerFactory.CreateLogger<ClientRateLimitMiddleware>())
{
_next = next;
_processor = new ClientRateLimitProcessor(counterHandler);
}
public async Task Invoke(HttpContext httpContext)
{
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
var options = downstreamReRoute.RateLimitOptions;
// check if rate limiting is enabled
if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting)
{
Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}");
await _next.Invoke(context);
return;
}
// compute identity from request
var identity = SetIdentity(context.HttpContext, options);
// check white list
if (IsWhitelisted(identity, options))
{
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting");
await _next.Invoke(context);
return;
}
var rule = options.RateLimitRule;
if (rule.Limit > 0)
{
// increment counter
var counter = _processor.ProcessRequest(identity, options);
// check if limit is reached
if (counter.TotalRequests > rule.Limit)
{
//compute retry after value
var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule);
// log blocked request
LogBlockedRequest(context.HttpContext, identity, counter, rule, context.DownstreamReRoute);
if (!downstreamReRoute.EnableEndpointEndpointRateLimiting)
{
Logger.LogInformation($"EndpointRateLimiting is not enabled for {downstreamReRoute.DownstreamPathTemplate.Value}");
await _next.Invoke(httpContext);
return;
}
// compute identity from request
var identity = SetIdentity(httpContext, options);
// check white list
if (IsWhitelisted(identity, options))
{
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting");
await _next.Invoke(httpContext);
return;
}
var rule = options.RateLimitRule;
if (rule.Limit > 0)
{
// increment counter
var counter = _processor.ProcessRequest(identity, options);
// check if limit is reached
if (counter.TotalRequests > rule.Limit)
{
//compute retry after value
var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule);
// log blocked request
LogBlockedRequest(httpContext, identity, counter, rule, downstreamReRoute);
var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
// break execution
await ReturnQuotaExceededResponse(context.HttpContext, options, retrystring);
var ds = ReturnQuotaExceededResponse(httpContext, options, retrystring);
httpContext.Items.UpsertDownstreamResponse(ds);
// Set Error
context.Errors.Add(new QuotaExceededError(this.GetResponseMessage(options)));
return;
}
}
//set X-Rate-Limit headers for the longest period
if (!options.DisableRateLimitHeaders)
{
var headers = _processor.GetRateLimitHeaders(context.HttpContext, identity, options);
context.HttpContext.Response.OnStarting(SetRateLimitHeaders, state: headers);
}
await _next.Invoke(context);
}
public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)
{
var clientId = "client";
if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader))
{
clientId = httpContext.Request.Headers[option.ClientIdHeader].First();
}
return new ClientRequestIdentity(
clientId,
httpContext.Request.Path.ToString().ToLowerInvariant(),
httpContext.Request.Method.ToLowerInvariant()
);
}
public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option)
{
if (option.ClientWhitelist.Contains(requestIdentity.ClientId))
{
return true;
}
return false;
}
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute)
{
Logger.LogInformation(
$"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}.");
httpContext.Items.SetError(new QuotaExceededError(this.GetResponseMessage(options), options.HttpStatusCode));
return;
}
}
//set X-Rate-Limit headers for the longest period
if (!options.DisableRateLimitHeaders)
{
var headers = _processor.GetRateLimitHeaders(httpContext, identity, options);
httpContext.Response.OnStarting(SetRateLimitHeaders, state: headers);
}
await _next.Invoke(httpContext);
}
public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter)
{
var message = this.GetResponseMessage(option);
if (!option.DisableRateLimitHeaders)
{
httpContext.Response.Headers["Retry-After"] = retryAfter;
}
httpContext.Response.StatusCode = option.HttpStatusCode;
return httpContext.Response.WriteAsync(message);
}
private string GetResponseMessage(RateLimitOptions option)
{
var message = string.IsNullOrEmpty(option.QuotaExceededMessage)
? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}."
: option.QuotaExceededMessage;
return message;
}
private Task SetRateLimitHeaders(object rateLimitHeaders)
{
var headers = (RateLimitHeaders)rateLimitHeaders;
headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit;
headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining;
headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset;
return Task.CompletedTask;
}
}
public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)
{
var clientId = "client";
if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader))
{
clientId = httpContext.Request.Headers[option.ClientIdHeader].First();
}
return new ClientRequestIdentity(
clientId,
httpContext.Request.Path.ToString().ToLowerInvariant(),
httpContext.Request.Method.ToLowerInvariant()
);
}
public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option)
{
if (option.ClientWhitelist.Contains(requestIdentity.ClientId))
{
return true;
}
return false;
}
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute)
{
Logger.LogInformation(
$"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}.");
}
public virtual DownstreamResponse ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter)
{
var message = GetResponseMessage(option);
var http = new HttpResponseMessage((HttpStatusCode)option.HttpStatusCode);
http.Content = new StringContent(message);
if (!option.DisableRateLimitHeaders)
{
http.Headers.TryAddWithoutValidation("Retry-After", retryAfter);
}
return new DownstreamResponse(http);
}
private string GetResponseMessage(RateLimitOptions option)
{
var message = string.IsNullOrEmpty(option.QuotaExceededMessage)
? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}."
: option.QuotaExceededMessage;
return message;
}
private Task SetRateLimitHeaders(object rateLimitHeaders)
{
var headers = (RateLimitHeaders)rateLimitHeaders;
headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit;
headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining;
headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset;
return Task.CompletedTask;
}
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.RateLimit.Middleware
{
public static class RateLimitMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseRateLimiting(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<ClientRateLimitMiddleware>();
}
}
namespace Ocelot.RateLimit.Middleware
{
using Microsoft.AspNetCore.Builder;
public static class RateLimitMiddlewareExtensions
{
public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ClientRateLimitMiddleware>();
}
}
}

View File

@ -4,8 +4,8 @@ namespace Ocelot.RateLimit
{
public class QuotaExceededError : Error
{
public QuotaExceededError(string message)
: base(message, OcelotErrorCode.QuotaExceededError)
public QuotaExceededError(string message, int httpStatusCode)
: base(message, OcelotErrorCode.QuotaExceededError, httpStatusCode)
{
}
}

Some files were not shown because too many files have changed in this diff Show More