Merge pull request #52 from ThreeMammals/develop

merge newest code
This commit is contained in:
geffzhang 2018-10-11 19:57:16 +08:00 committed by GitHub
commit 5fb54e8032
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 4498 additions and 3211 deletions

View File

@ -107,7 +107,7 @@ Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddOcelot() .AddOcelot(hostingContext.HostingEnvironment)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
@ -117,6 +117,22 @@ The way Ocelot merges the files is basically load them, loop over them, add any
At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems.
You can also give Ocelot a specific path to look in for the configuration files like below.
.. code-block:: csharp
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddOcelot("/foo/bar", hostingContext.HostingEnvironment)
.AddEnvironmentVariables();
})
Ocelot needs the HostingEnvironment so it know's to exclude anything environment specific from the algorithm.
Store configuration in consul Store configuration in consul
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -81,6 +81,7 @@ Placeholders
Ocelot allows placeholders that can be used in header transformation. Ocelot allows placeholders that can be used in header transformation.
{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP.
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. {TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
@ -120,6 +121,17 @@ finally if you are using a load balancer with Ocelot you will get multiple downs
"AllowAutoRedirect": false, "AllowAutoRedirect": false,
}, },
X-Forwarded-For
^^^^^^^^^^^^^^^
An example of using {RemoteIpAddress} placeholder...
.. code-block:: json
"UpstreamHeaderTransform": {
"X-Forwarded-For": "{RemoteIpAddress}"
}
Future Future
^^^^^^ ^^^^^^

View File

@ -22,7 +22,7 @@ namespace Ocelot.Authentication.Middleware
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
if (IsAuthenticatedRoute(context.DownstreamReRoute)) if (context.HttpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(context.DownstreamReRoute))
{ {
Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");

View File

@ -82,7 +82,7 @@
} }
else else
{ {
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} route does not require user to be authorised"); Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
await _next.Invoke(context); await _next.Invoke(context);
} }
} }

View File

@ -9,14 +9,15 @@ namespace Ocelot.Cache
HttpStatusCode statusCode, HttpStatusCode statusCode,
Dictionary<string, IEnumerable<string>> headers, Dictionary<string, IEnumerable<string>> headers,
string body, string body,
Dictionary<string, IEnumerable<string>> contentHeaders Dictionary<string, IEnumerable<string>> contentHeaders,
string reasonPhrase
) )
{ {
StatusCode = statusCode; StatusCode = statusCode;
Headers = headers ?? new Dictionary<string, IEnumerable<string>>(); Headers = headers ?? new Dictionary<string, IEnumerable<string>>();
ContentHeaders = contentHeaders ?? new Dictionary<string, IEnumerable<string>>(); ContentHeaders = contentHeaders ?? new Dictionary<string, IEnumerable<string>>();
Body = body ?? ""; Body = body ?? "";
ReasonPhrase = reasonPhrase;
} }
public HttpStatusCode StatusCode { get; private set; } public HttpStatusCode StatusCode { get; private set; }
@ -26,5 +27,7 @@ namespace Ocelot.Cache
public Dictionary<string, IEnumerable<string>> ContentHeaders { get; private set; } public Dictionary<string, IEnumerable<string>> ContentHeaders { get; private set; }
public string Body { get; private set; } public string Body { get; private set; }
public string ReasonPhrase { get; private set; }
} }
} }

View File

@ -87,7 +87,7 @@
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value); streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
} }
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList()); return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase);
} }
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response) internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
@ -109,7 +109,7 @@
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value); var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
var cached = new CachedResponse(statusCode, headers, body, contentHeaders); var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase);
return cached; return cached;
} }
} }

View File

@ -3,11 +3,11 @@ using Ocelot.Middleware.Pipeline;
namespace Ocelot.Claims.Middleware namespace Ocelot.Claims.Middleware
{ {
public static class ClaimsBuilderMiddlewareExtensions public static class ClaimsToClaimsMiddlewareExtensions
{ {
public static IOcelotPipelineBuilder UseClaimsBuilderMiddleware(this IOcelotPipelineBuilder builder) public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder)
{ {
return builder.UseMiddleware<ClaimsBuilderMiddleware>(); return builder.UseMiddleware<ClaimsToClaimsMiddleware>();
} }
} }
} }

View File

@ -8,15 +8,15 @@ using Ocelot.Middleware;
namespace Ocelot.Claims.Middleware namespace Ocelot.Claims.Middleware
{ {
public class ClaimsBuilderMiddleware : OcelotMiddleware public class ClaimsToClaimsMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IAddClaimsToRequest _addClaimsToRequest; private readonly IAddClaimsToRequest _addClaimsToRequest;
public ClaimsBuilderMiddleware(OcelotRequestDelegate next, public ClaimsToClaimsMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IAddClaimsToRequest addClaimsToRequest) IAddClaimsToRequest addClaimsToRequest)
:base(loggerFactory.CreateLogger<ClaimsBuilderMiddleware>()) :base(loggerFactory.CreateLogger<ClaimsToClaimsMiddleware>())
{ {
_next = next; _next = next;
_addClaimsToRequest = addClaimsToRequest; _addClaimsToRequest = addClaimsToRequest;

View File

@ -33,13 +33,12 @@ namespace Ocelot.Configuration.Builder
private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace; private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace;
private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace; private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace;
private readonly List<DownstreamHostAndPort> _downstreamAddresses; private readonly List<DownstreamHostAndPort> _downstreamAddresses;
private string _upstreamHost;
private string _key; private string _key;
private List<string> _delegatingHandlers; private List<string> _delegatingHandlers;
private List<AddHeader> _addHeadersToDownstream; private List<AddHeader> _addHeadersToDownstream;
private List<AddHeader> _addHeadersToUpstream; private List<AddHeader> _addHeadersToUpstream;
private bool _dangerousAcceptAnyServerCertificateValidator; private bool _dangerousAcceptAnyServerCertificateValidator;
private SecurityOptions _securityOptions;
public DownstreamReRouteBuilder() public DownstreamReRouteBuilder()
{ {
_downstreamAddresses = new List<DownstreamHostAndPort>(); _downstreamAddresses = new List<DownstreamHostAndPort>();
@ -54,12 +53,6 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithUpstreamHost(string upstreamAddresses)
{
_upstreamHost = upstreamAddresses;
return this;
}
public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions)
{ {
_loadBalancerOptions = loadBalancerOptions; _loadBalancerOptions = loadBalancerOptions;
@ -234,6 +227,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithSecurityOptions(SecurityOptions securityOptions)
{
_securityOptions = securityOptions;
return this;
}
public DownstreamReRoute Build() public DownstreamReRoute Build()
{ {
return new DownstreamReRoute( return new DownstreamReRoute(
@ -265,7 +264,8 @@ namespace Ocelot.Configuration.Builder
_delegatingHandlers, _delegatingHandlers,
_addHeadersToDownstream, _addHeadersToDownstream,
_addHeadersToUpstream, _addHeadersToUpstream,
_dangerousAcceptAnyServerCertificateValidator); _dangerousAcceptAnyServerCertificateValidator,
_securityOptions);
} }
} }
} }

View File

@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder
private bool _isAuthorised; private bool _isAuthorised;
private bool _isCached; private bool _isCached;
private bool _enableRateLimiting; private bool _enableRateLimiting;
private bool _useServiceDiscovery;
public ReRouteOptionsBuilder WithIsCached(bool isCached) public ReRouteOptionsBuilder WithIsCached(bool isCached)
{ {
@ -31,9 +32,15 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteOptionsBuilder WithUseServiceDiscovery(bool useServiceDiscovery)
{
_useServiceDiscovery = useServiceDiscovery;
return this;
}
public ReRouteOptions Build() public ReRouteOptions Build()
{ {
return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _enableRateLimiting); return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _enableRateLimiting, _useServiceDiscovery);
} }
} }
} }

View File

@ -0,0 +1,50 @@
namespace Ocelot.Configuration.Creator
{
using System.Collections.Generic;
using System.Linq;
using Builder;
using File;
public class AggregatesCreator : IAggregatesCreator
{
private readonly IUpstreamTemplatePatternCreator _creator;
public AggregatesCreator(IUpstreamTemplatePatternCreator creator)
{
_creator = creator;
}
public List<ReRoute> Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes)
{
return fileConfiguration.Aggregates
.Select(aggregate => SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration))
.Where(aggregate => aggregate != null)
.ToList();
}
private ReRoute SetUpAggregateReRoute(IEnumerable<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
{
var applicableReRoutes = reRoutes
.SelectMany(x => x.DownstreamReRoute)
.Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key))
.ToList();
if (applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count)
{
return null;
}
var upstreamTemplatePattern = _creator.Create(aggregateReRoute);
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamReRoutes(applicableReRoutes)
.WithUpstreamHost(aggregateReRoute.UpstreamHost)
.WithAggregator(aggregateReRoute.Aggregator)
.Build();
return reRoute;
}
}
}

View File

@ -0,0 +1,55 @@
namespace Ocelot.Configuration.Creator
{
using System;
using System.Collections.Generic;
using File;
using DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
public class ConfigurationCreator : IConfigurationCreator
{
private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator;
private readonly IQoSOptionsCreator _qosOptionsCreator;
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IAdministrationPath _adminPath;
private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator;
public ConfigurationCreator(
IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IServiceProvider serviceProvider,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator
)
{
_adminPath = serviceProvider.GetService<IAdministrationPath>();
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator;
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
}
public InternalConfiguration Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes)
{
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
var lbOptions = _loadBalancerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
var qosOptions = _qosOptionsCreator.Create(fileConfiguration.GlobalConfiguration.QoSOptions);
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions);
var adminPath = _adminPath != null ? _adminPath.Path : null;
return new InternalConfiguration(reRoutes,
adminPath,
serviceProviderConfiguration,
fileConfiguration.GlobalConfiguration.RequestIdKey,
lbOptions,
fileConfiguration.GlobalConfiguration.DownstreamScheme,
qosOptions,
httpHandlerOptions
);
}
}
}

View File

@ -0,0 +1,42 @@
namespace Ocelot.Configuration.Creator
{
using System.Collections.Generic;
using System.Linq;
using Builder;
using File;
public class DynamicsCreator : IDynamicsCreator
{
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
public DynamicsCreator(IRateLimitOptionsCreator rateLimitOptionsCreator)
{
_rateLimitOptionsCreator = rateLimitOptionsCreator;
}
public List<ReRoute> Create(FileConfiguration fileConfiguration)
{
return fileConfiguration.DynamicReRoutes
.Select(dynamic => SetUpDynamicReRoute(dynamic, fileConfiguration.GlobalConfiguration))
.ToList();
}
private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration)
{
var rateLimitOption = _rateLimitOptionsCreator
.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(rateLimitOption.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicReRoute.ServiceName)
.Build();
var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.Build();
return reRoute;
}
}
}

View File

@ -1,84 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Ocelot.Cache;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Validator;
using Ocelot.DependencyInjection;
using Ocelot.Logging;
using Ocelot.Responses;
using Microsoft.Extensions.DependencyInjection;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
using LoadBalancer.LoadBalancers; using System.Linq;
using System.Threading.Tasks;
using File;
using Validator;
using Responses;
/// <summary>
/// Register as singleton
/// </summary>
public class FileInternalConfigurationCreator : IInternalConfigurationCreator public class FileInternalConfigurationCreator : IInternalConfigurationCreator
{ {
private readonly IConfigurationValidator _configurationValidator; private readonly IConfigurationValidator _configurationValidator;
private readonly IOcelotLogger _logger; private readonly IConfigurationCreator _configCreator;
private readonly IClaimsToThingCreator _claimsToThingCreator; private readonly IDynamicsCreator _dynamicsCreator;
private readonly IAuthenticationOptionsCreator _authOptionsCreator; private readonly IReRoutesCreator _reRoutesCreator;
private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; private readonly IAggregatesCreator _aggregatesCreator;
private readonly IRequestIdKeyCreator _requestIdKeyCreator;
private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator;
private readonly IQoSOptionsCreator _qosOptionsCreator;
private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator;
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
private readonly IRegionCreator _regionCreator;
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IAdministrationPath _adminPath;
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
public FileInternalConfigurationCreator( public FileInternalConfigurationCreator(
IConfigurationValidator configurationValidator, IConfigurationValidator configurationValidator,
IOcelotLoggerFactory loggerFactory, IReRoutesCreator reRoutesCreator,
IClaimsToThingCreator claimsToThingCreator, IAggregatesCreator aggregatesCreator,
IAuthenticationOptionsCreator authOptionsCreator, IDynamicsCreator dynamicsCreator,
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, IConfigurationCreator configCreator
IRequestIdKeyCreator requestIdKeyCreator,
IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator,
IReRouteOptionsCreator fileReRouteOptionsCreator,
IRateLimitOptionsCreator rateLimitOptionsCreator,
IRegionCreator regionCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IServiceProvider serviceProvider,
IHeaderFindAndReplaceCreator headerFAndRCreator,
IDownstreamAddressesCreator downstreamAddressesCreator
) )
{ {
_downstreamAddressesCreator = downstreamAddressesCreator; _configCreator = configCreator;
_headerFAndRCreator = headerFAndRCreator; _dynamicsCreator = dynamicsCreator;
_adminPath = serviceProvider.GetService<IAdministrationPath>(); _aggregatesCreator = aggregatesCreator;
_regionCreator = regionCreator; _reRoutesCreator = reRoutesCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
_authOptionsCreator = authOptionsCreator;
_configurationValidator = configurationValidator; _configurationValidator = configurationValidator;
_logger = loggerFactory.CreateLogger<FileInternalConfigurationCreator>();
_claimsToThingCreator = claimsToThingCreator;
_serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator;
_fileReRouteOptionsCreator = fileReRouteOptionsCreator;
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
} }
public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration) public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration)
{
var config = await SetUpConfiguration(fileConfiguration);
return config;
}
private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration)
{ {
var response = await _configurationValidator.IsValid(fileConfiguration); var response = await _configurationValidator.IsValid(fileConfiguration);
@ -87,197 +38,20 @@ namespace Ocelot.Configuration.Creator
return new ErrorResponse<IInternalConfiguration>(response.Data.Errors); return new ErrorResponse<IInternalConfiguration>(response.Data.Errors);
} }
var reRoutes = new List<ReRoute>(); var reRoutes = _reRoutesCreator.Create(fileConfiguration);
foreach (var reRoute in fileConfiguration.ReRoutes) var aggregates = _aggregatesCreator.Create(fileConfiguration, reRoutes);
{
var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration);
var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); var dynamicReRoute = _dynamicsCreator.Create(fileConfiguration);
reRoutes.Add(ocelotReRoute); var mergedReRoutes = reRoutes
} .Union(aggregates)
.Union(dynamicReRoute)
.ToList();
foreach (var aggregate in fileConfiguration.Aggregates) var config = _configCreator.Create(fileConfiguration, mergedReRoutes);
{
var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration);
reRoutes.Add(ocelotReRoute);
}
foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes)
{
var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration);
reRoutes.Add(reRoute);
}
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
var qosOptions = _qosOptionsCreator.Create(fileConfiguration.GlobalConfiguration.QoSOptions);
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions);
var adminPath = _adminPath != null ? _adminPath.Path : null;
var config = new InternalConfiguration(reRoutes,
adminPath,
serviceProviderConfiguration,
fileConfiguration.GlobalConfiguration.RequestIdKey,
lbOptions,
fileConfiguration.GlobalConfiguration.DownstreamScheme,
qosOptions,
httpHandlerOptions
);
return new OkResponse<IInternalConfiguration>(config); return new OkResponse<IInternalConfiguration>(config);
} }
private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration)
{
var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(true)
.WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicReRoute.ServiceName)
.Build();
var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.Build();
return reRoute;
}
private ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
{
var applicableReRoutes = reRoutes
.SelectMany(x => x.DownstreamReRoute)
.Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key))
.ToList();
if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count)
{
//todo - log or throw or return error whatever?
}
//make another re route out of these
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute);
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamReRoutes(applicableReRoutes)
.WithUpstreamHost(aggregateReRoute.UpstreamHost)
.WithAggregator(aggregateReRoute.Aggregator)
.Build();
return reRoute;
}
private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes)
{
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamReRoute(downstreamReRoutes)
.WithUpstreamHost(fileReRoute.UpstreamHost)
.Build();
return reRoute;
}
private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute);
var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration);
var reRouteKey = CreateReRouteKey(fileReRoute);
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
var region = _regionCreator.Create(fileReRoute);
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute.HttpHandlerOptions);
var hAndRs = _headerFAndRCreator.Create(fileReRoute);
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
var lbOptions = CreateLoadBalancerOptions(fileReRoute.LoadBalancerOptions);
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName);
var reRoute = new DownstreamReRouteBuilder()
.WithKey(fileReRoute.Key)
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
.WithAuthenticationOptions(authOptionsForRoute)
.WithClaimsToHeaders(claimsToHeaders)
.WithClaimsToClaims(claimsToClaims)
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
.WithIsAuthorised(fileReRouteOptions.IsAuthorised)
.WithClaimsToQueries(claimsToQueries)
.WithRequestIdKey(requestIdKey)
.WithIsCached(fileReRouteOptions.IsCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
.WithLoadBalancerOptions(lbOptions)
.WithDownstreamAddresses(downstreamAddresses)
.WithLoadBalancerKey(reRouteKey)
.WithQosOptions(qosOptions)
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption)
.WithHttpHandlerOptions(httpHandlerOptions)
.WithServiceName(fileReRoute.ServiceName)
.WithUseServiceDiscovery(useServiceDiscovery)
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.WithUpstreamHost(fileReRoute.UpstreamHost)
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
.Build();
return reRoute;
}
private LoadBalancerOptions CreateLoadBalancerOptions(FileLoadBalancerOptions options)
{
return new LoadBalancerOptionsBuilder()
.WithType(options.Type)
.WithKey(options.Key)
.WithExpiryInMs(options.Expiry)
.Build();
}
private string CreateReRouteKey(FileReRoute fileReRoute)
{
if (!string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Type) && !string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Key) && fileReRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions))
{
return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}";
}
return $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}|{string.Join(",", fileReRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}";
}
} }
} }

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IAggregatesCreator
{
List<ReRoute> Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes);
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IConfigurationCreator
{
InternalConfiguration Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes);
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IDynamicsCreator
{
List<ReRoute> Create(FileConfiguration fileConfiguration);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface ILoadBalancerOptionsCreator
{
LoadBalancerOptions Create(FileLoadBalancerOptions options);
}
}

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
@ -5,7 +6,7 @@ namespace Ocelot.Configuration.Creator
public interface IQoSOptionsCreator public interface IQoSOptionsCreator
{ {
QoSOptions Create(FileQoSOptions options); QoSOptions Create(FileQoSOptions options);
QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods); QoSOptions Create(FileQoSOptions options, string pathTemplate, List<string> httpMethods);
QoSOptions Create(QoSOptions options, string pathTemplate, string[] httpMethods); QoSOptions Create(QoSOptions options, string pathTemplate, List<string> httpMethods);
} }
} }

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IReRouteKeyCreator
{
string Create(FileReRoute fileReRoute);
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IReRoutesCreator
{
List<ReRoute> Create(FileConfiguration fileConfiguration);
}
}

View File

@ -0,0 +1,12 @@
using Ocelot.Configuration.File;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Configuration.Creator
{
public interface ISecurityOptionsCreator
{
SecurityOptions Create(FileSecurityOptions securityOptions);
}
}

View File

@ -0,0 +1,17 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class LoadBalancerOptionsCreator : ILoadBalancerOptionsCreator
{
public LoadBalancerOptions Create(FileLoadBalancerOptions options)
{
return new LoadBalancerOptionsBuilder()
.WithType(options.Type)
.WithKey(options.Key)
.WithExpiryInMs(options.Expiry)
.Build();
}
}
}

View File

@ -2,6 +2,7 @@ namespace Ocelot.Configuration.Creator
{ {
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using System.Collections.Generic;
using System.Linq; using System.Linq;
public class QoSOptionsCreator : IQoSOptionsCreator public class QoSOptionsCreator : IQoSOptionsCreator
@ -15,14 +16,14 @@ namespace Ocelot.Configuration.Creator
.Build(); .Build();
} }
public QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods) public QoSOptions Create(FileQoSOptions options, string pathTemplate, List<string> httpMethods)
{ {
var key = CreateKey(pathTemplate, httpMethods); var key = CreateKey(pathTemplate, httpMethods);
return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking);
} }
public QoSOptions Create(QoSOptions options, string pathTemplate, string[] httpMethods) public QoSOptions Create(QoSOptions options, string pathTemplate, List<string> httpMethods)
{ {
var key = CreateKey(pathTemplate, httpMethods); var key = CreateKey(pathTemplate, httpMethods);
@ -39,7 +40,7 @@ namespace Ocelot.Configuration.Creator
.Build(); .Build();
} }
private string CreateKey(string pathTemplate, string[] httpMethods) private string CreateKey(string pathTemplate, List<string> httpMethods)
{ {
return $"{pathTemplate.FirstOrDefault()}|{string.Join(",", httpMethods)}"; return $"{pathTemplate.FirstOrDefault()}|{string.Join(",", httpMethods)}";
} }

View File

@ -1,5 +1,4 @@
using System; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
@ -8,11 +7,9 @@ namespace Ocelot.Configuration.Creator
{ {
public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration) public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration)
{ {
RateLimitOptions rateLimitOption = null;
if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting) if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting)
{ {
rateLimitOption = new RateLimitOptionsBuilder() return new RateLimitOptionsBuilder()
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
.WithClientWhiteList(fileRateLimitRule.ClientWhitelist) .WithClientWhiteList(fileRateLimitRule.ClientWhitelist)
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
@ -26,7 +23,7 @@ namespace Ocelot.Configuration.Creator
.Build(); .Build();
} }
return rateLimitOption; return new RateLimitOptionsBuilder().WithEnableRateLimiting(false).Build();
} }
} }
} }

View File

@ -0,0 +1,31 @@
using System.Linq;
using Ocelot.Configuration.File;
using Ocelot.LoadBalancer.LoadBalancers;
namespace Ocelot.Configuration.Creator
{
public class ReRouteKeyCreator : IReRouteKeyCreator
{
public string Create(FileReRoute fileReRoute)
{
if (IsStickySession(fileReRoute))
{
return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}";
}
return $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}|{string.Join(",", fileReRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}";
}
private bool IsStickySession(FileReRoute fileReRoute)
{
if (!string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Type)
&& !string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Key)
&& fileReRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions))
{
return true;
}
return false;
}
}
}

View File

@ -11,12 +11,14 @@ namespace Ocelot.Configuration.Creator
var isAuthorised = IsAuthorised(fileReRoute); var isAuthorised = IsAuthorised(fileReRoute);
var isCached = IsCached(fileReRoute); var isCached = IsCached(fileReRoute);
var enableRateLimiting = IsEnableRateLimiting(fileReRoute); var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName);
var options = new ReRouteOptionsBuilder() var options = new ReRouteOptionsBuilder()
.WithIsAuthenticated(isAuthenticated) .WithIsAuthenticated(isAuthenticated)
.WithIsAuthorised(isAuthorised) .WithIsAuthorised(isAuthorised)
.WithIsCached(isCached) .WithIsCached(isCached)
.WithRateLimiting(enableRateLimiting) .WithRateLimiting(enableRateLimiting)
.WithUseServiceDiscovery(useServiceDiscovery)
.Build(); .Build();
return options; return options;

View File

@ -0,0 +1,156 @@
namespace Ocelot.Configuration.Creator
{
using System.Collections.Generic;
using System.Linq;
using Cache;
using Builder;
using File;
public class ReRoutesCreator : IReRoutesCreator
{
private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator;
private readonly IClaimsToThingCreator _claimsToThingCreator;
private readonly IAuthenticationOptionsCreator _authOptionsCreator;
private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
private readonly IRequestIdKeyCreator _requestIdKeyCreator;
private readonly IQoSOptionsCreator _qosOptionsCreator;
private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator;
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
private readonly IRegionCreator _regionCreator;
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
private readonly IReRouteKeyCreator _reRouteKeyCreator;
private readonly ISecurityOptionsCreator _securityOptionsCreator;
public ReRoutesCreator(
IClaimsToThingCreator claimsToThingCreator,
IAuthenticationOptionsCreator authOptionsCreator,
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator,
IRequestIdKeyCreator requestIdKeyCreator,
IQoSOptionsCreator qosOptionsCreator,
IReRouteOptionsCreator fileReRouteOptionsCreator,
IRateLimitOptionsCreator rateLimitOptionsCreator,
IRegionCreator regionCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IHeaderFindAndReplaceCreator headerFAndRCreator,
IDownstreamAddressesCreator downstreamAddressesCreator,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IReRouteKeyCreator reRouteKeyCreator,
ISecurityOptionsCreator securityOptionsCreator
)
{
_reRouteKeyCreator = reRouteKeyCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_downstreamAddressesCreator = downstreamAddressesCreator;
_headerFAndRCreator = headerFAndRCreator;
_regionCreator = regionCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
_authOptionsCreator = authOptionsCreator;
_claimsToThingCreator = claimsToThingCreator;
_qosOptionsCreator = qosOptionsCreator;
_fileReRouteOptionsCreator = fileReRouteOptionsCreator;
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_securityOptionsCreator = securityOptionsCreator;
}
public List<ReRoute> Create(FileConfiguration fileConfiguration)
{
return fileConfiguration.ReRoutes
.Select(reRoute =>
{
var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration);
return SetUpReRoute(reRoute, downstreamReRoute);
})
.ToList();
}
private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute);
var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration);
var reRouteKey = _reRouteKeyCreator.Create(fileReRoute);
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod);
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
var region = _regionCreator.Create(fileReRoute);
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute.HttpHandlerOptions);
var hAndRs = _headerFAndRCreator.Create(fileReRoute);
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
var lbOptions = _loadBalancerOptionsCreator.Create(fileReRoute.LoadBalancerOptions);
var securityOptions = _securityOptionsCreator.Create(fileReRoute.SecurityOptions);
var reRoute = new DownstreamReRouteBuilder()
.WithKey(fileReRoute.Key)
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
.WithAuthenticationOptions(authOptionsForRoute)
.WithClaimsToHeaders(claimsToHeaders)
.WithClaimsToClaims(claimsToClaims)
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
.WithIsAuthorised(fileReRouteOptions.IsAuthorised)
.WithClaimsToQueries(claimsToQueries)
.WithRequestIdKey(requestIdKey)
.WithIsCached(fileReRouteOptions.IsCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
.WithLoadBalancerOptions(lbOptions)
.WithDownstreamAddresses(downstreamAddresses)
.WithLoadBalancerKey(reRouteKey)
.WithQosOptions(qosOptions)
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption)
.WithHttpHandlerOptions(httpHandlerOptions)
.WithServiceName(fileReRoute.ServiceName)
.WithUseServiceDiscovery(fileReRouteOptions.UseServiceDiscovery)
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
.WithSecurityOptions(securityOptions)
.Build();
return reRoute;
}
private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes)
{
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamReRoute(downstreamReRoutes)
.WithUpstreamHost(fileReRoute.UpstreamHost)
.Build();
return reRoute;
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class SecurityOptionsCreator : ISecurityOptionsCreator
{
public SecurityOptions Create(FileSecurityOptions securityOptions)
{
return new SecurityOptions(securityOptions.IPAllowedList, securityOptions.IPBlockedList);
}
}
}

View File

@ -30,12 +30,13 @@ namespace Ocelot.Configuration
bool isAuthenticated, bool isAuthenticated,
bool isAuthorised, bool isAuthorised,
AuthenticationOptions authenticationOptions, AuthenticationOptions authenticationOptions,
DownstreamPathTemplate downstreamDownstreamPathTemplate, DownstreamPathTemplate downstreamPathTemplate,
string loadBalancerKey, string loadBalancerKey,
List<string> delegatingHandlers, List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream, List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream, List<AddHeader> addHeadersToUpstream,
bool dangerousAcceptAnyServerCertificateValidator) bool dangerousAcceptAnyServerCertificateValidator,
SecurityOptions securityOptions)
{ {
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream; AddHeadersToDownstream = addHeadersToDownstream;
@ -63,9 +64,10 @@ namespace Ocelot.Configuration
IsAuthenticated = isAuthenticated; IsAuthenticated = isAuthenticated;
IsAuthorised = isAuthorised; IsAuthorised = isAuthorised;
AuthenticationOptions = authenticationOptions; AuthenticationOptions = authenticationOptions;
DownstreamDownstreamPathTemplate = downstreamDownstreamPathTemplate; DownstreamPathTemplate = downstreamPathTemplate;
LoadBalancerKey = loadBalancerKey; LoadBalancerKey = loadBalancerKey;
AddHeadersToUpstream = addHeadersToUpstream; AddHeadersToUpstream = addHeadersToUpstream;
SecurityOptions = securityOptions;
} }
public string Key { get; } public string Key { get; }
@ -91,11 +93,12 @@ namespace Ocelot.Configuration
public bool IsAuthenticated { get; } public bool IsAuthenticated { get; }
public bool IsAuthorised { get; } public bool IsAuthorised { get; }
public AuthenticationOptions AuthenticationOptions { get; } public AuthenticationOptions AuthenticationOptions { get; }
public DownstreamPathTemplate DownstreamDownstreamPathTemplate { get; } public DownstreamPathTemplate DownstreamPathTemplate { get; }
public string LoadBalancerKey { get; } public string LoadBalancerKey { get; }
public List<string> DelegatingHandlers { get; } public List<string> DelegatingHandlers { get; }
public List<AddHeader> AddHeadersToDownstream { get; } public List<AddHeader> AddHeadersToDownstream { get; }
public List<AddHeader> AddHeadersToUpstream { get; } public List<AddHeader> AddHeadersToUpstream { get; }
public bool DangerousAcceptAnyServerCertificateValidator { get; } public bool DangerousAcceptAnyServerCertificateValidator { get; }
public SecurityOptions SecurityOptions { get; }
} }
} }

View File

@ -21,6 +21,7 @@ namespace Ocelot.Configuration.File
DownstreamHostAndPorts = new List<FileHostAndPort>(); DownstreamHostAndPorts = new List<FileHostAndPort>();
DelegatingHandlers = new List<string>(); DelegatingHandlers = new List<string>();
LoadBalancerOptions = new FileLoadBalancerOptions(); LoadBalancerOptions = new FileLoadBalancerOptions();
SecurityOptions = new FileSecurityOptions();
Priority = 1; Priority = 1;
} }
@ -50,5 +51,6 @@ namespace Ocelot.Configuration.File
public int Priority { get;set; } public int Priority { get;set; }
public int Timeout { get; set; } public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; } public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
public FileSecurityOptions SecurityOptions { get; set; }
} }
} }

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Configuration.File
{
public class FileSecurityOptions
{
public FileSecurityOptions()
{
IPAllowedList = new List<string>();
IPBlockedList = new List<string>();
}
public List<string> IPAllowedList { get; set; }
public List<string> IPBlockedList { get; set; }
}
}

View File

@ -24,7 +24,7 @@
public string TimeoutStrategy { get; } public string TimeoutStrategy { get; }
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0; public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 || TimeoutValue > 0;
public string Key { get; } public string Key { get; }
} }

View File

@ -2,17 +2,19 @@ namespace Ocelot.Configuration
{ {
public class ReRouteOptions public class ReRouteOptions
{ {
public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting, bool useServiceDiscovery)
{ {
IsAuthenticated = isAuthenticated; IsAuthenticated = isAuthenticated;
IsAuthorised = isAuthorised; IsAuthorised = isAuthorised;
IsCached = isCached; IsCached = isCached;
EnableRateLimiting = isEnableRateLimiting; EnableRateLimiting = isEnableRateLimiting;
UseServiceDiscovery = useServiceDiscovery;
} }
public bool IsAuthenticated { get; private set; } public bool IsAuthenticated { get; private set; }
public bool IsAuthorised { get; private set; } public bool IsAuthorised { get; private set; }
public bool IsCached { get; private set; } public bool IsCached { get; private set; }
public bool EnableRateLimiting { get; private set; } public bool EnableRateLimiting { get; private set; }
public bool UseServiceDiscovery { get; private set; }
} }
} }

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Configuration
{
public class SecurityOptions
{
public SecurityOptions(List<string> allowedList, List<string> blockedList)
{
this.IPAllowedList = allowedList;
this.IPBlockedList = blockedList;
}
public List<string> IPAllowedList { get; private set; }
public List<string> IPBlockedList { get; private set; }
}
}

View File

@ -9,6 +9,7 @@ namespace Ocelot.DependencyInjection
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Configuration.File; using Configuration.File;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.AspNetCore.Hosting;
public static class ConfigurationBuilderExtensions public static class ConfigurationBuilderExtensions
{ {
@ -28,37 +29,42 @@ namespace Ocelot.DependencyInjection
return builder; return builder;
} }
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IHostingEnvironment env)
{ {
return builder.AddOcelot("."); return builder.AddOcelot(".", env);
} }
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder) public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IHostingEnvironment env)
{ {
const string pattern = "(?i)ocelot\\.([a-zA-Z0-9]*)(\\.json)$"; const string primaryConfigFile = "ocelot.json";
var reg = new Regex(pattern); const string globalConfigFile = "ocelot.global.json";
var files = Directory.GetFiles(folder) const string subConfigPattern = @"^ocelot\.[a-zA-Z0-9]+\.json$";
.Where(path => reg.IsMatch(path))
string excludeConfigName = env?.EnvironmentName != null ? $"ocelot.{env.EnvironmentName}.json" : string.Empty;
var reg = new Regex(subConfigPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
var files = new DirectoryInfo(folder)
.EnumerateFiles()
.Where(fi => reg.IsMatch(fi.Name) && (fi.Name != excludeConfigName))
.ToList(); .ToList();
var fileConfiguration = new FileConfiguration(); var fileConfiguration = new FileConfiguration();
foreach (var file in files) foreach (var file in files)
{ {
// windows and unix sigh... if(files.Count > 1 && file.Name.Equals(primaryConfigFile, StringComparison.OrdinalIgnoreCase))
if(files.Count > 1 && (Path.GetFileName(file) == "ocelot.json"))
{ {
continue; continue;
} }
var lines = File.ReadAllText(file); var lines = File.ReadAllText(file.FullName);
var config = JsonConvert.DeserializeObject<FileConfiguration>(lines); var config = JsonConvert.DeserializeObject<FileConfiguration>(lines);
// windows and unix sigh... if (file.Name.Equals(globalConfigFile, StringComparison.OrdinalIgnoreCase))
if (Path.GetFileName(file) == "ocelot.global.json")
{ {
fileConfiguration.GlobalConfiguration = config.GlobalConfiguration; fileConfiguration.GlobalConfiguration = config.GlobalConfiguration;
} }
@ -69,9 +75,9 @@ namespace Ocelot.DependencyInjection
var json = JsonConvert.SerializeObject(fileConfiguration); var json = JsonConvert.SerializeObject(fileConfiguration);
File.WriteAllText("ocelot.json", json); File.WriteAllText(primaryConfigFile, json);
builder.AddJsonFile("ocelot.json", false, false); builder.AddJsonFile(primaryConfigFile, false, false);
return builder; return builder;
} }

View File

@ -35,6 +35,8 @@ namespace Ocelot.DependencyInjection
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Middleware.Multiplexer; using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Creator; using Ocelot.Request.Creator;
using Ocelot.Security.IPSecurity;
using Ocelot.Security;
public class OcelotBuilder : IOcelotBuilder public class OcelotBuilder : IOcelotBuilder
{ {
@ -55,10 +57,16 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>(); Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>(); Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>(); Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
Services.AddSingleton<HostAndPortValidator>(); Services.TryAddSingleton<HostAndPortValidator>();
Services.AddSingleton<ReRouteFluentValidator>(); Services.TryAddSingleton<IReRoutesCreator, ReRoutesCreator>();
Services.AddSingleton<FileGlobalConfigurationFluentValidator>(); Services.TryAddSingleton<IAggregatesCreator, AggregatesCreator>();
Services.AddSingleton<FileQoSOptionsFluentValidator>(); Services.TryAddSingleton<IReRouteKeyCreator, ReRouteKeyCreator>();
Services.TryAddSingleton<IConfigurationCreator, ConfigurationCreator>();
Services.TryAddSingleton<IDynamicsCreator, DynamicsCreator>();
Services.TryAddSingleton<ILoadBalancerOptionsCreator, LoadBalancerOptionsCreator>();
Services.TryAddSingleton<ReRouteFluentValidator>();
Services.TryAddSingleton<FileGlobalConfigurationFluentValidator>();
Services.TryAddSingleton<FileQoSOptionsFluentValidator>();
Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>(); Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>(); Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>(); Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
@ -86,8 +94,8 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>(); Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>(); Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>(); Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>(); Services.TryAddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>(); Services.TryAddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();
Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>(); Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>(); Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
Services.TryAddSingleton<IHttpResponder, HttpContextResponder>(); Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
@ -108,7 +116,7 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<OcelotDiagnosticListener>(); Services.TryAddSingleton<OcelotDiagnosticListener>();
Services.TryAddSingleton<IMultiplexer, Multiplexer>(); Services.TryAddSingleton<IMultiplexer, Multiplexer>();
Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>(); Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
Services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>(); Services.TryAddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>(); Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>(); Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
Services.TryAddSingleton<IPlaceholders, Placeholders>(); Services.TryAddSingleton<IPlaceholders, Placeholders>();
@ -119,6 +127,9 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IQoSFactory, QoSFactory>(); Services.TryAddSingleton<IQoSFactory, QoSFactory>();
Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>(); Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>();
//add security
this.AddSecurity();
//add asp.net services.. //add asp.net services..
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
@ -147,6 +158,12 @@ namespace Ocelot.DependencyInjection
return this; return this;
} }
private void AddSecurity()
{
Services.TryAddSingleton<ISecurityOptionsCreator, SecurityOptionsCreator>();
Services.TryAddSingleton<ISecurityPolicy, IPSecurityPolicy>();
}
public IOcelotBuilder AddDelegatingHandler<THandler>(bool global = false) public IOcelotBuilder AddDelegatingHandler<THandler>(bool global = false)
where THandler : DelegatingHandler where THandler : DelegatingHandler
{ {

View File

@ -42,7 +42,7 @@
return downstreamRoute; return downstreamRoute;
} }
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new List<string> { upstreamHttpMethod });
var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build();

View File

@ -13,17 +13,14 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IDownstreamRouteProviderFactory _factory; private readonly IDownstreamRouteProviderFactory _factory;
private readonly IInternalConfigurationRepository _repo;
private readonly IMultiplexer _multiplexer; private readonly IMultiplexer _multiplexer;
public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IDownstreamRouteProviderFactory downstreamRouteFinder, IDownstreamRouteProviderFactory downstreamRouteFinder,
IInternalConfigurationRepository repo,
IMultiplexer multiplexer) IMultiplexer multiplexer)
:base(loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>()) :base(loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>())
{ {
_repo = repo;
_multiplexer = multiplexer; _multiplexer = multiplexer;
_next = next; _next = next;
_factory = downstreamRouteFinder; _factory = downstreamRouteFinder;
@ -51,7 +48,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
return; return;
} }
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamDownstreamPathTemplate.Value)); var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");

View File

@ -27,7 +27,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
var response = _replacer var response = _replacer
.Replace(context.DownstreamReRoute.DownstreamDownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); .Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
if (response.IsError) if (response.IsError)
{ {

View File

@ -8,11 +8,11 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
{ {
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
{ {
public Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues) public Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
{ {
var downstreamPath = new StringBuilder(); var downstreamPath = new StringBuilder();
downstreamPath.Append(downstreamDownstreamPathTemplate.Value); downstreamPath.Append(downstreamPathTemplate.Value);
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
{ {

View File

@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
{ {
public interface IDownstreamPathPlaceholderReplacer public interface IDownstreamPathPlaceholderReplacer
{ {
Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues); Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
} }
} }

View File

@ -1,22 +1,29 @@
using System.Collections.Generic; namespace Ocelot.Headers
using System.Linq;
using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses;
using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.Creator;
using Ocelot.Request.Middleware;
namespace Ocelot.Headers
{ {
using System.Collections.Generic;
using System.Linq;
using Infrastructure;
using Logging;
using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Ocelot.Configuration.Creator;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
public class AddHeadersToRequest : IAddHeadersToRequest public class AddHeadersToRequest : IAddHeadersToRequest
{ {
private readonly IClaimsParser _claimsParser; private readonly IClaimsParser _claimsParser;
private readonly IPlaceholders _placeholders;
private readonly IOcelotLogger _logger;
public AddHeadersToRequest(IClaimsParser claimsParser) public AddHeadersToRequest(IClaimsParser claimsParser, IPlaceholders placeholders, IOcelotLoggerFactory factory)
{ {
_logger = factory.CreateLogger<AddHeadersToRequest>();
_claimsParser = claimsParser; _claimsParser = claimsParser;
_placeholders = placeholders;
} }
public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest) public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest)
@ -46,6 +53,7 @@ namespace Ocelot.Headers
public void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context) public void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context)
{ {
var requestHeader = context.Request.Headers; var requestHeader = context.Request.Headers;
foreach (var header in headers) foreach (var header in headers)
{ {
if (requestHeader.ContainsKey(header.Key)) if (requestHeader.ContainsKey(header.Key))
@ -53,7 +61,22 @@ namespace Ocelot.Headers
requestHeader.Remove(header.Key); requestHeader.Remove(header.Key);
} }
requestHeader.Add(header.Key, header.Value); if (header.Value.StartsWith("{") && header.Value.EndsWith("}"))
{
var value = _placeholders.Get(header.Value);
if (value.IsError)
{
_logger.LogWarning($"Unable to add header to response {header.Key}: {header.Value}");
continue;
}
requestHeader.Add(header.Key, new StringValues(value.Data));
}
else
{
requestHeader.Add(header.Key, header.Value);
}
} }
} }
} }

View File

@ -1,15 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
using System.Collections.Generic;
using System.Linq;
using Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Middleware;
using Ocelot.Responses;
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
{ {
private readonly IPlaceholders _placeholders; private readonly IPlaceholders _placeholders;
@ -19,8 +17,11 @@ namespace Ocelot.Headers
_placeholders = placeholders; _placeholders = placeholders;
} }
public Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request) public Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs)
{ {
var response = context.DownstreamResponse;
var request = context.DownstreamRequest;
foreach (var f in fAndRs) foreach (var f in fAndRs)
{ {
var dict = response.Headers.ToDictionary(x => x.Key); var dict = response.Headers.ToDictionary(x => x.Key);

View File

@ -1,14 +1,12 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Middleware;
using Ocelot.Responses;
public interface IHttpResponseHeaderReplacer public interface IHttpResponseHeaderReplacer
{ {
Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest httpRequestMessage); Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs);
} }
} }

View File

@ -8,15 +8,15 @@ using Ocelot.Middleware;
namespace Ocelot.Headers.Middleware namespace Ocelot.Headers.Middleware
{ {
public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware public class ClaimsToHeadersMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IAddHeadersToRequest _addHeadersToRequest; private readonly IAddHeadersToRequest _addHeadersToRequest;
public HttpRequestHeadersBuilderMiddleware(OcelotRequestDelegate next, public ClaimsToHeadersMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IAddHeadersToRequest addHeadersToRequest) IAddHeadersToRequest addHeadersToRequest)
:base(loggerFactory.CreateLogger<HttpRequestHeadersBuilderMiddleware>()) :base(loggerFactory.CreateLogger<ClaimsToHeadersMiddleware>())
{ {
_next = next; _next = next;
_addHeadersToRequest = addHeadersToRequest; _addHeadersToRequest = addHeadersToRequest;
@ -26,7 +26,7 @@ namespace Ocelot.Headers.Middleware
{ {
if (context.DownstreamReRoute.ClaimsToHeaders.Any()) if (context.DownstreamReRoute.ClaimsToHeaders.Any())
{ {
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to headers"); 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); var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest);

View File

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

View File

@ -45,7 +45,7 @@ namespace Ocelot.Headers.Middleware
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); _postReplacer.Replace(context, postFAndRs);
_addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
} }

View File

@ -1,13 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Headers.Middleware
{
public static class HttpRequestHeadersBuilderMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseHttpRequestHeadersBuilderMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<HttpRequestHeadersBuilderMiddleware>();
}
}
}

View File

@ -1,47 +1,38 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Infrastructure namespace Ocelot.Infrastructure
{ {
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
public class Placeholders : IPlaceholders public class Placeholders : IPlaceholders
{ {
private readonly Dictionary<string, Func<Response<string>>> _placeholders; private readonly Dictionary<string, Func<Response<string>>> _placeholders;
private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders; private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders;
private readonly IBaseUrlFinder _finder; private readonly IBaseUrlFinder _finder;
private readonly IRequestScopedDataRepository _repo; private readonly IRequestScopedDataRepository _repo;
private readonly IHttpContextAccessor _httpContextAccessor;
public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo) public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo, IHttpContextAccessor httpContextAccessor)
{ {
_repo = repo; _repo = repo;
_httpContextAccessor = httpContextAccessor;
_finder = finder; _finder = finder;
_placeholders = new Dictionary<string, Func<Response<string>>>(); _placeholders = new Dictionary<string, Func<Response<string>>>
_placeholders.Add("{BaseUrl}", () => new OkResponse<string>(_finder.Find())); {
_placeholders.Add("{TraceId}", () => { { "{BaseUrl}", GetBaseUrl() },
var traceId = _repo.Get<string>("TraceId"); { "{TraceId}", GetTraceId() },
if(traceId.IsError) { "{RemoteIpAddress}", GetRemoteIpAddress() }
{
return new ErrorResponse<string>(traceId.Errors);
}
return new OkResponse<string>(traceId.Data); };
});
_requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>(); _requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>
_requestPlaceholders.Add("{DownstreamBaseUrl}", x => { {
var downstreamUrl = $"{x.Scheme}://{x.Host}"; { "{DownstreamBaseUrl}", GetDownstreamBaseUrl() }
};
if(x.Port != 80 && x.Port != 443)
{
downstreamUrl = $"{downstreamUrl}:{x.Port}";
}
return $"{downstreamUrl}/";
});
} }
public Response<string> Get(string key) public Response<string> Get(string key)
@ -67,5 +58,56 @@ namespace Ocelot.Infrastructure
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key)); return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
} }
private Func<Response<string>> GetRemoteIpAddress()
{
return () =>
{
// this can blow up so adding try catch and return error
try
{
var remoteIdAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
return new OkResponse<string>(remoteIdAddress);
}
catch (Exception e)
{
return new ErrorResponse<string>(new CouldNotFindPlaceholderError("{RemoteIpAddress}"));
}
};
}
private Func<DownstreamRequest, string> GetDownstreamBaseUrl()
{
return x =>
{
var downstreamUrl = $"{x.Scheme}://{x.Host}";
if (x.Port != 80 && x.Port != 443)
{
downstreamUrl = $"{downstreamUrl}:{x.Port}";
}
return $"{downstreamUrl}/";
};
}
private Func<Response<string>> GetTraceId()
{
return () =>
{
var traceId = _repo.Get<string>("TraceId");
if (traceId.IsError)
{
return new ErrorResponse<string>(traceId.Errors);
}
return new OkResponse<string>(traceId.Data);
};
}
private Func<Response<string>> GetBaseUrl()
{
return () => new OkResponse<string>(_finder.Find());
}
} }
} }

View File

@ -7,25 +7,27 @@ namespace Ocelot.Middleware
{ {
public class DownstreamResponse public class DownstreamResponse
{ {
public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, List<Header> headers) public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, List<Header> headers, string reasonPhrase)
{ {
Content = content; Content = content;
StatusCode = statusCode; StatusCode = statusCode;
Headers = headers ?? new List<Header>(); Headers = headers ?? new List<Header>();
ReasonPhrase = reasonPhrase;
} }
public DownstreamResponse(HttpResponseMessage response) public DownstreamResponse(HttpResponseMessage response)
:this(response.Content, response.StatusCode, response.Headers.Select(x => new Header(x.Key, x.Value)).ToList()) :this(response.Content, response.StatusCode, response.Headers.Select(x => new Header(x.Key, x.Value)).ToList(), response.ReasonPhrase)
{ {
} }
public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers) public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers, string reasonPhrase)
:this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList()) :this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList(), reasonPhrase)
{ {
} }
public HttpContent Content { get; } public HttpContent Content { get; }
public HttpStatusCode StatusCode { get; } public HttpStatusCode StatusCode { get; }
public List<Header> Headers { get; } public List<Header> Headers { get; }
public string ReasonPhrase {get;}
} }
} }

View File

@ -46,7 +46,7 @@ namespace Ocelot.Middleware.Multiplexer
Headers = {ContentType = new MediaTypeHeaderValue("application/json")} Headers = {ContentType = new MediaTypeHeaderValue("application/json")}
}; };
originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()); originalContext.DownstreamResponse = 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, List<DownstreamContext> downstreamContexts, int i) private static void MapAggregateError(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts, int i)

View File

@ -15,6 +15,7 @@ using Ocelot.Request.Middleware;
using Ocelot.Requester.Middleware; using Ocelot.Requester.Middleware;
using Ocelot.RequestId.Middleware; using Ocelot.RequestId.Middleware;
using Ocelot.Responder.Middleware; using Ocelot.Responder.Middleware;
using Ocelot.Security.Middleware;
using Ocelot.WebSockets.Middleware; using Ocelot.WebSockets.Middleware;
namespace Ocelot.Middleware.Pipeline namespace Ocelot.Middleware.Pipeline
@ -48,6 +49,9 @@ namespace Ocelot.Middleware.Pipeline
// Then we get the downstream route information // Then we get the downstream route information
builder.UseDownstreamRouteFinderMiddleware(); builder.UseDownstreamRouteFinderMiddleware();
// This security module, IP whitelist blacklist, extended security mechanism
builder.UseSecurityMiddleware();
//Expand other branch pipes //Expand other branch pipes
if (pipelineConfiguration.MapWhenOcelotPipeline != null) if (pipelineConfiguration.MapWhenOcelotPipeline != null)
{ {
@ -87,7 +91,7 @@ namespace Ocelot.Middleware.Pipeline
} }
// The next thing we do is look at any claims transforms in case this is important for authorisation // The next thing we do is look at any claims transforms in case this is important for authorisation
builder.UseClaimsBuilderMiddleware(); builder.UseClaimsToClaimsMiddleware();
// Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in.
builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware); builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);
@ -105,14 +109,14 @@ namespace Ocelot.Middleware.Pipeline
builder.Use(pipelineConfiguration.AuthorisationMiddleware); builder.Use(pipelineConfiguration.AuthorisationMiddleware);
} }
// Now we can run any header transformation logic // Now we can run the claims to headers transformation middleware
builder.UseHttpRequestHeadersBuilderMiddleware(); builder.UseClaimsToHeadersMiddleware();
// Allow the user to implement their own query string manipulation logic // Allow the user to implement their own query string manipulation logic
builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware); builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);
// Now we can run any query string transformation logic // Now we can run any claims to query string transformation middleware
builder.UseQueryStringBuilderMiddleware(); builder.UseClaimsToQueryStringMiddleware();
// Get the load balancer for this request // Get the load balancer for this request
builder.UseLoadBalancingMiddleware(); builder.UseLoadBalancingMiddleware();

View File

@ -1,22 +1,22 @@
using System.Linq; namespace Ocelot.QueryStrings.Middleware
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
namespace Ocelot.QueryStrings.Middleware
{ {
public class QueryStringBuilderMiddleware : OcelotMiddleware using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
public class ClaimsToQueryStringMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IAddQueriesToRequest _addQueriesToRequest; private readonly IAddQueriesToRequest _addQueriesToRequest;
public QueryStringBuilderMiddleware(OcelotRequestDelegate next, public ClaimsToQueryStringMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IAddQueriesToRequest addQueriesToRequest) IAddQueriesToRequest addQueriesToRequest)
: base(loggerFactory.CreateLogger<QueryStringBuilderMiddleware>()) : base(loggerFactory.CreateLogger<ClaimsToQueryStringMiddleware>())
{ {
_next = next; _next = next;
_addQueriesToRequest = addQueriesToRequest; _addQueriesToRequest = addQueriesToRequest;
@ -26,7 +26,7 @@ namespace Ocelot.QueryStrings.Middleware
{ {
if (context.DownstreamReRoute.ClaimsToQueries.Any()) if (context.DownstreamReRoute.ClaimsToQueries.Any())
{ {
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to queries"); 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); var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest);

View File

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

View File

@ -1,13 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.QueryStrings.Middleware
{
public static class QueryStringBuilderMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseQueryStringBuilderMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<QueryStringBuilderMiddleware>();
}
}
}

View File

@ -34,7 +34,7 @@ namespace Ocelot.RateLimit.Middleware
// check if rate limiting is enabled // check if rate limiting is enabled
if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting) if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting)
{ {
Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value}"); Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}");
await _next.Invoke(context); await _next.Invoke(context);
return; return;
} }
@ -45,7 +45,7 @@ namespace Ocelot.RateLimit.Middleware
// check white list // check white list
if (IsWhitelisted(identity, options)) if (IsWhitelisted(identity, options))
{ {
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} is white listed from rate limiting"); Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting");
await _next.Invoke(context); await _next.Invoke(context);
return; return;
} }

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Ocelot.RateLimit namespace Ocelot.RateLimit
{ {
@ -10,10 +11,11 @@ namespace Ocelot.RateLimit
/// </summary> /// </summary>
public struct RateLimitCounter public struct RateLimitCounter
{ {
public RateLimitCounter(DateTime timestamp, long totalRequest) [JsonConstructor]
public RateLimitCounter(DateTime timestamp, long totalRequests)
{ {
Timestamp = timestamp; Timestamp = timestamp;
TotalRequests = totalRequest; TotalRequests = totalRequests;
} }
public DateTime Timestamp { get; private set; } public DateTime Timestamp { get; private set; }

View File

@ -51,7 +51,7 @@ namespace Ocelot.Requester
handler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true; handler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
_logger _logger
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamDownstreamPathTemplate: {context.DownstreamReRoute.DownstreamDownstreamPathTemplate}"); .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}");
} }
var timeout = context.DownstreamReRoute.QosOptions.TimeoutValue == 0 var timeout = context.DownstreamReRoute.QosOptions.TimeoutValue == 0

View File

@ -27,7 +27,7 @@ namespace Ocelot.Requester.QoS
return new OkResponse<DelegatingHandler>(handler(request, _ocelotLoggerFactory)); return new OkResponse<DelegatingHandler>(handler(request, _ocelotLoggerFactory));
} }
return new ErrorResponse<DelegatingHandler>(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamDownstreamPathTemplate}")); return new ErrorResponse<DelegatingHandler>(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamPathTemplate}"));
} }
} }
} }

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Middleware; using Ocelot.Middleware;
@ -43,14 +44,9 @@ namespace Ocelot.Responder
AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) ); AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) );
} }
context.Response.OnStarting(state => SetStatusCode(context, (int)response.StatusCode);
{
var httpContext = (HttpContext)state;
httpContext.Response.StatusCode = (int)response.StatusCode; context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = response.ReasonPhrase;
return Task.CompletedTask;
}, context);
using(content) using(content)
{ {
@ -63,7 +59,15 @@ namespace Ocelot.Responder
public void SetErrorResponseOnContext(HttpContext context, int statusCode) public void SetErrorResponseOnContext(HttpContext context, int statusCode)
{ {
context.Response.StatusCode = statusCode; SetStatusCode(context, statusCode);
}
private void SetStatusCode(HttpContext context, int statusCode)
{
if (!context.Response.HasStarted)
{
context.Response.StatusCode = statusCode;
}
} }
private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader)

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Middleware;
using Ocelot.Responses;
namespace Ocelot.Security.IPSecurity
{
public class IPSecurityPolicy : ISecurityPolicy
{
public async Task<Response> Security(DownstreamContext context)
{
IPAddress clientIp = context.HttpContext.Connection.RemoteIpAddress;
SecurityOptions securityOptions = context.DownstreamReRoute.SecurityOptions;
if (securityOptions == null)
{
return new OkResponse();
}
if (securityOptions.IPBlockedList != null)
{
if (securityOptions.IPBlockedList.Exists(f => f == clientIp.ToString()))
{
var error = new UnauthenticatedError($" This request rejects access to {clientIp.ToString()} IP");
return new ErrorResponse(error);
}
}
if (securityOptions.IPAllowedList != null && securityOptions.IPAllowedList.Count > 0)
{
if (!securityOptions.IPAllowedList.Exists(f => f == clientIp.ToString()))
{
var error = new UnauthenticatedError($"{clientIp.ToString()} does not allow access, the request is invalid");
return new ErrorResponse(error);
}
}
return await Task.FromResult(new OkResponse());
}
}
}

View File

@ -0,0 +1,14 @@
using Ocelot.Middleware;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Ocelot.Security
{
public interface ISecurityPolicy
{
Task<Response> Security(DownstreamContext context);
}
}

View File

@ -0,0 +1,43 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Security.Middleware
{
public class SecurityMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IOcelotLogger _logger;
private readonly IEnumerable<ISecurityPolicy> _securityPolicies;
public SecurityMiddleware(IOcelotLoggerFactory loggerFactory,
IEnumerable<ISecurityPolicy> securityPolicies,
OcelotRequestDelegate next)
: base(loggerFactory.CreateLogger<SecurityMiddleware>())
{
_logger = loggerFactory.CreateLogger<SecurityMiddleware>();
_securityPolicies = securityPolicies;
_next = next;
}
public async Task Invoke(DownstreamContext context)
{
if (_securityPolicies != null)
{
foreach (var policie in _securityPolicies)
{
var result = await policie.Security(context);
if (!result.IsError)
{
continue;
}
this.SetPipelineError(context, result.Errors);
return;
}
}
await _next.Invoke(context);
}
}
}

View File

@ -0,0 +1,15 @@
using Ocelot.Middleware.Pipeline;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Security.Middleware
{
public static class SecurityMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseSecurityMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<SecurityMiddleware>();
}
}
}

View File

@ -1,4 +1,9 @@
// 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.
// Modified https://github.com/aspnet/Proxy websockets class to use in Ocelot.
using System; using System;
using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,7 +15,10 @@ namespace Ocelot.WebSockets.Middleware
{ {
public class WebSocketsProxyMiddleware : OcelotMiddleware public class WebSocketsProxyMiddleware : OcelotMiddleware
{ {
private OcelotRequestDelegate _next; private static readonly string[] NotForwardedWebSocketHeaders = new[] { "Connection", "Host", "Upgrade", "Sec-WebSocket-Accept", "Sec-WebSocket-Protocol", "Sec-WebSocket-Key", "Sec-WebSocket-Version", "Sec-WebSocket-Extensions" };
private const int DefaultWebSocketBufferSize = 4096;
private const int StreamCopyBufferSize = 81920;
private readonly OcelotRequestDelegate _next;
public WebSocketsProxyMiddleware(OcelotRequestDelegate next, public WebSocketsProxyMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory) IOcelotLoggerFactory loggerFactory)
@ -19,6 +27,37 @@ namespace Ocelot.WebSockets.Middleware
_next = next; _next = next;
} }
private static async Task PumpWebSocket(WebSocket source, WebSocket destination, int bufferSize, CancellationToken cancellationToken)
{
if (bufferSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize));
}
var buffer = new byte[bufferSize];
while (true)
{
WebSocketReceiveResult result;
try
{
result = await source.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
}
catch (OperationCanceledException)
{
await destination.CloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, null, cancellationToken);
return;
}
if (result.MessageType == WebSocketMessageType.Close)
{
await destination.CloseOutputAsync(source.CloseStatus.Value, source.CloseStatusDescription, cancellationToken);
return;
}
await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
}
}
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
await Proxy(context.HttpContext, context.DownstreamRequest.ToUri()); await Proxy(context.HttpContext, context.DownstreamRequest.ToUri());
@ -26,88 +65,42 @@ namespace Ocelot.WebSockets.Middleware
private async Task Proxy(HttpContext context, string serverEndpoint) private async Task Proxy(HttpContext context, string serverEndpoint)
{ {
var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync(); if (context == null)
var wsToDownstreamService = new ClientWebSocket();
foreach (var requestHeader in context.Request.Headers)
{ {
// Do not copy the Sec-Websocket headers because it is specified by the own connection it will fail when you copy this one. throw new ArgumentNullException(nameof(context));
if (requestHeader.Key.StartsWith("Sec-WebSocket"))
{
continue;
}
wsToDownstreamService.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
} }
var uri = new Uri(serverEndpoint); if (serverEndpoint == null)
await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None);
var receiveFromUpstreamSendToDownstream = Task.Run(async () =>
{ {
var buffer = new byte[1024 * 4]; throw new ArgumentNullException(nameof(serverEndpoint));
}
var receiveSegment = new ArraySegment<byte>(buffer); if (!context.WebSockets.IsWebSocketRequest)
while (wsToUpstreamClient.State == WebSocketState.Open || wsToUpstreamClient.State == WebSocketState.CloseSent)
{
var result = await wsToUpstreamClient.ReceiveAsync(receiveSegment, CancellationToken.None);
var sendSegment = new ArraySegment<byte>(buffer, 0, result.Count);
if(result.MessageType == WebSocketMessageType.Close)
{
await wsToUpstreamClient.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "",
CancellationToken.None);
await wsToDownstreamService.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "",
CancellationToken.None);
break;
}
await wsToDownstreamService.SendAsync(sendSegment, result.MessageType, result.EndOfMessage,
CancellationToken.None);
if (wsToUpstreamClient.State != WebSocketState.Open)
{
await wsToDownstreamService.CloseAsync(WebSocketCloseStatus.Empty, "",
CancellationToken.None);
break;
}
}
});
var receiveFromDownstreamAndSendToUpstream = Task.Run(async () =>
{ {
var buffer = new byte[1024 * 4]; throw new InvalidOperationException();
}
while (wsToDownstreamService.State == WebSocketState.Open || wsToDownstreamService.State == WebSocketState.CloseSent) var client = new ClientWebSocket();
foreach (var protocol in context.WebSockets.WebSocketRequestedProtocols)
{
client.Options.AddSubProtocol(protocol);
}
foreach (var headerEntry in context.Request.Headers)
{
if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase))
{ {
if (wsToUpstreamClient.State != WebSocketState.Open) client.Options.SetRequestHeader(headerEntry.Key, headerEntry.Value);
{
break;
}
else
{
var receiveSegment = new ArraySegment<byte>(buffer);
var result = await wsToDownstreamService.ReceiveAsync(receiveSegment, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
var sendSegment = new ArraySegment<byte>(buffer, 0, result.Count);
//send to upstream client
await wsToUpstreamClient.SendAsync(sendSegment, result.MessageType, result.EndOfMessage,
CancellationToken.None);
}
} }
}); }
await Task.WhenAll(receiveFromDownstreamAndSendToUpstream, receiveFromUpstreamSendToDownstream); var destinationUri = new Uri(serverEndpoint);
await client.ConnectAsync(destinationUri, context.RequestAborted);
using (var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol))
{
var bufferSize = DefaultWebSocketBufferSize;
await Task.WhenAll(PumpWebSocket(client, server, bufferSize, context.RequestAborted), PumpWebSocket(server, client, bufferSize, context.RequestAborted));
}
} }
} }
} }

View File

@ -31,6 +31,116 @@ namespace Ocelot.AcceptanceTests
_steps = new Steps(); _steps = new Steps();
} }
[Fact]
public void should_fix_issue_597()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/values?MailId={userid}",
UpstreamPathTemplate = "/key1data/{userid}",
UpstreamHttpMethod = new List<string> {"Get"},
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 8571
}
},
Key = "key1"
},
new FileReRoute
{
DownstreamPathTemplate = "/api/values?MailId={userid}",
UpstreamPathTemplate = "/key2data/{userid}",
UpstreamHttpMethod = new List<string> {"Get"},
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 8571
}
},
Key = "key2"
},
new FileReRoute
{
DownstreamPathTemplate = "/api/values?MailId={userid}",
UpstreamPathTemplate = "/key3data/{userid}",
UpstreamHttpMethod = new List<string> {"Get"},
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 8571
}
},
Key = "key3"
},
new FileReRoute
{
DownstreamPathTemplate = "/api/values?MailId={userid}",
UpstreamPathTemplate = "/key4data/{userid}",
UpstreamHttpMethod = new List<string> {"Get"},
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 8571
}
},
Key = "key4"
},
},
Aggregates = new List<FileAggregateReRoute>
{
new FileAggregateReRoute
{
ReRouteKeys = new List<string>{
"key1",
"key2",
"key3",
"key4"
},
UpstreamPathTemplate = "/EmpDetail/IN/{userid}"
},
new FileAggregateReRoute
{
ReRouteKeys = new List<string>{
"key1",
"key2",
},
UpstreamPathTemplate = "/EmpDetail/US/{userid}"
}
},
GlobalConfiguration = new FileGlobalConfiguration
{
RequestIdKey = "CorrelationID"
}
};
var expected = "{\"key1\":some_data,\"key2\":some_data}";
this.Given(x => x.GivenServiceIsRunning("http://localhost:8571", 200, "some_data"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/EmpDetail/US/1"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe(expected))
.BDDfy();
}
[Fact] [Fact]
public void should_return_response_200_with_simple_url_user_defined_aggregate() public void should_return_response_200_with_simple_url_user_defined_aggregate()
{ {
@ -71,7 +181,7 @@ namespace Ocelot.AcceptanceTests
Key = "Tom" Key = "Tom"
} }
}, },
Aggregates = new List<FileAggregateReRoute> Aggregates = new List<FileAggregateReRoute>
{ {
new FileAggregateReRoute new FileAggregateReRoute
{ {
@ -140,7 +250,7 @@ namespace Ocelot.AcceptanceTests
Key = "Tom" Key = "Tom"
} }
}, },
Aggregates = new List<FileAggregateReRoute> Aggregates = new List<FileAggregateReRoute>
{ {
new FileAggregateReRoute new FileAggregateReRoute
{ {
@ -368,6 +478,15 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
private void GivenServiceIsRunning(string baseUrl, int statusCode, string responseBody)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context =>
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
});
}
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
@ -440,7 +559,7 @@ namespace Ocelot.AcceptanceTests
var merge = $"{one}, {two}"; var merge = $"{one}, {two}";
merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", "");
var headers = responses.SelectMany(x => x.Headers).ToList(); var headers = responses.SelectMany(x => x.Headers).ToList();
return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers); return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason");
} }
} }
} }

View File

@ -4,6 +4,7 @@ namespace Ocelot.AcceptanceTests
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;

View File

@ -0,0 +1,76 @@
namespace Ocelot.AcceptanceTests
{
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class ReasonPhraseTests : IDisposable
{
private readonly Steps _steps;
private string _contentType;
private long? _contentLength;
private bool _contentTypeHeaderExists;
private readonly ServiceHandler _serviceHandler;
public ReasonPhraseTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
}
[Fact]
public void should_return_reason_phrase()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51339,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51339", "/", "some reason"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(_ => _steps.ThenTheReasonPhraseIs("some reason"))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string reasonPhrase)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = reasonPhrase;
await context.Response.WriteAsync("YOYO!");
});
}
public void Dispose()
{
_serviceHandler?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -452,6 +452,11 @@
header.First().ShouldBe(value); header.First().ShouldBe(value);
} }
public void ThenTheReasonPhraseIs(string expected)
{
_response.ReasonPhrase.ShouldBe(expected);
}
/// <summary> /// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary> /// </summary>

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Middleware;
using Ocelot.DependencyInjection;
using System.Net.Http;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes.Jobs;
using Ocelot.Configuration.Repository;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Errors.Middleware;
using Microsoft.Extensions.DependencyInjection;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Validators;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Configuration;
namespace Ocelot.Benchmarks
{
[SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 5)]
[Config(typeof(DownstreamRouteFinderMiddlewareBenchmarks))]
public class DownstreamRouteFinderMiddlewareBenchmarks : ManualConfig
{
private DownstreamRouteFinderMiddleware _middleware;
private DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next;
public DownstreamRouteFinderMiddlewareBenchmarks()
{
Add(StatisticColumn.AllStatistics);
Add(MemoryDiagnoser.Default);
Add(BaselineValidator.FailOnError);
}
[GlobalSetup]
public void SetUp()
{
var serviceCollection = new ServiceCollection();
var config = new ConfigurationRoot(new List<IConfigurationProvider>());
var builder = new OcelotBuilder(serviceCollection, config);
var services = serviceCollection.BuildServiceProvider();
var loggerFactory = services.GetService<IOcelotLoggerFactory>();
var drpf = services.GetService<IDownstreamRouteProviderFactory>();
var multiplexer = services.GetService<IMultiplexer>();
_next = async context => {
await Task.CompletedTask;
throw new Exception("BOOM");
};
_middleware = new DownstreamRouteFinderMiddleware(_next, loggerFactory, drpf, multiplexer);
var httpContext = new DefaultHttpContext();
httpContext.Request.Path = new PathString("/test");
httpContext.Request.QueryString = new QueryString("?a=b");
httpContext.Request.Headers.Add("Host", "most");
_downstreamContext = new DownstreamContext(httpContext)
{
Configuration = new InternalConfiguration(new List<ReRoute>(), null, null, null, null, null, null, null)
};
}
[Benchmark(Baseline = true)]
public async Task Baseline()
{
await _middleware.Invoke(_downstreamContext);
}
}
}

View File

@ -10,7 +10,8 @@ namespace Ocelot.Benchmarks
typeof(DictionaryBenchmarks), typeof(DictionaryBenchmarks),
typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), typeof(UrlPathToUrlPathTemplateMatcherBenchmarks),
typeof(AllTheThingsBenchmarks), typeof(AllTheThingsBenchmarks),
typeof(ExceptionHandlerMiddlewareBenchmarks) typeof(ExceptionHandlerMiddlewareBenchmarks),
typeof(DownstreamRouteFinderMiddlewareBenchmarks)
}); });
switcher.Run(args); switcher.Run(args);

View File

@ -0,0 +1,199 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Ocelot.IntegrationTests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using System.Linq;
using System.Net;
public class HeaderTests : IDisposable
{
private readonly HttpClient _httpClient;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private IWebHost _downstreamBuilder;
private HttpResponseMessage _response;
public HeaderTests()
{
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5005";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
}
[Fact]
public void should_pass_remote_ip_address_if_as_x_forwarded_for_header()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 6773,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHeaderTransform = new Dictionary<string,string>
{
{"X-Forwarded-For", "{RemoteIpAddress}"}
},
HttpHandlerOptions = new FileHttpHandlerOptions
{
AllowAutoRedirect = false
}
}
}
};
this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:6773", 200, "X-Forwarded-For"))
.And(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenXForwardedForIsSet())
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string headerKey)
{
_downstreamBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if (context.Request.Headers.TryGetValue(headerKey, out var values))
{
var result = values.First();
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(result);
}
});
})
.Build();
_downstreamBuilder.Start();
}
private void GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
config.AddJsonFile("ocelot.json", false, false);
config.AddEnvironmentVariables();
})
.ConfigureServices(x =>
{
x.AddOcelot();
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
_builder = _webHostBuilder.Build();
_builder.Start();
}
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{
var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
var text = File.ReadAllText(configurationPath);
configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
text = File.ReadAllText(configurationPath);
}
public async Task WhenIGetUrlOnTheApiGateway(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
_response = await _httpClient.SendAsync(request);
}
private void ThenTheStatusCodeShouldBe(HttpStatusCode code)
{
_response.StatusCode.ShouldBe(code);
}
private void ThenXForwardedForIsSet()
{
var windowsOrMac = "::1";
var linux = "127.0.0.1";
var header = _response.Content.ReadAsStringAsync().Result;
bool passed = false;
if(header == windowsOrMac || header == linux)
{
passed = true;
}
passed.ShouldBeTrue();
}
public void Dispose()
{
_builder?.Dispose();
_httpClient?.Dispose();
_downstreamBuilder?.Dispose();
}
}
}

View File

@ -46,6 +46,20 @@ namespace Ocelot.UnitTests.Authentication
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_call_next_middleware_if_route_is_using_options_method()
{
this.Given(x => GivenTheDownStreamRouteIs(
new DownstreamReRouteBuilder()
.WithUpstreamHttpMethod(new List<string> { "Options" })
.WithIsAuthenticated(true)
.Build()))
.And(x => GivenTheRequestIsUsingOptionsMethod())
.When(x => WhenICallTheMiddleware())
.Then(x => ThenTheUserIsAuthenticated())
.BDDfy();
}
private void WhenICallTheMiddleware() private void WhenICallTheMiddleware()
{ {
_next = (context) => { _next = (context) => {
@ -68,6 +82,11 @@ namespace Ocelot.UnitTests.Authentication
}; };
} }
private void GivenTheRequestIsUsingOptionsMethod()
{
_downstreamContext.HttpContext.Request.Method = "OPTIONS";
}
private void ThenTheUserIsAuthenticated() private void ThenTheUserIsAuthenticated()
{ {
var content = _downstreamContext.HttpContext.Response.Body.AsString(); var content = _downstreamContext.HttpContext.Response.Body.AsString();
@ -84,7 +103,7 @@ namespace Ocelot.UnitTests.Authentication
{ {
public static string AsString(this Stream stream) public static string AsString(this Stream stream)
{ {
using(var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))
{ {
string text = reader.ReadToEnd(); string text = reader.ReadToEnd();
return text; return text;

View File

@ -53,7 +53,7 @@
{ "content-type", new List<string> { "application/json" } } { "content-type", new List<string> { "application/json" } }
}; };
var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders); var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders, "some reason");
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
@ -69,7 +69,7 @@
{ "Expires", new List<string> { "-1" } } { "Expires", new List<string> { "-1" } }
}; };
var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary<string, IEnumerable<string>>(), "", contentHeaders); var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary<string, IEnumerable<string>>(), "", contentHeaders, "some reason");
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())

View File

@ -17,24 +17,24 @@ namespace Ocelot.UnitTests.Claims
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class ClaimsBuilderMiddlewareTests public class ClaimsToClaimsMiddlewareTests
{ {
private readonly Mock<IAddClaimsToRequest> _addHeaders; private readonly Mock<IAddClaimsToRequest> _addHeaders;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger; private Mock<IOcelotLogger> _logger;
private readonly ClaimsBuilderMiddleware _middleware; private readonly ClaimsToClaimsMiddleware _middleware;
private readonly DownstreamContext _downstreamContext; private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
public ClaimsBuilderMiddlewareTests() public ClaimsToClaimsMiddlewareTests()
{ {
_addHeaders = new Mock<IAddClaimsToRequest>(); _addHeaders = new Mock<IAddClaimsToRequest>();
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<ClaimsBuilderMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<ClaimsToClaimsMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_middleware = new ClaimsBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); _middleware = new ClaimsToClaimsMiddleware(_next, _loggerFactory.Object, _addHeaders.Object);
} }
[Fact] [Fact]

View File

@ -0,0 +1,164 @@
namespace Ocelot.UnitTests.Configuration
{
using System.Collections.Generic;
using System.Net.Http;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class AggregatesCreatorTests
{
private readonly AggregatesCreator _creator;
private readonly Mock<IUpstreamTemplatePatternCreator> _utpCreator;
private FileConfiguration _fileConfiguration;
private List<ReRoute> _reRoutes;
private List<ReRoute> _result;
private UpstreamPathTemplate _aggregate1Utp;
private UpstreamPathTemplate _aggregate2Utp;
public AggregatesCreatorTests()
{
_utpCreator = new Mock<IUpstreamTemplatePatternCreator>();
_creator = new AggregatesCreator(_utpCreator.Object);
}
[Fact]
public void should_return_no_aggregates()
{
var fileConfig = new FileConfiguration
{
Aggregates = new List<FileAggregateReRoute>
{
new FileAggregateReRoute
{
ReRouteKeys = new List<string>{"key1"}
}
}
};
var reRoutes = new List<ReRoute>();
this.Given(_ => GivenThe(fileConfig))
.And(_ => GivenThe(reRoutes))
.When(_ => WhenICreate())
.Then(_ => TheUtpCreatorIsNotCalled())
.And(_ => ThenTheResultIsNotNull())
.And(_ => ThenTheResultIsEmpty())
.BDDfy();
}
[Fact]
public void should_create_aggregates()
{
var fileConfig = new FileConfiguration
{
Aggregates = new List<FileAggregateReRoute>
{
new FileAggregateReRoute
{
ReRouteKeys = new List<string>{"key1", "key2"},
UpstreamHost = "hosty",
UpstreamPathTemplate = "templatey",
Aggregator = "aggregatory",
ReRouteIsCaseSensitive = true
},
new FileAggregateReRoute
{
ReRouteKeys = new List<string>{"key3", "key4"},
UpstreamHost = "hosty",
UpstreamPathTemplate = "templatey",
Aggregator = "aggregatory",
ReRouteIsCaseSensitive = true
}
}
};
var reRoutes = new List<ReRoute>
{
new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key1").Build()).Build(),
new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key2").Build()).Build(),
new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key3").Build()).Build(),
new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key4").Build()).Build()
};
this.Given(_ => GivenThe(fileConfig))
.And(_ => GivenThe(reRoutes))
.And(_ => GivenTheUtpCreatorReturns())
.When(_ => WhenICreate())
.Then(_ => ThenTheUtpCreatorIsCalledCorrectly())
.And(_ => ThenTheAggregatesAreCreated())
.BDDfy();
}
private void ThenTheAggregatesAreCreated()
{
_result.ShouldNotBeNull();
_result.Count.ShouldBe(2);
_result[0].UpstreamHttpMethod.ShouldContain(x => x == HttpMethod.Get);
_result[0].UpstreamHost.ShouldBe(_fileConfiguration.Aggregates[0].UpstreamHost);
_result[0].UpstreamTemplatePattern.ShouldBe(_aggregate1Utp);
_result[0].Aggregator.ShouldBe(_fileConfiguration.Aggregates[0].Aggregator);
_result[0].DownstreamReRoute.ShouldContain(x => x == _reRoutes[0].DownstreamReRoute[0]);
_result[0].DownstreamReRoute.ShouldContain(x => x == _reRoutes[1].DownstreamReRoute[0]);
_result[1].UpstreamHttpMethod.ShouldContain(x => x == HttpMethod.Get);
_result[1].UpstreamHost.ShouldBe(_fileConfiguration.Aggregates[1].UpstreamHost);
_result[1].UpstreamTemplatePattern.ShouldBe(_aggregate2Utp);
_result[1].Aggregator.ShouldBe(_fileConfiguration.Aggregates[1].Aggregator);
_result[1].DownstreamReRoute.ShouldContain(x => x == _reRoutes[2].DownstreamReRoute[0]);
_result[1].DownstreamReRoute.ShouldContain(x => x == _reRoutes[3].DownstreamReRoute[0]);
}
private void ThenTheUtpCreatorIsCalledCorrectly()
{
_utpCreator.Verify(x => x.Create(_fileConfiguration.Aggregates[0]), Times.Once);
_utpCreator.Verify(x => x.Create(_fileConfiguration.Aggregates[1]), Times.Once);
}
private void GivenTheUtpCreatorReturns()
{
_aggregate1Utp = new UpstreamPathTemplateBuilder().Build();
_aggregate2Utp = new UpstreamPathTemplateBuilder().Build();
_utpCreator.SetupSequence(x => x.Create(It.IsAny<IReRoute>()))
.Returns(_aggregate1Utp)
.Returns(_aggregate2Utp);
}
private void ThenTheResultIsEmpty()
{
_result.Count.ShouldBe(0);
}
private void ThenTheResultIsNotNull()
{
_result.ShouldNotBeNull();
}
private void TheUtpCreatorIsNotCalled()
{
_utpCreator.Verify(x => x.Create(It.IsAny<FileAggregateReRoute>()), Times.Never);
}
private void GivenThe(FileConfiguration fileConfiguration)
{
_fileConfiguration = fileConfiguration;
}
private void GivenThe(List<ReRoute> reRoutes)
{
_reRoutes = reRoutes;
}
private void WhenICreate()
{
_result = _creator.Create(_fileConfiguration, _reRoutes);
}
}
}

View File

@ -0,0 +1,124 @@
namespace Ocelot.UnitTests.Configuration
{
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.DependencyInjection;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class ConfigurationCreatorTests
{
private ConfigurationCreator _creator;
private InternalConfiguration _result;
private readonly Mock<IServiceProviderConfigurationCreator> _spcCreator;
private readonly Mock<IQoSOptionsCreator> _qosCreator;
private readonly Mock<IHttpHandlerOptionsCreator> _hhoCreator;
private readonly Mock<ILoadBalancerOptionsCreator> _lboCreator;
private FileConfiguration _fileConfig;
private List<ReRoute> _reRoutes;
private ServiceProviderConfiguration _spc;
private LoadBalancerOptions _lbo;
private QoSOptions _qoso;
private HttpHandlerOptions _hho;
private AdministrationPath _adminPath;
private readonly ServiceCollection _serviceCollection;
public ConfigurationCreatorTests()
{
_lboCreator = new Mock<ILoadBalancerOptionsCreator>();
_hhoCreator = new Mock<IHttpHandlerOptionsCreator>();
_qosCreator = new Mock<IQoSOptionsCreator>();
_spcCreator = new Mock<IServiceProviderConfigurationCreator>();
_serviceCollection = new ServiceCollection();
}
[Fact]
public void should_build_configuration_with_no_admin_path()
{
this.Given(_ => GivenTheDependenciesAreSetUp())
.When(_ => WhenICreate())
.Then(_ => ThenTheDepdenciesAreCalledCorrectly())
.And(_ => ThenThePropertiesAreSetCorrectly())
.And(_ => ThenTheAdminPathIsNull())
.BDDfy();
}
[Fact]
public void should_build_configuration_with_admin_path()
{
this.Given(_ => GivenTheDependenciesAreSetUp())
.And(_ => GivenTheAdminPath())
.When(_ => WhenICreate())
.Then(_ => ThenTheDepdenciesAreCalledCorrectly())
.And(_ => ThenThePropertiesAreSetCorrectly())
.And(_ => ThenTheAdminPathIsSet())
.BDDfy();
}
private void ThenTheAdminPathIsNull()
{
_result.AdministrationPath.ShouldBeNull();
}
private void ThenThePropertiesAreSetCorrectly()
{
_result.ShouldNotBeNull();
_result.ServiceProviderConfiguration.ShouldBe(_spc);
_result.LoadBalancerOptions.ShouldBe(_lbo);
_result.QoSOptions.ShouldBe(_qoso);
_result.HttpHandlerOptions.ShouldBe(_hho);
_result.ReRoutes.ShouldBe(_reRoutes);
_result.RequestId.ShouldBe(_fileConfig.GlobalConfiguration.RequestIdKey);
_result.DownstreamScheme.ShouldBe(_fileConfig.GlobalConfiguration.DownstreamScheme);
}
private void ThenTheAdminPathIsSet()
{
_result.AdministrationPath.ShouldBe("wooty");
}
private void ThenTheDepdenciesAreCalledCorrectly()
{
_spcCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration), Times.Once);
_lboCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration.LoadBalancerOptions), Times.Once);
_qosCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration.QoSOptions), Times.Once);
_hhoCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration.HttpHandlerOptions), Times.Once);
}
private void GivenTheAdminPath()
{
_adminPath = new AdministrationPath("wooty");
_serviceCollection.AddSingleton<IAdministrationPath>(_adminPath);
}
private void GivenTheDependenciesAreSetUp()
{
_fileConfig = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration()
};
_reRoutes = new List<ReRoute>();
_spc = new ServiceProviderConfiguration("", "", 1, "", "", 1);
_lbo = new LoadBalancerOptionsBuilder().Build();
_qoso = new QoSOptions(1, 1, 1, "");
_hho = new HttpHandlerOptionsBuilder().Build();
_spcCreator.Setup(x => x.Create(It.IsAny<FileGlobalConfiguration>())).Returns(_spc);
_lboCreator.Setup(x => x.Create(It.IsAny<FileLoadBalancerOptions>())).Returns(_lbo);
_qosCreator.Setup(x => x.Create(It.IsAny<FileQoSOptions>())).Returns(_qoso);
_hhoCreator.Setup(x => x.Create(It.IsAny<FileHttpHandlerOptions>())).Returns(_hho);
}
private void WhenICreate()
{
var serviceProvider = _serviceCollection.BuildServiceProvider();
_creator = new ConfigurationCreator(_spcCreator.Object, _qosCreator.Object, _hhoCreator.Object, serviceProvider, _lboCreator.Object);
_result = _creator.Create(_fileConfig, _reRoutes);
}
}
}

View File

@ -0,0 +1,126 @@
namespace Ocelot.UnitTests.Configuration
{
using System.Collections.Generic;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class DynamicsCreatorTests
{
private readonly DynamicsCreator _creator;
private readonly Mock<IRateLimitOptionsCreator> _rloCreator;
private List<ReRoute> _result;
private FileConfiguration _fileConfig;
private RateLimitOptions _rlo1;
private RateLimitOptions _rlo2;
public DynamicsCreatorTests()
{
_rloCreator = new Mock<IRateLimitOptionsCreator>();
_creator = new DynamicsCreator(_rloCreator.Object);
}
[Fact]
public void should_return_nothing()
{
var fileConfig = new FileConfiguration();
this.Given(_ => GivenThe(fileConfig))
.When(_ => WhenICreate())
.Then(_ => ThenNothingIsReturned())
.And(_ => ThenTheRloCreatorIsNotCalled())
.BDDfy();
}
[Fact]
public void should_return_re_routes()
{
var fileConfig = new FileConfiguration
{
DynamicReRoutes = new List<FileDynamicReRoute>
{
new FileDynamicReRoute
{
ServiceName = "1",
RateLimitRule = new FileRateLimitRule
{
EnableRateLimiting = false
}
},
new FileDynamicReRoute
{
ServiceName = "2",
RateLimitRule = new FileRateLimitRule
{
EnableRateLimiting = true
}
}
}
};
this.Given(_ => GivenThe(fileConfig))
.And(_ => GivenTheRloCreatorReturns())
.When(_ => WhenICreate())
.Then(_ => ThenTheReRoutesAreReturned())
.And(_ => ThenTheRloCreatorIsCalledCorrectly())
.BDDfy();
}
private void ThenTheRloCreatorIsCalledCorrectly()
{
_rloCreator.Verify(x => x.Create(_fileConfig.DynamicReRoutes[0].RateLimitRule,
_fileConfig.GlobalConfiguration), Times.Once);
_rloCreator.Verify(x => x.Create(_fileConfig.DynamicReRoutes[1].RateLimitRule,
_fileConfig.GlobalConfiguration), Times.Once);
}
private void ThenTheReRoutesAreReturned()
{
_result.Count.ShouldBe(2);
_result[0].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeFalse();
_result[0].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo1);
_result[0].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[0].ServiceName);
_result[1].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue();
_result[1].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo2);
_result[1].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[1].ServiceName);
}
private void GivenTheRloCreatorReturns()
{
_rlo1 = new RateLimitOptionsBuilder().Build();
_rlo2 = new RateLimitOptionsBuilder().WithEnableRateLimiting(true).Build();
_rloCreator
.SetupSequence(x => x.Create(It.IsAny<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>()))
.Returns(_rlo1)
.Returns(_rlo2);
}
private void ThenTheRloCreatorIsNotCalled()
{
_rloCreator.Verify(x => x.Create(It.IsAny<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>()), Times.Never);
}
private void ThenNothingIsReturned()
{
_result.Count.ShouldBe(0);
}
private void WhenICreate()
{
_result = _creator.Create(_fileConfig);
}
private void GivenThe(FileConfiguration fileConfig)
{
_fileConfig = fileConfig;
}
}
}

View File

@ -70,6 +70,28 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_create_with_add_headers_to_request()
{
const string key = "X-Forwarded-For";
const string value = "{RemoteIpAddress}";
var reRoute = new FileReRoute
{
UpstreamHeaderTransform = new Dictionary<string, string>
{
{key, value},
}
};
var expected = new AddHeader(key, value);
this.Given(x => GivenTheReRoute(reRoute))
.When(x => WhenICreate())
.Then(x => ThenTheFollowingAddHeaderToUpstreamIsReturned(expected))
.BDDfy();
}
[Fact] [Fact]
public void should_use_base_url_placeholder() public void should_use_base_url_placeholder()
{ {

View File

@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.Configuration
private void ThenTheConfigurationIsReturned() private void ThenTheConfigurationIsReturned()
{ {
_getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("initial"); _getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("initial");
} }
private void WhenIGetTheConfiguration() private void WhenIGetTheConfiguration()

View File

@ -0,0 +1,54 @@
namespace Ocelot.UnitTests.Configuration
{
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class LoadBalancerOptionsCreatorTests
{
private readonly ILoadBalancerOptionsCreator _creator;
private FileLoadBalancerOptions _fileLoadBalancerOptions;
private LoadBalancerOptions _result;
public LoadBalancerOptionsCreatorTests()
{
_creator = new LoadBalancerOptionsCreator();
}
[Fact]
public void should_create()
{
var fileLoadBalancerOptions = new FileLoadBalancerOptions
{
Type = "test",
Key = "west",
Expiry = 1
};
this.Given(_ => GivenThe(fileLoadBalancerOptions))
.When(_ => WhenICreate())
.Then(_ => ThenTheOptionsAreCreated(fileLoadBalancerOptions))
.BDDfy();
}
private void ThenTheOptionsAreCreated(FileLoadBalancerOptions expected)
{
_result.Type.ShouldBe(expected.Type);
_result.Key.ShouldBe(expected.Key);
_result.ExpiryInMs.ShouldBe(expected.Expiry);
}
private void WhenICreate()
{
_result = _creator.Create(_fileLoadBalancerOptions);
}
private void GivenThe(FileLoadBalancerOptions fileLoadBalancerOptions)
{
_fileLoadBalancerOptions = fileLoadBalancerOptions;
}
}
}

View File

@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Linq;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.LoadBalancer.LoadBalancers;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class ReRouteKeyCreatorTests
{
private ReRouteKeyCreator _creator;
private FileReRoute _reRoute;
private string _result;
public ReRouteKeyCreatorTests()
{
_creator = new ReRouteKeyCreator();
}
[Fact]
public void should_return_sticky_session_key()
{
var reRoute = new FileReRoute
{
LoadBalancerOptions = new FileLoadBalancerOptions
{
Key = "testy",
Type = nameof(CookieStickySessions)
}
};
this.Given(_ => GivenThe(reRoute))
.When(_ => WhenICreate())
.Then(_ => ThenTheResultIs($"{nameof(CookieStickySessions)}:{reRoute.LoadBalancerOptions.Key}"))
.BDDfy();
}
[Fact]
public void should_return_re_route_key()
{
var reRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/product",
UpstreamHttpMethod = new List<string> {"GET", "POST", "PUT"},
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 123
},
new FileHostAndPort
{
Host = "localhost",
Port = 123
}
}
};
this.Given(_ => GivenThe(reRoute))
.When(_ => WhenICreate())
.Then(_ => ThenTheResultIs($"{reRoute.UpstreamPathTemplate}|{string.Join(",", reRoute.UpstreamHttpMethod)}|{string.Join(",", reRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}"))
.BDDfy();
}
private void GivenThe(FileReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenICreate()
{
_result = _creator.Create(_reRoute);
}
private void ThenTheResultIs(string expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -1,17 +1,17 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class ReRouteOptionsCreatorTests public class ReRouteOptionsCreatorTests
{ {
private ReRouteOptionsCreator _creator; private readonly ReRouteOptionsCreator _creator;
private FileReRoute _reRoute; private FileReRoute _reRoute;
private ReRouteOptions _result; private ReRouteOptions _result;
@ -40,7 +40,8 @@ namespace Ocelot.UnitTests.Configuration
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {
TtlSeconds = 1 TtlSeconds = 1
} },
ServiceName = "west"
}; };
var expected = new ReRouteOptionsBuilder() var expected = new ReRouteOptionsBuilder()
@ -48,6 +49,7 @@ namespace Ocelot.UnitTests.Configuration
.WithIsAuthorised(true) .WithIsAuthorised(true)
.WithIsCached(true) .WithIsCached(true)
.WithRateLimiting(true) .WithRateLimiting(true)
.WithUseServiceDiscovery(true)
.Build(); .Build();
this.Given(x => x.GivenTheFollowing(reRoute)) this.Given(x => x.GivenTheFollowing(reRoute))
@ -72,6 +74,7 @@ namespace Ocelot.UnitTests.Configuration
_result.IsAuthorised.ShouldBe(expected.IsAuthorised); _result.IsAuthorised.ShouldBe(expected.IsAuthorised);
_result.IsCached.ShouldBe(expected.IsCached); _result.IsCached.ShouldBe(expected.IsCached);
_result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting);
_result.UseServiceDiscovery.ShouldBe(expected.UseServiceDiscovery);
} }
} }
} }

View File

@ -0,0 +1,276 @@
namespace Ocelot.UnitTests.Configuration
{
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using Ocelot.Cache;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class ReRoutesCreatorTests
{
private ReRoutesCreator _creator;
private Mock<IClaimsToThingCreator> _cthCreator;
private Mock<IAuthenticationOptionsCreator> _aoCreator;
private Mock<IUpstreamTemplatePatternCreator> _utpCreator;
private Mock<IRequestIdKeyCreator> _ridkCreator;
private Mock<IQoSOptionsCreator> _qosoCreator;
private Mock<IReRouteOptionsCreator> _rroCreator;
private Mock<IRateLimitOptionsCreator> _rloCreator;
private Mock<IRegionCreator> _rCreator;
private Mock<IHttpHandlerOptionsCreator> _hhoCreator;
private Mock<IHeaderFindAndReplaceCreator> _hfarCreator;
private Mock<IDownstreamAddressesCreator> _daCreator;
private Mock<ILoadBalancerOptionsCreator> _lboCreator;
private Mock<IReRouteKeyCreator> _rrkCreator;
private Mock<ISecurityOptionsCreator> _soCreator;
private FileConfiguration _fileConfig;
private ReRouteOptions _rro;
private string _requestId;
private string _rrk;
private UpstreamPathTemplate _upt;
private AuthenticationOptions _ao;
private List<ClaimToThing> _ctt;
private QoSOptions _qoso;
private RateLimitOptions _rlo;
private string _region;
private HttpHandlerOptions _hho;
private HeaderTransformations _ht;
private List<DownstreamHostAndPort> _dhp;
private LoadBalancerOptions _lbo;
private List<ReRoute> _result;
private SecurityOptions _securityOptions;
public ReRoutesCreatorTests()
{
_cthCreator = new Mock<IClaimsToThingCreator>();
_aoCreator = new Mock<IAuthenticationOptionsCreator>();
_utpCreator = new Mock<IUpstreamTemplatePatternCreator>();
_ridkCreator = new Mock<IRequestIdKeyCreator>();
_qosoCreator = new Mock<IQoSOptionsCreator>();
_rroCreator = new Mock<IReRouteOptionsCreator>();
_rloCreator = new Mock<IRateLimitOptionsCreator>();
_rCreator = new Mock<IRegionCreator>();
_hhoCreator = new Mock<IHttpHandlerOptionsCreator>();
_hfarCreator = new Mock<IHeaderFindAndReplaceCreator>();
_daCreator = new Mock<IDownstreamAddressesCreator>();
_lboCreator = new Mock<ILoadBalancerOptionsCreator>();
_rrkCreator = new Mock<IReRouteKeyCreator>();
_soCreator = new Mock<ISecurityOptionsCreator>();
_creator = new ReRoutesCreator(
_cthCreator.Object,
_aoCreator.Object,
_utpCreator.Object,
_ridkCreator.Object,
_qosoCreator.Object,
_rroCreator.Object,
_rloCreator.Object,
_rCreator.Object,
_hhoCreator.Object,
_hfarCreator.Object,
_daCreator.Object,
_lboCreator.Object,
_rrkCreator.Object,
_soCreator.Object
);
}
[Fact]
public void should_return_nothing()
{
var fileConfig = new FileConfiguration();
this.Given(_ => GivenThe(fileConfig))
.When(_ => WhenICreate())
.Then(_ => ThenNoReRoutesAreReturned())
.BDDfy();
}
[Fact]
public void should_return_re_routes()
{
var fileConfig = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
ServiceName = "dave",
DangerousAcceptAnyServerCertificateValidator = true,
AddClaimsToRequest = new Dictionary<string, string>
{
{ "a","b" }
},
AddHeadersToRequest = new Dictionary<string, string>
{
{ "c","d" }
},
AddQueriesToRequest = new Dictionary<string, string>
{
{ "e","f" }
},
UpstreamHttpMethod = new List<string> { "GET", "POST" }
},
new FileReRoute
{
ServiceName = "wave",
DangerousAcceptAnyServerCertificateValidator = false,
AddClaimsToRequest = new Dictionary<string, string>
{
{ "g","h" }
},
AddHeadersToRequest = new Dictionary<string, string>
{
{ "i","j" }
},
AddQueriesToRequest = new Dictionary<string, string>
{
{ "k","l" }
},
UpstreamHttpMethod = new List<string> { "PUT", "DELETE" }
}
}
};
this.Given(_ => GivenThe(fileConfig))
.And(_ => GivenTheDependenciesAreSetUpCorrectly())
.When(_ => WhenICreate())
.Then(_ => ThenTheDependenciesAreCalledCorrectly())
.And(_ => ThenTheReRoutesAreCreated())
.BDDfy();
}
private void ThenTheDependenciesAreCalledCorrectly()
{
ThenTheDepsAreCalledFor(_fileConfig.ReRoutes[0], _fileConfig.GlobalConfiguration);
ThenTheDepsAreCalledFor(_fileConfig.ReRoutes[1], _fileConfig.GlobalConfiguration);
}
private void GivenTheDependenciesAreSetUpCorrectly()
{
_rro = new ReRouteOptions(false, false, false, false, false);
_requestId = "testy";
_rrk = "besty";
_upt = new UpstreamPathTemplateBuilder().Build();
_ao = new AuthenticationOptionsBuilder().Build();
_ctt = new List<ClaimToThing>();
_qoso = new QoSOptionsBuilder().Build();
_rlo = new RateLimitOptionsBuilder().Build();
_region = "vesty";
_hho = new HttpHandlerOptionsBuilder().Build();
_ht = new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>(), new List<AddHeader>(), new List<AddHeader>());
_dhp = new List<DownstreamHostAndPort>();
_lbo = new LoadBalancerOptionsBuilder().Build();
_rroCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_rro);
_ridkCreator.Setup(x => x.Create(It.IsAny<FileReRoute>(), It.IsAny<FileGlobalConfiguration>())).Returns(_requestId);
_rrkCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_rrk);
_utpCreator.Setup(x => x.Create(It.IsAny<IReRoute>())).Returns(_upt);
_aoCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_ao);
_cthCreator.Setup(x => x.Create(It.IsAny<Dictionary<string, string>>())).Returns(_ctt);
_qosoCreator.Setup(x => x.Create(It.IsAny<FileQoSOptions>(), It.IsAny<string>(), It.IsAny<List<string>>())).Returns(_qoso);
_rloCreator.Setup(x => x.Create(It.IsAny<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>())).Returns(_rlo);
_rCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_region);
_hhoCreator.Setup(x => x.Create(It.IsAny<FileHttpHandlerOptions>())).Returns(_hho);
_hfarCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_ht);
_daCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_dhp);
_lboCreator.Setup(x => x.Create(It.IsAny<FileLoadBalancerOptions>())).Returns(_lbo);
}
private void ThenTheReRoutesAreCreated()
{
_result.Count.ShouldBe(2);
ThenTheReRouteIsSet(_fileConfig.ReRoutes[0], 0);
ThenTheReRouteIsSet(_fileConfig.ReRoutes[1], 1);
}
private void ThenNoReRoutesAreReturned()
{
_result.ShouldBeEmpty();
}
private void GivenThe(FileConfiguration fileConfig)
{
_fileConfig = fileConfig;
}
private void WhenICreate()
{
_result = _creator.Create(_fileConfig);
}
private void ThenTheReRouteIsSet(FileReRoute expected, int reRouteIndex)
{
_result[reRouteIndex].DownstreamReRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated);
_result[reRouteIndex].DownstreamReRoute[0].IsAuthorised.ShouldBe(_rro.IsAuthorised);
_result[reRouteIndex].DownstreamReRoute[0].IsCached.ShouldBe(_rro.IsCached);
_result[reRouteIndex].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBe(_rro.EnableRateLimiting);
_result[reRouteIndex].DownstreamReRoute[0].RequestIdKey.ShouldBe(_requestId);
_result[reRouteIndex].DownstreamReRoute[0].LoadBalancerKey.ShouldBe(_rrk);
_result[reRouteIndex].DownstreamReRoute[0].UpstreamPathTemplate.ShouldBe(_upt);
_result[reRouteIndex].DownstreamReRoute[0].AuthenticationOptions.ShouldBe(_ao);
_result[reRouteIndex].DownstreamReRoute[0].ClaimsToHeaders.ShouldBe(_ctt);
_result[reRouteIndex].DownstreamReRoute[0].ClaimsToQueries.ShouldBe(_ctt);
_result[reRouteIndex].DownstreamReRoute[0].ClaimsToClaims.ShouldBe(_ctt);
_result[reRouteIndex].DownstreamReRoute[0].QosOptions.ShouldBe(_qoso);
_result[reRouteIndex].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo);
_result[reRouteIndex].DownstreamReRoute[0].CacheOptions.Region.ShouldBe(_region);
_result[reRouteIndex].DownstreamReRoute[0].CacheOptions.TtlSeconds.ShouldBe(expected.FileCacheOptions.TtlSeconds);
_result[reRouteIndex].DownstreamReRoute[0].HttpHandlerOptions.ShouldBe(_hho);
_result[reRouteIndex].DownstreamReRoute[0].UpstreamHeadersFindAndReplace.ShouldBe(_ht.Upstream);
_result[reRouteIndex].DownstreamReRoute[0].DownstreamHeadersFindAndReplace.ShouldBe(_ht.Downstream);
_result[reRouteIndex].DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(_ht.AddHeadersToUpstream);
_result[reRouteIndex].DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(_ht.AddHeadersToDownstream);
_result[reRouteIndex].DownstreamReRoute[0].DownstreamAddresses.ShouldBe(_dhp);
_result[reRouteIndex].DownstreamReRoute[0].LoadBalancerOptions.ShouldBe(_lbo);
_result[reRouteIndex].DownstreamReRoute[0].UseServiceDiscovery.ShouldBe(_rro.UseServiceDiscovery);
_result[reRouteIndex].DownstreamReRoute[0].DangerousAcceptAnyServerCertificateValidator.ShouldBe(expected.DangerousAcceptAnyServerCertificateValidator);
_result[reRouteIndex].DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DelegatingHandlers);
_result[reRouteIndex].DownstreamReRoute[0].ServiceName.ShouldBe(expected.ServiceName);
_result[reRouteIndex].DownstreamReRoute[0].DownstreamScheme.ShouldBe(expected.DownstreamScheme);
_result[reRouteIndex].DownstreamReRoute[0].RouteClaimsRequirement.ShouldBe(expected.RouteClaimsRequirement);
_result[reRouteIndex].DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate);
_result[reRouteIndex].DownstreamReRoute[0].Key.ShouldBe(expected.Key);
_result[reRouteIndex].UpstreamHttpMethod
.Select(x => x.Method)
.ToList()
.ShouldContain(x => x == expected.UpstreamHttpMethod[0]);
_result[reRouteIndex].UpstreamHttpMethod
.Select(x => x.Method)
.ToList()
.ShouldContain(x => x == expected.UpstreamHttpMethod[1]);
_result[reRouteIndex].UpstreamHost.ShouldBe(expected.UpstreamHost);
_result[reRouteIndex].DownstreamReRoute.Count.ShouldBe(1);
_result[reRouteIndex].UpstreamTemplatePattern.ShouldBe(_upt);
}
private void ThenTheDepsAreCalledFor(FileReRoute fileReRoute, FileGlobalConfiguration globalConfig)
{
_rroCreator.Verify(x => x.Create(fileReRoute), Times.Once);
_ridkCreator.Verify(x => x.Create(fileReRoute, globalConfig), Times.Once);
_rrkCreator.Verify(x => x.Create(fileReRoute), Times.Once);
_utpCreator.Verify(x => x.Create(fileReRoute), Times.Exactly(2));
_aoCreator.Verify(x => x.Create(fileReRoute), Times.Once);
_cthCreator.Verify(x => x.Create(fileReRoute.AddHeadersToRequest), Times.Once);
_cthCreator.Verify(x => x.Create(fileReRoute.AddClaimsToRequest), Times.Once);
_cthCreator.Verify(x => x.Create(fileReRoute.AddQueriesToRequest), Times.Once);
_qosoCreator.Verify(x => x.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod));
_rloCreator.Verify(x => x.Create(fileReRoute.RateLimitOptions, globalConfig), Times.Once);
_rCreator.Verify(x => x.Create(fileReRoute), Times.Once);
_hhoCreator.Verify(x => x.Create(fileReRoute.HttpHandlerOptions), Times.Once);
_hfarCreator.Verify(x => x.Create(fileReRoute), Times.Once);
_daCreator.Verify(x => x.Create(fileReRoute), Times.Once);
_lboCreator.Verify(x => x.Create(fileReRoute.LoadBalancerOptions), Times.Once);
_soCreator.Verify(x => x.Create(fileReRoute.SecurityOptions), Times.Once);
}
}
}

View File

@ -0,0 +1,72 @@
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Text;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class SecurityOptionsCreatorTests
{
private FileReRoute _fileReRoute;
private FileGlobalConfiguration _fileGlobalConfig;
private SecurityOptions _result;
private ISecurityOptionsCreator _creator;
public SecurityOptionsCreatorTests()
{
_creator = new SecurityOptionsCreator();
}
[Fact]
public void should_create_security_config()
{
var ipAllowedList = new List<string>() { "127.0.0.1", "192.168.1.1" };
var ipBlockedList = new List<string>() { "127.0.0.1", "192.168.1.1" };
var fileReRoute = new FileReRoute
{
SecurityOptions = new FileSecurityOptions()
{
IPAllowedList = ipAllowedList,
IPBlockedList = ipBlockedList
}
};
var expected = new SecurityOptions(ipAllowedList, ipBlockedList);
this.Given(x => x.GivenThe(fileReRoute))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheResultIs(expected))
.BDDfy();
}
private void GivenThe(FileReRoute reRoute)
{
_fileReRoute = reRoute;
}
private void WhenICreate()
{
_result = _creator.Create(_fileReRoute.SecurityOptions);
}
private void ThenTheResultIs(SecurityOptions expected)
{
for (int i = 0; i < expected.IPAllowedList.Count; i++)
{
_result.IPAllowedList[i].ShouldBe(expected.IPAllowedList[i]);
}
for (int i = 0; i < expected.IPBlockedList.Count; i++)
{
_result.IPBlockedList[i].ShouldBe(expected.IPBlockedList[i]);
}
}
}
}

View File

@ -9,6 +9,8 @@
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Moq;
using Microsoft.AspNetCore.Hosting;
public class ConfigurationBuilderExtensionsTests public class ConfigurationBuilderExtensionsTests
{ {
@ -19,6 +21,21 @@
private FileConfiguration _reRouteA; private FileConfiguration _reRouteA;
private FileConfiguration _reRouteB; private FileConfiguration _reRouteB;
private FileConfiguration _aggregate; private FileConfiguration _aggregate;
private FileConfiguration _envSpecific;
private Mock<IHostingEnvironment> _hostingEnvironment;
public ConfigurationBuilderExtensionsTests()
{
_hostingEnvironment = new Mock<IHostingEnvironment>();
// Clean up config files before each test
var subConfigFiles = new DirectoryInfo(".").GetFiles("ocelot.*.json");
foreach(var config in subConfigFiles)
{
config.Delete();
}
}
[Fact] [Fact]
public void should_add_base_url_to_config() public void should_add_base_url_to_config()
@ -32,23 +49,35 @@
[Fact] [Fact]
public void should_merge_files() public void should_merge_files()
{ {
this.Given(_ => GivenMultipleConfigurationFiles("")) this.Given(_ => GivenMultipleConfigurationFiles("", false))
.And(_ => GivenTheEnvironmentIs(null))
.When(_ => WhenIAddOcelotConfiguration()) .When(_ => WhenIAddOcelotConfiguration())
.Then(_ => ThenTheConfigsAreMerged()) .Then(_ => ThenTheConfigsAreMerged())
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_merge_files_except_env()
{
this.Given(_ => GivenMultipleConfigurationFiles("", true))
.And(_ => GivenTheEnvironmentIs("Env"))
.When(_ => WhenIAddOcelotConfiguration())
.Then(_ => ThenTheConfigsAreMerged())
.And(_ => NotContainsEnvSpecificConfig())
.BDDfy();
}
[Fact] [Fact]
public void should_merge_files_in_specific_folder() public void should_merge_files_in_specific_folder()
{ {
string configFolder = "ConfigFiles"; string configFolder = "ConfigFiles";
this.Given(_ => GivenMultipleConfigurationFiles(configFolder)) this.Given(_ => GivenMultipleConfigurationFiles(configFolder, false))
.When(_ => WhenIAddOcelotConfigurationWithSpecificFolder(configFolder)) .When(_ => WhenIAddOcelotConfigurationWithSpecificFolder(configFolder))
.Then(_ => ThenTheConfigsAreMerged()) .Then(_ => ThenTheConfigsAreMerged())
.BDDfy(); .BDDfy();
} }
private void GivenMultipleConfigurationFiles(string folder) private void GivenMultipleConfigurationFiles(string folder, bool addEnvSpecificConfig)
{ {
if (!string.IsNullOrEmpty(folder)) if (!string.IsNullOrEmpty(folder))
{ {
@ -85,7 +114,7 @@
new FileReRoute new FileReRoute
{ {
DownstreamScheme = "DownstreamScheme", DownstreamScheme = "DownstreamScheme",
DownstreamPathTemplate = "DownstreamDownstreamPathTemplate", DownstreamPathTemplate = "DownstreamPathTemplate",
Key = "Key", Key = "Key",
UpstreamHost = "UpstreamHost", UpstreamHost = "UpstreamHost",
UpstreamHttpMethod = new List<string> UpstreamHttpMethod = new List<string>
@ -174,6 +203,32 @@
} }
}; };
_envSpecific = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamScheme = "DownstreamSchemeSpec",
DownstreamPathTemplate = "DownstreamPathTemplateSpec",
Key = "KeySpec",
UpstreamHost = "UpstreamHostSpec",
UpstreamHttpMethod = new List<string>
{
"UpstreamHttpMethodSpec"
},
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "HostSpec",
Port = 80
}
}
}
}
};
string globalFilename = Path.Combine(folder, "ocelot.global.json"); string globalFilename = Path.Combine(folder, "ocelot.global.json");
string reroutesAFilename = Path.Combine(folder, "ocelot.reRoutesA.json"); string reroutesAFilename = Path.Combine(folder, "ocelot.reRoutesA.json");
string reroutesBFilename = Path.Combine(folder, "ocelot.reRoutesB.json"); string reroutesBFilename = Path.Combine(folder, "ocelot.reRoutesB.json");
@ -183,19 +238,32 @@
File.WriteAllText(reroutesAFilename, JsonConvert.SerializeObject(_reRouteA)); File.WriteAllText(reroutesAFilename, JsonConvert.SerializeObject(_reRouteA));
File.WriteAllText(reroutesBFilename, JsonConvert.SerializeObject(_reRouteB)); File.WriteAllText(reroutesBFilename, JsonConvert.SerializeObject(_reRouteB));
File.WriteAllText(aggregatesFilename, JsonConvert.SerializeObject(_aggregate)); File.WriteAllText(aggregatesFilename, JsonConvert.SerializeObject(_aggregate));
if (addEnvSpecificConfig)
{
string envSpecificFilename = Path.Combine(folder, "ocelot.Env.json");
File.WriteAllText(envSpecificFilename, JsonConvert.SerializeObject(_envSpecific));
}
}
private void GivenTheEnvironmentIs(string env)
{
_hostingEnvironment.SetupGet(x => x.EnvironmentName).Returns(env);
} }
private void WhenIAddOcelotConfiguration() private void WhenIAddOcelotConfiguration()
{ {
IConfigurationBuilder builder = new ConfigurationBuilder(); IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddOcelot();
builder.AddOcelot(_hostingEnvironment.Object);
_configRoot = builder.Build(); _configRoot = builder.Build();
} }
private void WhenIAddOcelotConfigurationWithSpecificFolder(string folder) private void WhenIAddOcelotConfigurationWithSpecificFolder(string folder)
{ {
IConfigurationBuilder builder = new ConfigurationBuilder(); IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddOcelot(folder); builder.AddOcelot(folder, _hostingEnvironment.Object);
_configRoot = builder.Build(); _configRoot = builder.Build();
} }
@ -235,6 +303,14 @@
fc.Aggregates.Count.ShouldBe(_aggregate.Aggregates.Count); fc.Aggregates.Count.ShouldBe(_aggregate.Aggregates.Count);
} }
private void NotContainsEnvSpecificConfig()
{
var fc = (FileConfiguration)_configRoot.Get(typeof(FileConfiguration));
fc.ReRoutes.ShouldNotContain(x => x.DownstreamScheme == _envSpecific.ReRoutes[0].DownstreamScheme);
fc.ReRoutes.ShouldNotContain(x => x.DownstreamPathTemplate == _envSpecific.ReRoutes[0].DownstreamPathTemplate);
fc.ReRoutes.ShouldNotContain(x => x.Key == _envSpecific.ReRoutes[0].Key);
}
private void GivenTheBaseUrl(string baseUrl) private void GivenTheBaseUrl(string baseUrl)
{ {
#pragma warning disable CS0618 #pragma warning disable CS0618

View File

@ -37,7 +37,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_handlerOptions = new HttpHandlerOptionsBuilder().Build(); _handlerOptions = new HttpHandlerOptionsBuilder().Build();
_loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(NoLoadBalancer)).Build(); _loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(NoLoadBalancer)).Build();
_qosOptionsCreator _qosOptionsCreator
.Setup(x => x.Create(It.IsAny<QoSOptions>(), It.IsAny<string>(), It.IsAny<string[]>())) .Setup(x => x.Create(It.IsAny<QoSOptions>(), It.IsAny<string>(), It.IsAny<List<string>>()))
.Returns(_qoSOptions); .Returns(_qoSOptions);
_creator = new DownstreamRouteCreator(_qosOptionsCreator.Object); _creator = new DownstreamRouteCreator(_qosOptionsCreator.Object);
} }
@ -198,7 +198,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void GivenTheQosCreatorReturns(QoSOptions options) private void GivenTheQosCreatorReturns(QoSOptions options)
{ {
_qosOptionsCreator _qosOptionsCreator
.Setup(x => x.Create(It.IsAny<QoSOptions>(), It.IsAny<string>(), It.IsAny<string[]>())) .Setup(x => x.Create(It.IsAny<QoSOptions>(), It.IsAny<string>(), It.IsAny<List<string>>()))
.Returns(options); .Returns(options);
} }
@ -211,7 +211,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void ThenTheDownstreamRouteIsCreated() private void ThenTheDownstreamRouteIsCreated()
{ {
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
_result.Data.ReRoute.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get); _result.Data.ReRoute.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get);
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET");
@ -229,21 +229,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void ThenTheDownstreamPathIsForwardSlash() private void ThenTheDownstreamPathIsForwardSlash()
{ {
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/"); _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/");
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET");
} }
private void ThenThePathDoesNotHaveTrailingSlash() private void ThenThePathDoesNotHaveTrailingSlash()
{ {
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET");
} }
private void ThenTheQueryStringIsRemoved() private void ThenTheQueryStringIsRemoved()
{ {
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET");
} }
@ -260,7 +260,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldBe(expected); _result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldBe(expected);
_result.Data.ReRoute.DownstreamReRoute[0].QosOptions.UseQos.ShouldBeTrue(); _result.Data.ReRoute.DownstreamReRoute[0].QosOptions.UseQos.ShouldBeTrue();
_qosOptionsCreator _qosOptionsCreator
.Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny<string[]>()), Times.Once); .Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny<List<string>>()), Times.Once);
} }
private void GivenTheConfiguration(IInternalConfiguration config) private void GivenTheConfiguration(IInternalConfiguration config)

View File

@ -23,7 +23,6 @@
{ {
private readonly Mock<IDownstreamRouteProvider> _finder; private readonly Mock<IDownstreamRouteProvider> _finder;
private readonly Mock<IDownstreamRouteProviderFactory> _factory; private readonly Mock<IDownstreamRouteProviderFactory> _factory;
private readonly Mock<IInternalConfigurationRepository> _repo;
private Response<DownstreamRoute> _downstreamRoute; private Response<DownstreamRoute> _downstreamRoute;
private IInternalConfiguration _config; private IInternalConfiguration _config;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
@ -35,7 +34,6 @@
public DownstreamRouteFinderMiddlewareTests() public DownstreamRouteFinderMiddlewareTests()
{ {
_repo = new Mock<IInternalConfigurationRepository>();
_finder = new Mock<IDownstreamRouteProvider>(); _finder = new Mock<IDownstreamRouteProvider>();
_factory = new Mock<IDownstreamRouteProviderFactory>(); _factory = new Mock<IDownstreamRouteProviderFactory>();
_factory.Setup(x => x.Get(It.IsAny<IInternalConfiguration>())).Returns(_finder.Object); _factory.Setup(x => x.Get(It.IsAny<IInternalConfiguration>())).Returns(_finder.Object);
@ -45,7 +43,7 @@
_loggerFactory.Setup(x => x.CreateLogger<DownstreamRouteFinderMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<DownstreamRouteFinderMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_multiplexer = new Mock<IMultiplexer>(); _multiplexer = new Mock<IMultiplexer>();
_middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object, _repo.Object, _multiplexer.Object); _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object, _multiplexer.Object);
} }
[Fact] [Fact]

View File

@ -459,7 +459,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
.WithUpstreamHost("MATCH")
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
@ -545,7 +544,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
.WithUpstreamHost("MATCH")
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
@ -556,7 +554,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { }) // empty list of methods .WithUpstreamHttpMethod(new List<string> { }) // empty list of methods
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
.WithUpstreamHost("MATCH")
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string> { }) // empty list of methods .WithUpstreamHttpMethod(new List<string> { }) // empty list of methods
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
@ -587,7 +584,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string>()) .WithUpstreamHttpMethod(new List<string>())
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
.WithUpstreamHost("MATCH")
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string>()) .WithUpstreamHttpMethod(new List<string>())
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
@ -618,7 +614,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string>()) .WithUpstreamHttpMethod(new List<string>())
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
.WithUpstreamHost("MATCH")
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string>()) .WithUpstreamHttpMethod(new List<string>())
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
@ -659,7 +654,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
.WithUpstreamHost("MATCH")
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath"))
@ -759,7 +753,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void ThenTheFollowingIsReturned(DownstreamRoute expected) private void ThenTheFollowingIsReturned(DownstreamRoute expected)
{ {
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value);
_result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority);
for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++)

View File

@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
private void WhenIReplaceTheTemplateVariables() private void WhenIReplaceTheTemplateVariables()
{ {
_result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues);
} }
private void ThenTheDownstreamUrlPathIsReturned(string expected) private void ThenTheDownstreamUrlPathIsReturned(string expected)

View File

@ -15,6 +15,9 @@ using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
using Ocelot.Infrastructure;
using Ocelot.Logging;
public class AddHeadersToRequestClaimToThingTests public class AddHeadersToRequestClaimToThingTests
{ {
private readonly AddHeadersToRequest _addHeadersToRequest; private readonly AddHeadersToRequest _addHeadersToRequest;
@ -24,11 +27,15 @@ namespace Ocelot.UnitTests.Headers
private List<ClaimToThing> _configuration; private List<ClaimToThing> _configuration;
private Response _result; private Response _result;
private Response<string> _claimValue; private Response<string> _claimValue;
private Mock<IPlaceholders> _placeholders;
private Mock<IOcelotLoggerFactory> _factory;
public AddHeadersToRequestClaimToThingTests() public AddHeadersToRequestClaimToThingTests()
{ {
_parser = new Mock<IClaimsParser>(); _parser = new Mock<IClaimsParser>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object); _placeholders = new Mock<IPlaceholders>();
_factory = new Mock<IOcelotLoggerFactory>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object, _placeholders.Object, _factory.Object);
_downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }

View File

@ -1,23 +1,56 @@
using Microsoft.AspNetCore.Http; namespace Ocelot.UnitTests.Headers
using Moq;
using Ocelot.Configuration.Creator;
using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Headers
{ {
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Configuration.Creator;
using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser;
using Responder;
using Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class AddHeadersToRequestPlainTests public class AddHeadersToRequestPlainTests
{ {
private readonly AddHeadersToRequest _addHeadersToRequest; private readonly AddHeadersToRequest _addHeadersToRequest;
private HttpContext _context; private HttpContext _context;
private AddHeader _addedHeader; private AddHeader _addedHeader;
private readonly Mock<IPlaceholders> _placeholders;
private Mock<IOcelotLoggerFactory> _factory;
private readonly Mock<IOcelotLogger> _logger;
public AddHeadersToRequestPlainTests() public AddHeadersToRequestPlainTests()
{ {
_addHeadersToRequest = new AddHeadersToRequest(Mock.Of<IClaimsParser>()); _placeholders = new Mock<IPlaceholders>();
_factory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_factory.Setup(x => x.CreateLogger<AddHeadersToRequest>()).Returns(_logger.Object);
_addHeadersToRequest = new AddHeadersToRequest(Mock.Of<IClaimsParser>(), _placeholders.Object, _factory.Object);
}
[Fact]
public void should_log_error_if_cannot_find_placeholder()
{
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError()));
this.Given(_ => GivenHttpRequestWithoutHeaders())
.When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}"))
.Then(_ => ThenAnErrorIsLogged("X-Forwarded-For", "{RemoteIdAddress}"))
.BDDfy();
}
[Fact]
public void should_add_placeholder_to_downstream_request()
{
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new OkResponse<string>("replaced"));
this.Given(_ => GivenHttpRequestWithoutHeaders())
.When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}"))
.Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders("replaced"))
.BDDfy();
} }
[Fact] [Fact]
@ -38,9 +71,23 @@ namespace Ocelot.UnitTests.Headers
.BDDfy(); .BDDfy();
} }
private void ThenAnErrorIsLogged(string key, string value)
{
_logger.Verify(x => x.LogWarning($"Unable to add header to response {key}: {value}"), Times.Once);
}
private void GivenHttpRequestWithoutHeaders() private void GivenHttpRequestWithoutHeaders()
{ {
_context = new DefaultHttpContext(); _context = new DefaultHttpContext
{
Request =
{
Headers =
{
}
}
};
} }
private void GivenHttpRequestWithHeader(string headerKey, string headerValue) private void GivenHttpRequestWithHeader(string headerKey, string headerValue)
@ -71,5 +118,12 @@ namespace Ocelot.UnitTests.Headers
value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null."); value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null.");
value.ToString().ShouldBe(_addedHeader.Value); value.ToString().ShouldBe(_addedHeader.Value);
} }
private void ThenTheHeaderGetsTakenOverToTheRequestHeaders(string expected)
{
var requestHeaders = _context.Request.Headers;
var value = requestHeaders[_addedHeader.Key];
value.ToString().ShouldBe(expected);
}
} }
} }

View File

@ -19,25 +19,25 @@ namespace Ocelot.UnitTests.Headers
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class HttpRequestHeadersBuilderMiddlewareTests public class ClaimsToHeadersMiddlewareTests
{ {
private readonly Mock<IAddHeadersToRequest> _addHeaders; private readonly Mock<IAddHeadersToRequest> _addHeaders;
private Response<DownstreamRoute> _downstreamRoute; private Response<DownstreamRoute> _downstreamRoute;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger; private Mock<IOcelotLogger> _logger;
private HttpRequestHeadersBuilderMiddleware _middleware; private ClaimsToHeadersMiddleware _middleware;
private DownstreamContext _downstreamContext; private DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
public HttpRequestHeadersBuilderMiddlewareTests() public ClaimsToHeadersMiddlewareTests()
{ {
_addHeaders = new Mock<IAddHeadersToRequest>(); _addHeaders = new Mock<IAddHeadersToRequest>();
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<HttpRequestHeadersBuilderMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<ClaimsToHeadersMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); _middleware = new ClaimsToHeadersMiddleware(_next, _loggerFactory.Object, _addHeaders.Object);
_downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }

View File

@ -109,7 +109,7 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()
{ {
_postReplacer.Verify(x => x.Replace(It.IsAny<DownstreamResponse>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<DownstreamRequest>()), Times.Once); _postReplacer.Verify(x => x.Replace(It.IsAny<DownstreamContext>(), It.IsAny<List<HeaderFindAndReplace>>()), Times.Once);
} }
private void GivenTheFollowingRequest() private void GivenTheFollowingRequest()

View File

@ -1,22 +1,22 @@
using Xunit;
using Shouldly;
using TestStack.BDDfy;
using System.Net.Http;
using Ocelot.Headers;
using Ocelot.Configuration;
using System.Collections.Generic;
using Ocelot.Responses;
using System.Linq;
using System.Net;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Request.Middleware;
using Xunit;
using Shouldly;
using TestStack.BDDfy;
using System.Net.Http;
using Ocelot.Headers;
using Ocelot.Configuration;
using System.Collections.Generic;
using Ocelot.Responses;
using System.Linq;
using System.Net;
using Moq;
public class HttpResponseHeaderReplacerTests public class HttpResponseHeaderReplacerTests
{ {
private DownstreamResponse _response; private DownstreamResponse _response;
@ -27,12 +27,14 @@ namespace Ocelot.UnitTests.Headers
private DownstreamRequest _request; private DownstreamRequest _request;
private Mock<IBaseUrlFinder> _finder; private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo; private Mock<IRequestScopedDataRepository> _repo;
private Mock<IHttpContextAccessor> _accessor;
public HttpResponseHeaderReplacerTests() public HttpResponseHeaderReplacerTests()
{ {
_accessor = new Mock<IHttpContextAccessor>();
_repo = new Mock<IRequestScopedDataRepository>(); _repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>(); _finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object); _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object);
_replacer = new HttpResponseHeaderReplacer(_placeholders); _replacer = new HttpResponseHeaderReplacer(_placeholders);
} }
@ -43,7 +45,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"}) new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> {new HeaderFindAndReplace("test", "test", "chiken", 0)}; var fAndRs = new List<HeaderFindAndReplace> {new HeaderFindAndReplace("test", "test", "chiken", 0)};
@ -61,7 +63,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"}) new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>();
@ -84,7 +86,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl}) new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> var fAndRs = new List<HeaderFindAndReplace>
{ {
@ -111,7 +113,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl}) new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> var fAndRs = new List<HeaderFindAndReplace>
{ {
@ -138,7 +140,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl}) new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> var fAndRs = new List<HeaderFindAndReplace>
{ {
@ -165,7 +167,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl}) new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> var fAndRs = new List<HeaderFindAndReplace>
{ {
@ -192,7 +194,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl}) new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> var fAndRs = new List<HeaderFindAndReplace>
{ {
@ -219,7 +221,7 @@ namespace Ocelot.UnitTests.Headers
new List<KeyValuePair<string, IEnumerable<string>>>() new List<KeyValuePair<string, IEnumerable<string>>>()
{ {
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl}) new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
}); }, "");
var fAndRs = new List<HeaderFindAndReplace> var fAndRs = new List<HeaderFindAndReplace>
{ {
@ -261,7 +263,8 @@ namespace Ocelot.UnitTests.Headers
private void WhenICallTheReplacer() private void WhenICallTheReplacer()
{ {
_result = _replacer.Replace(_response, _headerFindAndReplaces, _request); var context = new DownstreamContext(new DefaultHttpContext()) {DownstreamResponse = _response, DownstreamRequest = _request};
_result = _replacer.Replace(context, _headerFindAndReplaces);
} }
private void ThenTheHeaderShouldBe(string key, string value) private void ThenTheHeaderShouldBe(string key, string value)

View File

@ -1,27 +1,31 @@
using System;
using System.Net.Http;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests.Infrastructure namespace Ocelot.UnitTests.Infrastructure
{ {
using Microsoft.AspNetCore.Http;
using System;
using System.Net;
using System.Net.Http;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
using Shouldly;
using Xunit;
public class PlaceholdersTests public class PlaceholdersTests
{ {
private IPlaceholders _placeholders; private readonly IPlaceholders _placeholders;
private Mock<IBaseUrlFinder> _finder; private readonly Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo; private readonly Mock<IRequestScopedDataRepository> _repo;
private readonly Mock<IHttpContextAccessor> _accessor;
public PlaceholdersTests() public PlaceholdersTests()
{ {
_accessor = new Mock<IHttpContextAccessor>();
_repo = new Mock<IRequestScopedDataRepository>(); _repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>(); _finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object); _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object);
} }
[Fact] [Fact]
@ -33,6 +37,15 @@ namespace Ocelot.UnitTests.Infrastructure
result.Data.ShouldBe(baseUrl); result.Data.ShouldBe(baseUrl);
} }
[Fact]
public void should_return_remote_ip_address()
{
var httpContext = new DefaultHttpContext(){Connection = { RemoteIpAddress = IPAddress.Any}};
_accessor.Setup(x => x.HttpContext).Returns(httpContext);
var result = _placeholders.Get("{RemoteIpAddress}");
result.Data.ShouldBe(httpContext.Connection.RemoteIpAddress.ToString());
}
[Fact] [Fact]
public void should_return_key_does_not_exist() public void should_return_key_does_not_exist()
{ {

View File

@ -49,13 +49,13 @@ namespace Ocelot.UnitTests.Middleware
var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var billDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList<KeyValuePair<string, IEnumerable<string>>>()), DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList<KeyValuePair<string, IEnumerable<string>>>(), "some reason"),
DownstreamReRoute = billDownstreamReRoute DownstreamReRoute = billDownstreamReRoute
}; };
var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()), DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason"),
DownstreamReRoute = georgeDownstreamReRoute DownstreamReRoute = georgeDownstreamReRoute
}; };
@ -69,6 +69,7 @@ namespace Ocelot.UnitTests.Middleware
.When(x => WhenIAggregate()) .When(x => WhenIAggregate())
.Then(x => ThenTheContentIs(expected)) .Then(x => ThenTheContentIs(expected))
.And(x => ThenTheContentTypeIs("application/json")) .And(x => ThenTheContentTypeIs("application/json"))
.And(x => ThenTheReasonPhraseIs("cannot return from aggregate..which reason phrase would you use?"))
.BDDfy(); .BDDfy();
} }
@ -91,13 +92,13 @@ namespace Ocelot.UnitTests.Middleware
var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var billDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()), DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason"),
DownstreamReRoute = billDownstreamReRoute DownstreamReRoute = billDownstreamReRoute
}; };
var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()), DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason"),
DownstreamReRoute = georgeDownstreamReRoute, DownstreamReRoute = georgeDownstreamReRoute,
}; };
@ -116,6 +117,11 @@ namespace Ocelot.UnitTests.Middleware
.BDDfy(); .BDDfy();
} }
private void ThenTheReasonPhraseIs(string expected)
{
_upstreamContext.DownstreamResponse.ReasonPhrase.ShouldBe(expected);
}
private void ThenTheErrorIsMapped() private void ThenTheErrorIsMapped()
{ {
_upstreamContext.Errors.ShouldBe(_downstreamContexts[1].Errors); _upstreamContext.Errors.ShouldBe(_downstreamContexts[1].Errors);

View File

@ -43,11 +43,11 @@ namespace Ocelot.UnitTests.Middleware
{ {
new DownstreamContext(new DefaultHttpContext()) new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()) DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason")
}, },
new DownstreamContext(new DefaultHttpContext()) new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()) DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason")
} }
}; };
@ -72,11 +72,11 @@ namespace Ocelot.UnitTests.Middleware
{ {
new DownstreamContext(new DefaultHttpContext()) new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()) DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason")
}, },
new DownstreamContext(new DefaultHttpContext()) new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()) DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason")
} }
}; };
@ -146,7 +146,7 @@ namespace Ocelot.UnitTests.Middleware
var laura = await responses[1].Content.ReadAsStringAsync(); var laura = await responses[1].Content.ReadAsStringAsync();
var content = $"{tom}, {laura}"; var content = $"{tom}, {laura}";
var headers = responses.SelectMany(x => x.Headers).ToList(); var headers = responses.SelectMany(x => x.Headers).ToList();
return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers); return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers, "some reason");
} }
} }
} }

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