Merge remote-tracking branch 'refs/remotes/origin/develop' into RateLimit

# Conflicts:
#	src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
#	src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs
#	src/Ocelot/Configuration/QoSOptions.cs
#	src/Ocelot/Configuration/ReRoute.cs
#	test/Ocelot.AcceptanceTests/configuration.json
This commit is contained in:
geffzhang 2017-02-15 08:49:40 +08:00
commit 0aad1f8fa0
83 changed files with 1854 additions and 345 deletions

BIN
.DS_Store vendored

Binary file not shown.

6
.gitignore vendored
View File

@ -236,3 +236,9 @@ _Pvt_Extensions
# FAKE - F# Make # FAKE - F# Make
.fake/ .fake/
tools/ tools/
# MacOS
.DS_Store
# Ocelot acceptance test config
test/Ocelot.AcceptanceTests/configuration.json

View File

@ -122,7 +122,7 @@ Ocelot's primary functionality is to take incomeing http requests and forward th
to a downstream service. At the moment in the form of another http request (in the future to a downstream service. At the moment in the form of another http request (in the future
this could be any transport mechanism.). this could be any transport mechanism.).
Ocelot always adds a trailing slash to an UpstreamTemplate. Ocelot always adds a trailing slash to an UpstreamPathTemplate.
Ocelot's describes the routing of one request to another as a ReRoute. In order to get Ocelot's describes the routing of one request to another as a ReRoute. In order to get
anything working in Ocelot you need to set up a ReRoute in the configuration. anything working in Ocelot you need to set up a ReRoute in the configuration.
@ -140,16 +140,16 @@ the following.
"DownstreamScheme": "https", "DownstreamScheme": "https",
"DownstreamPort": 80, "DownstreamPort": 80,
"DownstreamHost" "localhost" "DownstreamHost" "localhost"
"UpstreamTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Put" "UpstreamHttpMethod": "Put"
} }
The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to.
The UpstreamTemplate is the URL that Ocelot will use to identity which The UpstreamPathTemplate is the URL that Ocelot will use to identity which
DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so
Ocelot can distinguish between requests to the same URL and is obviously needed to work :) Ocelot can distinguish between requests to the same URL and is obviously needed to work :)
In Ocelot you can add placeholders for variables to your Templates in the form of {something}. In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
The placeholder needs to be in both the DownstreamPathTemplate and UpstreamTemplate. If it is The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is
Ocelot will attempt to replace the placeholder with the correct variable value from the Ocelot will attempt to replace the placeholder with the correct variable value from the
Upstream URL when the request comes in. Upstream URL when the request comes in.
@ -190,7 +190,7 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel
{ {
"DownstreamPathTemplate": "/api/posts/{postId}", "DownstreamPathTemplate": "/api/posts/{postId}",
"DownstreamScheme": "https", "DownstreamScheme": "https",
"UpstreamTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Put", "UpstreamHttpMethod": "Put",
"ServiceName": "product" "ServiceName": "product"
"LoadBalancer": "LeastConnection" "LoadBalancer": "LeastConnection"
@ -305,19 +305,25 @@ Below is an example configuration that will transforms claims to query string pa
This shows a transform where Ocelot looks at the users LocationId claim and add its as This shows a transform where Ocelot looks at the users LocationId claim and add its as
a query string parameter to be forwarded onto the downstream service. a query string parameter to be forwarded onto the downstream service.
## Logging ## Quality of Service
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment. Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
for the standard asp.net core logging stuff at the moment. .NET library called Polly check them out [here](https://github.com/App-vNext/Polly).
There are a bunch of debugging logs in the ocelot middlewares however I think the Add the following section to a ReRoute configuration.
system probably needs more logging in the code it calls into. Other than the debugging
there is a global error handler that should catch any errors thrown and log them as errors.
The reason for not just using bog standard framework logging is that I could not "QoSOptions": {
work out how to override the request id that get's logged when setting IncludeScopes "ExceptionsAllowedBeforeBreaking":3,
to true for logging settings. Nicely onto the next feature. "DurationOfBreak":5,
"TimeoutValue":5000
}
You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be
implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped.
TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out.
If you do not add a QoS section QoS will not be used.
## RequestId / CorrelationId ## RequestId / CorrelationId
@ -404,6 +410,20 @@ http request before it is passed to Ocelots request creator.
Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added
after as Ocelot does not call the next middleware. after as Ocelot does not call the next middleware.
## Logging
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
for the standard asp.net core logging stuff at the moment.
There are a bunch of debugging logs in the ocelot middlewares however I think the
system probably needs more logging in the code it calls into. Other than the debugging
there is a global error handler that should catch any errors thrown and log them as errors.
The reason for not just using bog standard framework logging is that I could not
work out how to override the request id that get's logged when setting IncludeScopes
to true for logging settings. Nicely onto the next feature.
## Not supported ## Not supported
Ocelot does not support... Ocelot does not support...

View File

@ -42,7 +42,7 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
// internal build variables - don't change these. // internal build variables - don't change these.
var releaseTag = ""; var releaseTag = "";
var committedVersion = "0.0.0-dev"; string committedVersion = "0.0.0-dev";
var buildVersion = committedVersion; var buildVersion = committedVersion;
var target = Argument("target", "Default"); var target = Argument("target", "Default");
@ -310,7 +310,8 @@ private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbo
.ToDictionary(v => v[0], v => v[1]); .ToDictionary(v => v[0], v => v[1]);
var codePackage = packagesDir + File(artifacts["nuget"]); var codePackage = packagesDir + File(artifacts["nuget"]);
var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]);
Information("Pushing package");
NuGetPush( NuGetPush(
codePackage, codePackage,
@ -318,14 +319,6 @@ private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbo
ApiKey = feedApiKey, ApiKey = feedApiKey,
Source = codeFeedUrl Source = codeFeedUrl
}); });
NuGetPush(
symbolsPackage,
new NuGetPushSettings {
ApiKey = feedApiKey,
Source = symbolFeedUrl
});
} }
/// gets the resource from the specified url /// gets the resource from the specified url

View File

@ -15,7 +15,7 @@
# The path template we are listening on for this re route, Ocelot will add a trailing # The path template we are listening on for this re route, Ocelot will add a trailing
# slash to this property. Then when a request is made Ocelot makes sure a trailing # slash to this property. Then when a request is made Ocelot makes sure a trailing
# slash is added, so everything matches # slash is added, so everything matches
"UpstreamTemplate": "/identityserverexample", "UpstreamPathTemplate": "/identityserverexample",
# The method we are listening for on this re route # The method we are listening for on this re route
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
# Only support identity server at the moment # Only support identity server at the moment

View File

@ -1,6 +1,6 @@
 {  {
"projects": [ "src", "test" ], "projects": [ "src", "test" ],
"sdk": { "sdk": {
"version": "1.0.0-preview2-003133" "version": "1.0.0-preview2-003131"
} }
} }

View File

@ -12,7 +12,7 @@ namespace Ocelot.Authentication.Handler.Creator
/// </summary> /// </summary>
public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator
{ {
public Response<RequestDelegate> CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions) public Response<RequestDelegate> Create(IApplicationBuilder app, AuthenticationOptions authOptions)
{ {
var builder = app.New(); var builder = app.New();

View File

@ -8,6 +8,6 @@ namespace Ocelot.Authentication.Handler.Creator
public interface IAuthenticationHandlerCreator public interface IAuthenticationHandlerCreator
{ {
Response<RequestDelegate> CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions); Response<RequestDelegate> Create(IApplicationBuilder app, AuthenticationOptions authOptions);
} }
} }

View File

@ -19,7 +19,7 @@ namespace Ocelot.Authentication.Handler.Factory
public Response<AuthenticationHandler> Get(IApplicationBuilder app, AuthenticationOptions authOptions) public Response<AuthenticationHandler> Get(IApplicationBuilder app, AuthenticationOptions authOptions)
{ {
var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions); var handler = _creator.Create(app, authOptions);
if (!handler.IsError) if (!handler.IsError)
{ {

View File

@ -2,7 +2,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Authentication.Handler.Factory; using Ocelot.Authentication.Handler.Factory;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Errors; using Ocelot.Errors;

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;

View File

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses; using Ocelot.Responses;
@ -61,7 +60,7 @@ namespace Ocelot.Authorisation.Middleware
SetPipelineError(new List<Error> SetPipelineError(new List<Error>
{ {
new UnauthorisedError( new UnauthorisedError(
$"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamTemplate}") $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}")
}); });
} }
} }

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.Builder
{
public class AuthenticationOptionsBuilder
{
private string _provider;
private string _providerRootUrl;
private string _scopeName;
private string _scopeSecret;
private bool _requireHttps;
private List<string> _additionalScopes;
public AuthenticationOptionsBuilder WithProvider(string provider)
{
_provider = provider;
return this;
}
public AuthenticationOptionsBuilder WithProviderRootUrl(string providerRootUrl)
{
_providerRootUrl = providerRootUrl;
return this;
}
public AuthenticationOptionsBuilder WithScopeName(string scopeName)
{
_scopeName = scopeName;
return this;
}
public AuthenticationOptionsBuilder WithScopeSecret(string scopeSecret)
{
_scopeSecret = scopeSecret;
return this;
}
public AuthenticationOptionsBuilder WithRequireHttps(bool requireHttps)
{
_requireHttps = requireHttps;
return this;
}
public AuthenticationOptionsBuilder WithAdditionalScopes(List<string> additionalScopes)
{
_additionalScopes = additionalScopes;
return this;
}
public AuthenticationOptions Build()
{
return new AuthenticationOptions(_provider, _providerRootUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret);
}
}
}

View File

@ -0,0 +1,34 @@
namespace Ocelot.Configuration.Builder
{
public class QoSOptionsBuilder
{
private int _exceptionsAllowedBeforeBreaking;
private int _durationOfBreak;
private int _timeoutValue;
public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking)
{
_exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
return this;
}
public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak)
{
_durationOfBreak = durationOfBreak;
return this;
}
public QoSOptionsBuilder WithTimeoutValue(int timeoutValue)
{
_timeoutValue = timeoutValue;
return this;
}
public QoSOptions Build()
{
return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue);
}
}
}

View File

@ -0,0 +1,62 @@
namespace Ocelot.Configuration.Builder
{
public class ServiceProviderConfiguraionBuilder
{
private string _serviceName;
private string _downstreamHost;
private int _downstreamPort;
private bool _userServiceDiscovery;
private string _serviceDiscoveryProvider;
private string _serviceDiscoveryProviderHost;
private int _serviceDiscoveryProviderPort;
public ServiceProviderConfiguraionBuilder WithServiceName(string serviceName)
{
_serviceName = serviceName;
return this;
}
public ServiceProviderConfiguraionBuilder WithDownstreamHost(string downstreamHost)
{
_downstreamHost = downstreamHost;
return this;
}
public ServiceProviderConfiguraionBuilder WithDownstreamPort(int downstreamPort)
{
_downstreamPort = downstreamPort;
return this;
}
public ServiceProviderConfiguraionBuilder WithUseServiceDiscovery(bool userServiceDiscovery)
{
_userServiceDiscovery = userServiceDiscovery;
return this;
}
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider)
{
_serviceDiscoveryProvider = serviceDiscoveryProvider;
return this;
}
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost)
{
_serviceDiscoveryProviderHost = serviceDiscoveryProviderHost;
return this;
}
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort)
{
_serviceDiscoveryProviderPort = serviceDiscoveryProviderPort;
return this;
}
public ServiceProviderConfiguraion Build()
{
return new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery,
_serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort);
}
}
}

View File

@ -0,0 +1,356 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Ocelot.Utilities;
namespace Ocelot.Configuration.Creator
{
/// <summary>
/// Register as singleton
/// </summary>
public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator
{
private readonly IOptions<FileConfiguration> _options;
private readonly IConfigurationValidator _configurationValidator;
private const string RegExMatchEverything = ".*";
private const string RegExMatchEndString = "$";
private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashOnly = "^/$";
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
private readonly ILoadBalancerFactory _loadBalanceFactory;
private readonly ILoadBalancerHouse _loadBalancerHouse;
private readonly IQoSProviderFactory _qoSProviderFactory;
private readonly IQosProviderHouse _qosProviderHouse;
public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options,
IConfigurationValidator configurationValidator,
IClaimToThingConfigurationParser claimToThingConfigurationParser,
ILogger<FileOcelotConfigurationCreator> logger,
ILoadBalancerFactory loadBalancerFactory,
ILoadBalancerHouse loadBalancerHouse,
IQoSProviderFactory qoSProviderFactory,
IQosProviderHouse qosProviderHouse)
{
_loadBalanceFactory = loadBalancerFactory;
_loadBalancerHouse = loadBalancerHouse;
_qoSProviderFactory = qoSProviderFactory;
_qosProviderHouse = qosProviderHouse;
_options = options;
_configurationValidator = configurationValidator;
_claimToThingConfigurationParser = claimToThingConfigurationParser;
_logger = logger;
}
public async Task<Response<IOcelotConfiguration>> Create()
{
var config = await SetUpConfiguration();
return new OkResponse<IOcelotConfiguration>(config);
}
private async Task<IOcelotConfiguration> SetUpConfiguration()
{
var response = _configurationValidator.IsValid(_options.Value);
if (response.Data.IsError)
{
var errorBuilder = new StringBuilder();
foreach (var error in response.Errors)
{
errorBuilder.AppendLine(error.Message);
}
throw new Exception($"Unable to start Ocelot..configuration, errors were {errorBuilder}");
}
var reRoutes = new List<ReRoute>();
foreach (var reRoute in _options.Value.ReRoutes)
{
var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
reRoutes.Add(ocelotReRoute);
}
return new OcelotConfiguration(reRoutes);
}
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var isAuthenticated = IsAuthenticated(fileReRoute);
var isAuthorised = IsAuthorised(fileReRoute);
var isCached = IsCached(fileReRoute);
var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration);
var reRouteKey = BuildReRouteKey(fileReRoute);
var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute);
var isQos = IsQoS(fileReRoute);
var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration);
var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute);
<<<<<<< HEAD
ReRoute reRoute;
var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false;
RateLimitOptions rateLimitOption = null;
if (enableRateLimiting)
{
rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader,
fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders,
globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix,
new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit)
, globalConfiguration.RateLimitOptions.HttpStatusCode);
}
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
=======
var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest);
var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest);
var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest);
var qosOptions = BuildQoSOptions(fileReRoute);
var reRoute = new ReRouteBuilder()
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithIsAuthenticated(isAuthenticated)
.WithAuthenticationOptions(authOptionsForRoute)
.WithClaimsToHeaders(claimsToHeaders)
.WithClaimsToClaims(claimsToClaims)
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
.WithIsAuthorised(isAuthorised)
.WithClaimsToQueries(claimsToQueries)
.WithRequestIdKey(requestIdKey)
.WithIsCached(isCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds))
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
.WithLoadBalancer(fileReRoute.LoadBalancer)
.WithDownstreamHost(fileReRoute.DownstreamHost)
.WithDownstreamPort(fileReRoute.DownstreamPort)
.WithLoadBalancerKey(reRouteKey)
.WithServiceProviderConfiguraion(serviceProviderConfiguration)
.WithIsQos(isQos)
.WithQosOptions(qosOptions)
.Build();
await SetupLoadBalancer(reRoute);
SetupQosProvider(reRoute);
return reRoute;
}
private QoSOptions BuildQoSOptions(FileReRoute fileReRoute)
{
return new QoSOptionsBuilder()
.WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking)
.WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak)
.WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue)
.Build();
}
>>>>>>> refs/remotes/origin/develop
private bool IsQoS(FileReRoute fileReRoute)
{
return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0;
}
<<<<<<< HEAD
if (isAuthenticated)
{
var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider,
fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName,
fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes,
fileReRoute.AuthenticationOptions.ScopeSecret);
var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest);
var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest);
var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest);
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
fileReRoute.UpstreamTemplate,
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
authOptionsForRoute, claimsToHeaders, claimsToClaims,
fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)
, fileReRoute.DownstreamScheme,
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
serviceProviderConfiguration, isQos,
new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue),
enableRateLimiting, rateLimitOption);
}
else
{
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
fileReRoute.UpstreamTemplate,
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
fileReRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds),
fileReRoute.DownstreamScheme,
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
serviceProviderConfiguration, isQos,
new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue),
enableRateLimiting, rateLimitOption);
}
=======
private bool IsAuthenticated(FileReRoute fileReRoute)
{
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
}
>>>>>>> refs/remotes/origin/develop
private bool IsAuthorised(FileReRoute fileReRoute)
{
return fileReRoute.RouteClaimsRequirement?.Count > 0;
}
private bool IsCached(FileReRoute fileReRoute)
{
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
}
private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
var requestIdKey = globalRequestIdConfiguration
? globalConfiguration.RequestIdKey
: fileReRoute.RequestIdKey;
return requestIdKey;
}
private string BuildReRouteKey(FileReRoute fileReRoute)
{
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}";
return loadBalancerKey;
}
private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute)
{
return new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
}
private async Task SetupLoadBalancer(ReRoute reRoute)
{
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
_loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer);
}
private void SetupQosProvider(ReRoute reRoute)
{
var loadBalancer = _qoSProviderFactory.Get(reRoute);
_qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer);
}
private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
return new ServiceProviderConfiguraionBuilder()
.WithServiceName(fileReRoute.ServiceName)
.WithDownstreamHost(fileReRoute.DownstreamHost)
.WithDownstreamPort(fileReRoute.DownstreamPort)
.WithUseServiceDiscovery(useServiceDiscovery)
.WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider)
.WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
.WithServiceDiscoveryProviderPort(serviceProviderPort)
.Build();
}
private string BuildUpstreamTemplatePattern(FileReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate;
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
var placeholders = new List<string>();
for (var i = 0; i < upstreamTemplate.Length; i++)
{
if (IsPlaceHolder(upstreamTemplate, i))
{
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
var variableName = upstreamTemplate.Substring(i, difference);
placeholders.Add(variableName);
}
}
foreach (var placeholder in placeholders)
{
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
}
if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}
var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
return route;
}
private List<ClaimToThing> BuildAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
{
var claimsToTHings = new List<ClaimToThing>();
foreach (var add in thingBeingAdded)
{
var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value);
if (claimToHeader.IsError)
{
_logger.LogCritical(new EventId(1, "Application Failed to start"),
$"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect");
throw new Exception(claimToHeader.Errors[0].Message);
}
claimsToTHings.Add(claimToHeader.Data);
}
return claimsToTHings;
}
private bool IsPlaceHolder(string upstreamTemplate, int i)
{
return upstreamTemplate[i] == '{';
}
}
}

View File

@ -17,7 +17,7 @@ namespace Ocelot.Configuration.File
} }
public string DownstreamPathTemplate { get; set; } public string DownstreamPathTemplate { get; set; }
public string UpstreamTemplate { get; set; } public string UpstreamPathTemplate { get; set; }
public string UpstreamHttpMethod { get; set; } public string UpstreamHttpMethod { get; set; }
public FileAuthenticationOptions AuthenticationOptions { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; }
public Dictionary<string, string> AddHeadersToRequest { get; set; } public Dictionary<string, string> AddHeadersToRequest { get; set; }

View File

@ -0,0 +1,35 @@
<<<<<<< HEAD
using Polly.Timeout;
using System;
=======
using System;
using Polly.Timeout;
>>>>>>> refs/remotes/origin/develop
namespace Ocelot.Configuration
{
public class QoSOptions
{
public QoSOptions(
int exceptionsAllowedBeforeBreaking,
int durationofBreak,
int timeoutValue,
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
{
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak);
TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue);
TimeoutStrategy = timeoutStrategy;
}
public int ExceptionsAllowedBeforeBreaking { get; private set; }
public TimeSpan DurationOfBreak { get; private set; }
public TimeSpan TimeoutValue { get; private set; }
public TimeoutStrategy TimeoutStrategy { get; private set; }
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Values;
namespace Ocelot.Configuration
{
public class ReRoute
{
public ReRoute(PathTemplate downstreamPathTemplate,
PathTemplate upstreamTemplate,
HttpMethod upstreamHttpMethod,
string upstreamTemplatePattern,
bool isAuthenticated,
AuthenticationOptions authenticationOptions,
List<ClaimToThing> configurationHeaderExtractorProperties,
List<ClaimToThing> claimsToClaims,
Dictionary<string, string> routeClaimsRequirement,
bool isAuthorised,
List<ClaimToThing> claimsToQueries,
<<<<<<< HEAD
string requestIdKey, bool isCached, CacheOptions fileCacheOptions,
string downstreamScheme, string loadBalancer, string downstreamHost,
int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion,
bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions)
=======
string requestIdKey,
bool isCached,
CacheOptions fileCacheOptions,
string downstreamScheme,
string loadBalancer,
string downstreamHost,
int downstreamPort,
string reRouteKey,
ServiceProviderConfiguraion serviceProviderConfiguraion,
bool isQos,
QoSOptions qos)
>>>>>>> refs/remotes/origin/develop
{
ReRouteKey = reRouteKey;
ServiceProviderConfiguraion = serviceProviderConfiguraion;
LoadBalancer = loadBalancer;
DownstreamHost = downstreamHost;
DownstreamPort = downstreamPort;
DownstreamPathTemplate = downstreamPathTemplate;
UpstreamPathTemplate = upstreamTemplate;
UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern;
IsAuthenticated = isAuthenticated;
AuthenticationOptions = authenticationOptions;
RouteClaimsRequirement = routeClaimsRequirement;
IsAuthorised = isAuthorised;
RequestIdKey = requestIdKey;
IsCached = isCached;
FileCacheOptions = fileCacheOptions;
ClaimsToQueries = claimsToQueries
?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims
?? new List<ClaimToThing>();
ClaimsToHeaders = configurationHeaderExtractorProperties
?? new List<ClaimToThing>();
DownstreamScheme = downstreamScheme;
IsQos = isQos;
QosOptions = qos;
EnableEndpointRateLimiting = enableRateLimit;
RateLimitOptions = ratelimitOptions;
}
public string ReRouteKey {get;private set;}
public PathTemplate DownstreamPathTemplate { get; private set; }
public PathTemplate UpstreamPathTemplate { get; private set; }
public string UpstreamTemplatePattern { get; private set; }
public HttpMethod UpstreamHttpMethod { get; private set; }
public bool IsAuthenticated { get; private set; }
public bool IsAuthorised { get; private set; }
public AuthenticationOptions AuthenticationOptions { get; private set; }
public List<ClaimToThing> ClaimsToQueries { get; private set; }
public List<ClaimToThing> ClaimsToHeaders { get; private set; }
public List<ClaimToThing> ClaimsToClaims { get; private set; }
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
public string RequestIdKey { get; private set; }
public bool IsCached { get; private set; }
public CacheOptions FileCacheOptions { get; private set; }
public string DownstreamScheme {get;private set;}
public bool IsQos { get; private set; }
public QoSOptions QosOptions { get; private set; }
public string LoadBalancer {get;private set;}
public string DownstreamHost { get; private set; }
public int DownstreamPort { get; private set; }
public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; }
public bool EnableEndpointRateLimiting { get; private set; }
public RateLimitOptions RateLimitOptions { get; private set; }
}
}

View File

@ -54,7 +54,7 @@ namespace Ocelot.Configuration.Validator
continue; continue;
} }
var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}");
errors.Add(error); errors.Add(error);
} }
@ -94,18 +94,18 @@ namespace Ocelot.Configuration.Validator
private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration)
{ {
var hasDupes = configuration.ReRoutes var hasDupes = configuration.ReRoutes
.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any()); .GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any());
if (!hasDupes) if (!hasDupes)
{ {
return new ConfigurationValidationResult(false); return new ConfigurationValidationResult(false);
} }
var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }) var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod })
.Where(x => x.Skip(1).Any()); .Where(x => x.Skip(1).Any());
var errors = dupes var errors = dupes
.Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate))) .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamPathTemplate)))
.Cast<Error>() .Cast<Error>()
.ToList(); .ToList();

View File

@ -28,6 +28,7 @@ using Ocelot.Logging;
using Ocelot.QueryStrings; using Ocelot.QueryStrings;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.QoS;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Ocelot.RateLimit; using Ocelot.RateLimit;
@ -62,6 +63,8 @@ namespace Ocelot.DependencyInjection
{ {
services.AddMvcCore().AddJsonFormatters(); services.AddMvcCore().AddJsonFormatters();
services.AddLogging(); services.AddLogging();
services.AddSingleton<IQosProviderHouse, QosProviderHouse>();
services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>();
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();

View File

@ -26,15 +26,22 @@ namespace Ocelot.DownstreamRouteFinder.Finder
{ {
var configuration = await _configProvider.Get(); var configuration = await _configProvider.Get();
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));
foreach (var reRoute in applicableReRoutes) foreach (var reRoute in applicableReRoutes)
{ {
if (upstreamUrlPath == reRoute.UpstreamTemplatePattern)
{
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
}
var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);
if (urlMatch.Data.Match) if (urlMatch.Data.Match)
{ {
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate); var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
} }

View File

@ -8,7 +8,7 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
{ {
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
{ {
public Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamPathTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues) public Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
{ {
var downstreamPath = new StringBuilder(); var downstreamPath = new StringBuilder();

View File

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

View File

@ -25,6 +25,8 @@
ServicesAreNullError, ServicesAreNullError,
ServicesAreEmptyError, ServicesAreEmptyError,
UnableToFindServiceDiscoveryProviderError, UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError UnableToFindLoadBalancerError,
RequestTimedOutError,
UnableToFindQoSProviderError
} }
} }

View File

@ -14,16 +14,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
public async Task<ILoadBalancer> Get(ReRoute reRoute) public async Task<ILoadBalancer> Get(ReRoute reRoute)
{ {
var serviceConfig = new ServiceProviderConfiguraion( var serviceProvider = _serviceProviderFactory.Get(reRoute.ServiceProviderConfiguraion);
reRoute.ServiceProviderConfiguraion.ServiceName,
reRoute.ServiceProviderConfiguraion.DownstreamHost,
reRoute.ServiceProviderConfiguraion.DownstreamPort,
reRoute.ServiceProviderConfiguraion.UseServiceDiscovery,
reRoute.ServiceProviderConfiguraion.ServiceDiscoveryProvider,
reRoute.ServiceProviderConfiguraion.ServiceProviderHost,
reRoute.ServiceProviderConfiguraion.ServiceProviderPort);
var serviceProvider = _serviceProviderFactory.Get(serviceConfig);
switch (reRoute.LoadBalancer) switch (reRoute.LoadBalancer)
{ {

View File

@ -6,7 +6,6 @@ using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.QueryStrings.Middleware; using Ocelot.QueryStrings.Middleware;
using Ocelot.ServiceDiscovery;
namespace Ocelot.LoadBalancer.Middleware namespace Ocelot.LoadBalancer.Middleware
{ {
@ -31,7 +30,7 @@ namespace Ocelot.LoadBalancer.Middleware
{ {
_logger.LogDebug("started calling load balancing middleware"); _logger.LogDebug("started calling load balancing middleware");
var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.ReRouteKey);
if(loadBalancer.IsError) if(loadBalancer.IsError)
{ {
SetPipelineError(loadBalancer.Errors); SetPipelineError(loadBalancer.Errors);

View File

@ -105,7 +105,7 @@ namespace Ocelot.RateLimit.Middleware
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule)
{ {
_logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}.");
} }
public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter)

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Requester.QoS;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
@ -18,7 +19,7 @@ namespace Ocelot.Request.Builder
string contentType, string contentType,
RequestId.RequestId requestId, RequestId.RequestId requestId,
bool isQos, bool isQos,
QoSOptions qos) IQoSProvider qosProvider)
{ {
var request = await new RequestBuilder() var request = await new RequestBuilder()
.WithHttpMethod(httpMethod) .WithHttpMethod(httpMethod)
@ -30,7 +31,7 @@ namespace Ocelot.Request.Builder
.WithRequestId(requestId) .WithRequestId(requestId)
.WithCookies(cookies) .WithCookies(cookies)
.WithIsQos(isQos) .WithIsQos(isQos)
.WithQos(qos) .WithQos(qosProvider)
.Build(); .Build();
return new OkResponse<Request>(request); return new OkResponse<Request>(request);

View File

@ -1,8 +1,8 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Configuration;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
@ -17,6 +17,6 @@ namespace Ocelot.Request.Builder
string contentType, string contentType,
RequestId.RequestId requestId, RequestId.RequestId requestId,
bool isQos, bool isQos,
QoSOptions qos); IQoSProvider qosProvider);
} }
} }

View File

@ -8,7 +8,7 @@ using System.Net.Http.Headers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Ocelot.Configuration; using Ocelot.Requester.QoS;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
@ -24,7 +24,7 @@ namespace Ocelot.Request.Builder
private IRequestCookieCollection _cookies; private IRequestCookieCollection _cookies;
private readonly string[] _unsupportedHeaders = {"host"}; private readonly string[] _unsupportedHeaders = {"host"};
private bool _isQos; private bool _isQos;
private QoSOptions _qos; private IQoSProvider _qoSProvider;
public RequestBuilder WithHttpMethod(string httpMethod) public RequestBuilder WithHttpMethod(string httpMethod)
{ {
@ -80,9 +80,9 @@ namespace Ocelot.Request.Builder
return this; return this;
} }
public RequestBuilder WithQos(QoSOptions qos) public RequestBuilder WithQos(IQoSProvider qoSProvider)
{ {
_qos = qos; _qoSProvider = qoSProvider;
return this; return this;
} }
@ -105,7 +105,7 @@ namespace Ocelot.Request.Builder
var cookieContainer = CreateCookieContainer(uri); var cookieContainer = CreateCookieContainer(uri);
return new Request(httpRequestMessage, cookieContainer,_isQos,_qos); return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider);
} }
private Uri CreateUri() private Uri CreateUri()

View File

@ -1,10 +1,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester.QoS;
namespace Ocelot.Request.Middleware namespace Ocelot.Request.Middleware
{ {
@ -13,15 +13,18 @@ namespace Ocelot.Request.Middleware
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IRequestCreator _requestCreator; private readonly IRequestCreator _requestCreator;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IQosProviderHouse _qosProviderHouse;
public HttpRequestBuilderMiddleware(RequestDelegate next, public HttpRequestBuilderMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository requestScopedDataRepository, IRequestScopedDataRepository requestScopedDataRepository,
IRequestCreator requestCreator) IRequestCreator requestCreator,
IQosProviderHouse qosProviderHouse)
:base(requestScopedDataRepository) :base(requestScopedDataRepository)
{ {
_next = next; _next = next;
_requestCreator = requestCreator; _requestCreator = requestCreator;
_qosProviderHouse = qosProviderHouse;
_logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>(); _logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>();
} }
@ -29,17 +32,35 @@ namespace Ocelot.Request.Middleware
{ {
_logger.LogDebug("started calling request builder middleware"); _logger.LogDebug("started calling request builder middleware");
var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute.ReRouteKey);
if (qosProvider.IsError)
{
_logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error");
SetPipelineError(qosProvider.Errors);
return;
}
var buildResult = await _requestCreator var buildResult = await _requestCreator
.Build(context.Request.Method, DownstreamUrl, context.Request.Body, .Build(context.Request.Method,
context.Request.Headers, context.Request.Cookies, context.Request.QueryString, DownstreamUrl,
context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), context.Request.Body,
DownstreamRoute.ReRoute.IsQos,DownstreamRoute.ReRoute.QosOptions); context.Request.Headers,
context.Request.Cookies,
context.Request.QueryString,
context.Request.ContentType,
new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier),
DownstreamRoute.ReRoute.IsQos,
qosProvider.Data);
if (buildResult.IsError) if (buildResult.IsError)
{ {
_logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); _logger.LogDebug("IRequestCreator returned an error, setting pipeline error");
SetPipelineError(buildResult.Errors); SetPipelineError(buildResult.Errors);
return; return;
} }
_logger.LogDebug("setting upstream request"); _logger.LogDebug("setting upstream request");

View File

@ -2,22 +2,27 @@
using Ocelot.Values; using Ocelot.Values;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using Ocelot.Requester.QoS;
namespace Ocelot.Request namespace Ocelot.Request
{ {
public class Request public class Request
{ {
public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer,bool isQos, QoSOptions qos) public Request(
HttpRequestMessage httpRequestMessage,
CookieContainer cookieContainer,
bool isQos,
IQoSProvider qosProvider)
{ {
HttpRequestMessage = httpRequestMessage; HttpRequestMessage = httpRequestMessage;
CookieContainer = cookieContainer; CookieContainer = cookieContainer;
IsQos = isQos; IsQos = isQos;
Qos = qos; QosProvider = qosProvider;
} }
public HttpRequestMessage HttpRequestMessage { get; private set; } public HttpRequestMessage HttpRequestMessage { get; private set; }
public CookieContainer CookieContainer { get; private set; } public CookieContainer CookieContainer { get; private set; }
public bool IsQos { get; private set; } public bool IsQos { get; private set; }
public QoSOptions Qos { get; private set; } public IQoSProvider QosProvider { get; private set; }
} }
} }

View File

@ -1,74 +0,0 @@
using Ocelot.Logging;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Requester
{
public class CircuitBreakingDelegatingHandler : DelegatingHandler
{
private readonly IOcelotLogger _logger;
private readonly int _exceptionsAllowedBeforeBreaking;
private readonly TimeSpan _durationOfBreak;
private readonly Policy _circuitBreakerPolicy;
private readonly TimeoutPolicy _timeoutPolicy;
public CircuitBreakingDelegatingHandler(int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak,TimeSpan timeoutValue
,TimeoutStrategy timeoutStrategy, IOcelotLogger logger, HttpMessageHandler innerHandler)
: base(innerHandler)
{
this._exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
this._durationOfBreak = durationOfBreak;
_circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: exceptionsAllowedBeforeBreaking,
durationOfBreak: durationOfBreak,
onBreak: (ex, breakDelay) =>
{
_logger.LogError(".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
},
onReset: () => _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."),
onHalfOpen: () => _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.")
);
_timeoutPolicy = Policy.TimeoutAsync(timeoutValue, timeoutStrategy);
_logger = logger;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Task<HttpResponseMessage> responseTask = null;
try
{
responseTask = Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync<HttpResponseMessage>(() =>
{
return base.SendAsync(request,cancellationToken);
});
return responseTask;
}
catch (BrokenCircuitException ex)
{
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex);
throw;
}
catch (HttpRequestException)
{
return responseTask;
}
}
private static bool IsTransientFailure(HttpResponseMessage result)
{
return result.StatusCode >= HttpStatusCode.InternalServerError;
}
}
}

View File

@ -1,35 +1,39 @@
using Ocelot.Configuration; using System;
using Ocelot.Logging;
using Ocelot.Values;
using Polly.Timeout;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using Ocelot.Logging;
using Ocelot.Requester.QoS;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
internal class HttpClientBuilder internal class HttpClientBuilder
{ {
private readonly Dictionary<int, Func<DelegatingHandler>> handlers = new Dictionary<int, Func<DelegatingHandler>>(); private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
public HttpClientBuilder WithCircuitBreaker(QoSOptions qos, IOcelotLogger logger, HttpMessageHandler innerHandler) public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler)
{ {
handlers.Add(5000, () => new CircuitBreakingDelegatingHandler(qos.ExceptionsAllowedBeforeBreaking, qos.DurationOfBreak, qos.TimeoutValue, qos.TimeoutStrategy, logger, innerHandler)); _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler));
return this; return this;
} }
internal HttpClient Build(HttpMessageHandler innerHandler) internal HttpClient Build(HttpMessageHandler innerHandler)
{ {
return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(innerHandler); return _handlers.Any() ?
new HttpClient(CreateHttpMessageHandler()) :
new HttpClient(innerHandler);
} }
private HttpMessageHandler CreateHttpMessageHandler() private HttpMessageHandler CreateHttpMessageHandler()
{ {
HttpMessageHandler httpMessageHandler = new HttpClientHandler(); HttpMessageHandler httpMessageHandler = new HttpClientHandler();
handlers.OrderByDescending(handler => handler.Key).Select(handler => handler.Value).Reverse().ToList().ForEach(handler => _handlers
.OrderByDescending(handler => handler.Key)
.Select(handler => handler.Value)
.Reverse()
.ToList()
.ForEach(handler =>
{ {
var delegatingHandler = handler(); var delegatingHandler = handler();
delegatingHandler.InnerHandler = httpMessageHandler; delegatingHandler.InnerHandler = httpMessageHandler;

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Errors;
using Ocelot.Responses;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
@ -19,14 +19,15 @@ namespace Ocelot.Requester
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
{ {
HttpClientBuilder builder = new HttpClientBuilder(); var builder = new HttpClientBuilder();
using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer })
{ {
if (request.IsQos) if (request.IsQos)
{ {
builder.WithCircuitBreaker(request.Qos, _logger, handler); builder.WithQoS(request.QosProvider, _logger, handler);
} }
using (var httpClient = builder.Build(handler)) using (var httpClient = builder.Build(handler))
{ {
try try
@ -34,13 +35,19 @@ namespace Ocelot.Requester
var response = await httpClient.SendAsync(request.HttpRequestMessage); var response = await httpClient.SendAsync(request.HttpRequestMessage);
return new OkResponse<HttpResponseMessage>(response); return new OkResponse<HttpResponseMessage>(response);
} }
catch (Exception exception) catch (TimeoutRejectedException exception)
{ {
return return
new ErrorResponse<HttpResponseMessage>(new List<Error> new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
{ }
new UnableToCompleteRequestError(exception) catch (BrokenCircuitException exception)
}); {
return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (Exception exception)
{
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
} }
} }
} }

View File

@ -0,0 +1,47 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester
{
public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler
{
private readonly IQoSProvider _qoSProvider;
private readonly IOcelotLogger _logger;
public PollyCircuitBreakingDelegatingHandler(
IQoSProvider qoSProvider,
IOcelotLogger logger,
HttpMessageHandler innerHandler)
: base(innerHandler)
{
_qoSProvider = qoSProvider;
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await Policy
.WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy)
.ExecuteAsync(() => base.SendAsync(request,cancellationToken));
}
catch (BrokenCircuitException ex)
{
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex);
throw;
}
catch (HttpRequestException ex)
{
_logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
throw;
}
}
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Responses;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester.QoS
{
public interface IQoSProviderFactory
{
IQoSProvider Get(ReRoute reRoute);
}
public class QoSProviderFactory : IQoSProviderFactory
{
private readonly IOcelotLoggerFactory _loggerFactory;
public QoSProviderFactory(IOcelotLoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public IQoSProvider Get(ReRoute reRoute)
{
if (reRoute.IsQos)
{
return new PollyQoSProvider(reRoute, _loggerFactory);
}
return new NoQoSProvider();
}
}
public interface IQoSProvider
{
CircuitBreaker CircuitBreaker { get; }
}
public class NoQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
public class PollyQoSProvider : IQoSProvider
{
private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
private readonly TimeoutPolicy _timeoutPolicy;
private readonly IOcelotLogger _logger;
private readonly CircuitBreaker _circuitBreaker;
public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<PollyQoSProvider>();
_timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy);
_circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
durationOfBreak: reRoute.QosOptions.DurationOfBreak,
onBreak: (ex, breakDelay) =>
{
_logger.LogError(
".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
},
onReset: () =>
{
_logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again.");
},
onHalfOpen: () =>
{
_logger.LogDebug(".Breaker logging: Half-open; next call is a trial.");
}
);
_circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy);
}
public CircuitBreaker CircuitBreaker => _circuitBreaker;
}
public class CircuitBreaker
{
public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy)
{
CircuitBreakerPolicy = circuitBreakerPolicy;
TimeoutPolicy = timeoutPolicy;
}
public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; }
public TimeoutPolicy TimeoutPolicy { get; private set; }
}
public interface IQosProviderHouse
{
Response<IQoSProvider> Get(string key);
Response Add(string key, IQoSProvider loadBalancer);
}
public class QosProviderHouse : IQosProviderHouse
{
private readonly Dictionary<string, IQoSProvider> _qoSProviders;
public QosProviderHouse()
{
_qoSProviders = new Dictionary<string, IQoSProvider>();
}
public Response<IQoSProvider> Get(string key)
{
IQoSProvider qoSProvider;
if (_qoSProviders.TryGetValue(key, out qoSProvider))
{
return new OkResponse<IQoSProvider>(_qoSProviders[key]);
}
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
{
new UnableToFindQoSProviderError($"unabe to find qos provider for {key}")
});
}
public Response Add(string key, IQoSProvider loadBalancer)
{
if (!_qoSProviders.ContainsKey(key))
{
_qoSProviders.Add(key, loadBalancer);
}
_qoSProviders.Remove(key);
_qoSProviders.Add(key, loadBalancer);
return new OkResponse();
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Errors;
namespace Ocelot.Requester.QoS
{
public class UnableToFindQoSProviderError : Error
{
public UnableToFindQoSProviderError(string message)
: base(message, OcelotErrorCode.UnableToFindQoSProviderError)
{
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using Ocelot.Errors;
namespace Ocelot.Requester
{
public class RequestTimedOutError : Error
{
public RequestTimedOutError(Exception exception)
: base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError)
{
}
}
}

View File

@ -22,6 +22,11 @@ namespace Ocelot.Responder
return new OkResponse<int>(403); return new OkResponse<int>(403);
} }
if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError))
{
return new OkResponse<int>(503);
}
return new OkResponse<int>(404); return new OkResponse<int>(404);
} }
} }

View File

@ -5,7 +5,13 @@ namespace Ocelot.Responses
{ {
public class ErrorResponse<T> : Response<T> public class ErrorResponse<T> : Response<T>
{ {
public ErrorResponse(List<Error> errors) : base(errors) public ErrorResponse(Error error)
: base(new List<Error> {error})
{
}
public ErrorResponse(List<Error> errors)
: base(errors)
{ {
} }
} }

View File

@ -1,8 +1,8 @@
namespace Ocelot.Values namespace Ocelot.Values
{ {
public class DownstreamPathTemplate public class PathTemplate
{ {
public DownstreamPathTemplate(string value) public PathTemplate(string value)
{ {
Value = value; Value = value;
} }

BIN
test/.DS_Store vendored

Binary file not shown.

View File

@ -47,7 +47,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = _downstreamServicePort, DownstreamPort = _downstreamServicePort,
DownstreamHost = _downstreamServiceHost, DownstreamHost = _downstreamServiceHost,
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Post", UpstreamHttpMethod = "Post",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
@ -85,7 +85,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = _downstreamServicePort, DownstreamPort = _downstreamServicePort,
DownstreamHost = _downstreamServiceHost, DownstreamHost = _downstreamServiceHost,
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Post", UpstreamHttpMethod = "Post",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
@ -123,7 +123,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = _downstreamServicePort, DownstreamPort = _downstreamServicePort,
DownstreamHost = _downstreamServiceHost, DownstreamHost = _downstreamServiceHost,
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
@ -163,7 +163,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = _downstreamServicePort, DownstreamPort = _downstreamServicePort,
DownstreamHost = _downstreamServiceHost, DownstreamHost = _downstreamServiceHost,
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Post", UpstreamHttpMethod = "Post",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
@ -204,7 +204,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = _downstreamServicePort, DownstreamPort = _downstreamServicePort,
DownstreamHost = _downstreamServiceHost, DownstreamHost = _downstreamServiceHost,
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Post", UpstreamHttpMethod = "Post",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {

View File

@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51876, DownstreamPort = 51876,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
@ -98,7 +98,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51876, DownstreamPort = 51876,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {

View File

@ -35,7 +35,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {
@ -71,7 +71,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {

View File

@ -34,7 +34,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/products/{productId}", UpstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }
@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/products/{productId}", UpstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = false, ReRouteIsCaseSensitive = false,
} }
@ -89,7 +89,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/products/{productId}", UpstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true, ReRouteIsCaseSensitive = true,
} }
@ -117,7 +117,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamPathTemplate = "/PRODUCTS/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true, ReRouteIsCaseSensitive = true,
} }
@ -145,7 +145,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/products/{productId}", UpstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true, ReRouteIsCaseSensitive = true,
} }
@ -173,7 +173,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamPathTemplate = "/PRODUCTS/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true, ReRouteIsCaseSensitive = true,
} }

View File

@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 52876, DownstreamPort = 52876,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {

View File

@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 57876, DownstreamPort = 57876,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {

View File

@ -46,7 +46,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/api/ClientRateLimit", UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,
@ -56,7 +56,7 @@ namespace Ocelot.AcceptanceTests
ClientWhitelist = new List<string>(), ClientWhitelist = new List<string>(),
Limit = 3, Limit = 3,
Period = "1s", Period = "1s",
PeriodTimespan = 100 PeriodTimespan = 1000
} }
} }
}, },
@ -101,7 +101,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/api/ClientRateLimit", UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,

View File

@ -49,7 +49,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 41879, DownstreamPort = 41879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }
@ -86,7 +86,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 41879, DownstreamPort = 41879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -124,7 +124,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 41879, DownstreamPort = 41879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -162,7 +162,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 41879, DownstreamPort = 41879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }
@ -199,7 +199,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 41879, DownstreamPort = 41879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }
@ -236,7 +236,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 41879, DownstreamPort = 41879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }

View File

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class QoSTests : IDisposable
{
private IWebHost _brokenService;
private readonly Steps _steps;
private int _requestCount;
private IWebHost _workingService;
public QoSTests()
{
_steps = new Steps();
}
[Fact]
public void should_open_circuit_breaker_then_close()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 500,
DurationOfBreak = 1000
}
}
}
};
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura"))
.Given(x => _steps.GivenThereIsAConfiguration(configuration))
.Given(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void open_circuit_should_not_effect_different_reRoute()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 500,
DurationOfBreak = 1000
}
},
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51880,
UpstreamPathTemplate = "working",
UpstreamHttpMethod = "Get",
}
}
};
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura"))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/working"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
private void GivenIWaitMilliseconds(int ms)
{
Thread.Sleep(ms);
}
private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody)
{
_brokenService = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
//circuit starts closed
if (_requestCount == 0)
{
_requestCount++;
context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody);
return;
}
//request one times out and polly throws exception, circuit opens
if (_requestCount == 1)
{
_requestCount++;
await Task.Delay(1000);
context.Response.StatusCode = 200;
return;
}
//after break closes we return 200 OK
if (_requestCount == 2)
{
context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody);
return;
}
});
})
.Build();
_brokenService.Start();
}
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
{
_workingService = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
});
})
.Build();
_workingService.Start();
}
public void Dispose()
{
_workingService?.Dispose();
_brokenService?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -37,7 +37,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,
} }
@ -65,7 +65,7 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -95,8 +95,8 @@ namespace Ocelot.AcceptanceTests
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod ="Get" UpstreamHttpMethod = "Get",
} }
}, },
GlobalConfiguration = new FileGlobalConfiguration GlobalConfiguration = new FileGlobalConfiguration

View File

@ -30,7 +30,7 @@ namespace Ocelot.AcceptanceTests
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "http://localhost:53876/", DownstreamPathTemplate = "http://localhost:53876/",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get" UpstreamHttpMethod = "Get"
} }
} }

View File

@ -44,7 +44,7 @@ namespace Ocelot.AcceptanceTests
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -73,7 +73,7 @@ namespace Ocelot.AcceptanceTests
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -102,7 +102,7 @@ namespace Ocelot.AcceptanceTests
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost/", DownstreamHost = "localhost/",
DownstreamPort = 51879, DownstreamPort = 51879,
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -131,7 +131,7 @@ namespace Ocelot.AcceptanceTests
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,
UpstreamTemplate = "/products/", UpstreamPathTemplate = "/products/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -160,9 +160,8 @@ namespace Ocelot.AcceptanceTests
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,
UpstreamTemplate = "/products", UpstreamPathTemplate = "/products",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }
}; };
@ -182,22 +181,23 @@ namespace Ocelot.AcceptanceTests
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{
new FileReRoute
{ {
new FileReRoute DownstreamPathTemplate = "/products",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions()
{ {
DownstreamPathTemplate = "/products",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions()
{
ExceptionsAllowedBeforeBreaking = 3, ExceptionsAllowedBeforeBreaking = 3,
DurationOfBreak =5, DurationOfBreak = 5,
TimeoutValue = 5000 } TimeoutValue = 5000
} }
} }
}
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura"))
@ -221,7 +221,7 @@ namespace Ocelot.AcceptanceTests
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,
UpstreamTemplate = "/products/{productId}", UpstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
} }
@ -249,7 +249,7 @@ namespace Ocelot.AcceptanceTests
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Post", UpstreamHttpMethod = "Post",
} }
} }
@ -274,7 +274,7 @@ namespace Ocelot.AcceptanceTests
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/newThing", DownstreamPathTemplate = "/newThing",
UpstreamTemplate = "/newThing", UpstreamPathTemplate = "/newThing",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHost = "localhost", DownstreamHost = "localhost",
DownstreamPort = 51879, DownstreamPort = 51879,

View File

@ -68,7 +68,7 @@ namespace Ocelot.AcceptanceTests
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ServiceName = serviceName, ServiceName = serviceName,
LoadBalancer = "LeastConnection", LoadBalancer = "LeastConnection",

View File

@ -6,6 +6,7 @@ using Moq;
using Ocelot.Authentication.Handler; using Ocelot.Authentication.Handler;
using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Creator;
using Ocelot.Authentication.Handler.Factory; using Ocelot.Authentication.Handler.Factory;
using Ocelot.Configuration.Builder;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
@ -33,7 +34,11 @@ namespace Ocelot.UnitTests.Authentication
[Fact] [Fact]
public void should_return_identity_server_access_token_handler() public void should_return_identity_server_access_token_handler()
{ {
this.Given(x => x.GivenTheAuthenticationOptionsAre(new AuthenticationOptions("IdentityServer", "","",false, new List<string>(), ""))) var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider("IdentityServer")
.Build();
this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions))
.And(x => x.GivenTheCreatorReturns()) .And(x => x.GivenTheCreatorReturns())
.When(x => x.WhenIGetFromTheFactory()) .When(x => x.WhenIGetFromTheFactory())
.Then(x => x.ThenTheHandlerIsReturned("IdentityServer")) .Then(x => x.ThenTheHandlerIsReturned("IdentityServer"))
@ -43,7 +48,10 @@ namespace Ocelot.UnitTests.Authentication
[Fact] [Fact]
public void should_return_error_if_cannot_create_handler() public void should_return_error_if_cannot_create_handler()
{ {
this.Given(x => x.GivenTheAuthenticationOptionsAre(new AuthenticationOptions("IdentityServer", "", "", false, new List<string>(), ""))) var authenticationOptions = new AuthenticationOptionsBuilder()
.Build();
this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions))
.And(x => x.GivenTheCreatorReturnsAnError()) .And(x => x.GivenTheCreatorReturnsAnError())
.When(x => x.WhenIGetFromTheFactory()) .When(x => x.WhenIGetFromTheFactory())
.Then(x => x.ThenAnErrorResponseIsReturned()) .Then(x => x.ThenAnErrorResponseIsReturned())
@ -58,7 +66,7 @@ namespace Ocelot.UnitTests.Authentication
private void GivenTheCreatorReturnsAnError() private void GivenTheCreatorReturnsAnError()
{ {
_creator _creator
.Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny<IApplicationBuilder>(), It.IsAny<AuthenticationOptions>())) .Setup(x => x.Create(It.IsAny<IApplicationBuilder>(), It.IsAny<AuthenticationOptions>()))
.Returns(new ErrorResponse<RequestDelegate>(new List<Error> .Returns(new ErrorResponse<RequestDelegate>(new List<Error>
{ {
new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for xxx") new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for xxx")
@ -68,7 +76,7 @@ namespace Ocelot.UnitTests.Authentication
private void GivenTheCreatorReturns() private void GivenTheCreatorReturns()
{ {
_creator _creator
.Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny<IApplicationBuilder>(), It.IsAny<AuthenticationOptions>())) .Setup(x => x.Create(It.IsAny<IApplicationBuilder>(), It.IsAny<AuthenticationOptions>()))
.Returns(new OkResponse<RequestDelegate>(x => Task.CompletedTask)); .Returns(new OkResponse<RequestDelegate>(x => Task.CompletedTask));
} }

View File

@ -71,7 +71,9 @@ namespace Ocelot.UnitTests.Authentication
[Fact] [Fact]
public void should_call_next_middleware_if_route_is_not_authenticated() public void should_call_next_middleware_if_route_is_not_authenticated()
{ {
this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().Build()))) this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder()
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheUserIsAuthenticated()) .Then(x => x.ThenTheUserIsAuthenticated())
.BDDfy(); .BDDfy();

View File

@ -63,7 +63,11 @@ namespace Ocelot.UnitTests.Authorization
[Fact] [Fact]
public void should_call_authorisation_service() public void should_call_authorisation_service()
{ {
this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithIsAuthorised(true).Build()))) this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithIsAuthorised(true)
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build())))
.And(x => x.GivenTheAuthServiceReturns(new OkResponse<bool>(true))) .And(x => x.GivenTheAuthServiceReturns(new OkResponse<bool>(true)))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) .Then(x => x.ThenTheAuthServiceIsCalledCorrectly())

View File

@ -87,7 +87,12 @@ namespace Ocelot.UnitTests.Cache
private void GivenTheDownstreamRouteIs() private void GivenTheDownstreamRouteIs()
{ {
var reRoute = new ReRouteBuilder().WithIsCached(true).WithCacheOptions(new CacheOptions(100)).Build(); var reRoute = new ReRouteBuilder()
.WithIsCached(true)
.WithCacheOptions(new CacheOptions(100))
.WithUpstreamHttpMethod("Get")
.Build();
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), reRoute); var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), reRoute);
_scopedRepo _scopedRepo

View File

@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.Claims
{ {
new ClaimToThing("sub", "UserType", "|", 0) new ClaimToThing("sub", "UserType", "|", 0)
}) })
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))

View File

@ -29,7 +29,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}",
UpstreamTemplate = "http://asdf.com" UpstreamPathTemplate = "http://asdf.com"
} }
} }
})) }))
@ -48,7 +48,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/api/products/", DownstreamPathTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com" UpstreamPathTemplate = "http://asdf.com"
} }
} }
})) }))
@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/api/products/", DownstreamPathTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com", UpstreamPathTemplate = "http://asdf.com",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
Provider = "IdentityServer" Provider = "IdentityServer"
@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/api/products/", DownstreamPathTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com", UpstreamPathTemplate = "http://asdf.com",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
Provider = "BootyBootyBottyRockinEverywhere" Provider = "BootyBootyBottyRockinEverywhere"
@ -114,12 +114,12 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/api/products/", DownstreamPathTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com" UpstreamPathTemplate = "http://asdf.com"
}, },
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "http://www.bbc.co.uk", DownstreamPathTemplate = "http://www.bbc.co.uk",
UpstreamTemplate = "http://asdf.com" UpstreamPathTemplate = "http://asdf.com"
} }
} }
})) }))

View File

@ -9,6 +9,7 @@ using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -28,9 +29,15 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory; private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse; private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
private readonly Mock<ILoadBalancer> _loadBalancer; private readonly Mock<ILoadBalancer> _loadBalancer;
private readonly Mock<IQoSProviderFactory> _qosProviderFactory;
private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly Mock<IQoSProvider> _qosProvider;
public FileConfigurationCreatorTests() public FileConfigurationCreatorTests()
{ {
_qosProviderFactory = new Mock<IQoSProviderFactory>();
_qosProviderHouse = new Mock<IQosProviderHouse>();
_qosProvider = new Mock<IQoSProvider>();
_logger = new Mock<ILogger<FileOcelotConfigurationCreator>>(); _logger = new Mock<ILogger<FileOcelotConfigurationCreator>>();
_configParser = new Mock<IClaimToThingConfigurationParser>(); _configParser = new Mock<IClaimToThingConfigurationParser>();
_validator = new Mock<IConfigurationValidator>(); _validator = new Mock<IConfigurationValidator>();
@ -40,7 +47,8 @@ namespace Ocelot.UnitTests.Configuration
_loadBalancer = new Mock<ILoadBalancer>(); _loadBalancer = new Mock<ILoadBalancer>();
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object,
_loadBalancerFactory.Object, _loadBalancerHouse.Object); _loadBalancerFactory.Object, _loadBalancerHouse.Object,
_qosProviderFactory.Object, _qosProviderHouse.Object);
} }
[Fact] [Fact]
@ -53,7 +61,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamHost = "127.0.0.1", DownstreamHost = "127.0.0.1",
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -64,10 +72,39 @@ namespace Ocelot.UnitTests.Configuration
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly())
.And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_create_qos_provider()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "127.0.0.1",
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions
{
TimeoutValue = 1,
DurationOfBreak = 1,
ExceptionsAllowedBeforeBreaking = 1
}
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheQosProviderFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.TheQosProviderFactoryIsCalledCorrectly())
.And(x => x.ThenTheQosProviderHouseIsCalledCorrectly())
.BDDfy();
}
[Fact] [Fact]
public void should_use_downstream_host() public void should_use_downstream_host()
{ {
@ -78,7 +115,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamHost = "127.0.0.1", DownstreamHost = "127.0.0.1",
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -91,7 +128,7 @@ namespace Ocelot.UnitTests.Configuration
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamHost("127.0.0.1") .WithDownstreamHost("127.0.0.1")
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build() .Build()
@ -109,7 +146,7 @@ namespace Ocelot.UnitTests.Configuration
new FileReRoute new FileReRoute
{ {
DownstreamScheme = "https", DownstreamScheme = "https",
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
} }
@ -122,7 +159,7 @@ namespace Ocelot.UnitTests.Configuration
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamScheme("https") .WithDownstreamScheme("https")
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build() .Build()
@ -139,7 +176,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = false, ReRouteIsCaseSensitive = false,
@ -161,13 +198,15 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithServiceName("ProductService") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithUseServiceDiscovery(true) .WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("consul") .WithServiceDiscoveryProvider("consul")
.WithServiceDiscoveryAddress("127.0.01") .WithServiceDiscoveryProviderHost("127.0.0.1")
.WithServiceName("ProductService")
.Build())
.Build() .Build()
})) }))
.BDDfy(); .BDDfy();
@ -182,7 +221,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = false, ReRouteIsCaseSensitive = false,
@ -195,12 +234,12 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithUseServiceDiscovery(false) .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithServiceDiscoveryProvider(null) .WithUseServiceDiscovery(false)
.WithServiceDiscoveryAddress(null) .Build())
.Build() .Build()
})) }))
.BDDfy(); .BDDfy();
@ -215,7 +254,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = false ReRouteIsCaseSensitive = false
@ -228,7 +267,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build() .Build()
@ -245,7 +284,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get" UpstreamHttpMethod = "Get"
} }
@ -257,7 +296,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build() .Build()
@ -274,7 +313,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
@ -287,7 +326,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/$")
.Build() .Build()
@ -304,7 +343,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
@ -321,7 +360,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/$")
.WithRequestIdKey("blahhhh") .WithRequestIdKey("blahhhh")
@ -339,7 +378,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
@ -352,7 +391,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/$")
.Build() .Build()
@ -363,18 +402,23 @@ namespace Ocelot.UnitTests.Configuration
[Fact] [Fact]
public void should_create_with_headers_to_extract() public void should_create_with_headers_to_extract()
{ {
var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider("IdentityServer")
.WithProviderRootUrl("http://localhost:51888")
.WithRequireHttps(false)
.WithScopeSecret("secret")
.WithScopeName("api")
.WithAdditionalScopes(new List<string>())
.Build();
var expected = new List<ReRoute> var expected = new List<ReRoute>
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/$")
.WithAuthenticationProvider("IdentityServer") .WithAuthenticationOptions(authenticationOptions)
.WithAuthenticationProviderUrl("http://localhost:51888")
.WithRequireHttps(false)
.WithScopeSecret("secret")
.WithAuthenticationProviderScopeName("api")
.WithClaimsToHeaders(new List<ClaimToThing> .WithClaimsToHeaders(new List<ClaimToThing>
{ {
new ClaimToThing("CustomerId", "CustomerId", "", 0), new ClaimToThing("CustomerId", "CustomerId", "", 0),
@ -388,7 +432,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true, ReRouteIsCaseSensitive = true,
@ -427,18 +471,23 @@ namespace Ocelot.UnitTests.Configuration
[Fact] [Fact]
public void should_create_with_authentication_properties() public void should_create_with_authentication_properties()
{ {
var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider("IdentityServer")
.WithProviderRootUrl("http://localhost:51888")
.WithRequireHttps(false)
.WithScopeSecret("secret")
.WithScopeName("api")
.WithAdditionalScopes(new List<string>())
.Build();
var expected = new List<ReRoute> var expected = new List<ReRoute>
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/$")
.WithAuthenticationProvider("IdentityServer") .WithAuthenticationOptions(authenticationOptions)
.WithAuthenticationProviderUrl("http://localhost:51888")
.WithRequireHttps(false)
.WithScopeSecret("secret")
.WithAuthenticationProviderScopeName("api")
.Build() .Build()
}; };
@ -448,7 +497,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}", UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true, ReRouteIsCaseSensitive = true,
@ -481,7 +530,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
@ -494,7 +543,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$")
.Build() .Build()
@ -511,7 +560,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/",
DownstreamPathTemplate = "/products/{productId}", DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
@ -524,7 +573,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}/")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$")
.Build() .Build()
@ -541,7 +590,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
UpstreamTemplate = "/", UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/api/products/", DownstreamPathTemplate = "/api/products/",
UpstreamHttpMethod = "Get", UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
@ -554,9 +603,9 @@ namespace Ocelot.UnitTests.Configuration
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("/api/products/") .WithDownstreamPathTemplate("/api/products/")
.WithUpstreamTemplate("/") .WithUpstreamPathTemplate("/")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/$") .WithUpstreamTemplatePattern("^/$")
.Build() .Build()
})) }))
.BDDfy(); .BDDfy();
@ -591,7 +640,7 @@ namespace Ocelot.UnitTests.Configuration
result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value);
result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod);
result.UpstreamTemplate.ShouldBe(expected.UpstreamTemplate); result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value);
result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern);
} }
} }
@ -631,5 +680,24 @@ namespace Ocelot.UnitTests.Configuration
_loadBalancerHouse _loadBalancerHouse
.Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once); .Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once);
} }
private void GivenTheQosProviderFactoryReturns()
{
_qosProviderFactory
.Setup(x => x.Get(It.IsAny<ReRoute>()))
.Returns(_qosProvider.Object);
}
private void TheQosProviderFactoryIsCalledCorrectly()
{
_qosProviderFactory
.Verify(x => x.Get(It.IsAny<ReRoute>()), Times.Once);
}
private void ThenTheQosProviderHouseIsCalledCorrectly()
{
_qosProviderHouse
.Verify(x => x.Add(It.IsAny<string>(), _qosProvider.Object), Times.Once);
}
} }
} }

View File

@ -84,7 +84,10 @@ namespace Ocelot.UnitTests.Configuration
public List<ReRoute> ReRoutes => new List<ReRoute> public List<ReRoute> ReRoutes => new List<ReRoute>
{ {
new ReRouteBuilder().WithDownstreamPathTemplate(_downstreamTemplatePath).Build() new ReRouteBuilder()
.WithDownstreamPathTemplate(_downstreamTemplatePath)
.WithUpstreamHttpMethod("Get")
.Build()
}; };
} }
} }

View File

@ -61,7 +61,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_call_scoped_data_repository_correctly() public void should_call_scoped_data_repository_correctly()
{ {
this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) this.Given(x => x.GivenTheDownStreamRouteFinderReturns(
new DownstreamRoute(
new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("any old string")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy(); .BDDfy();

View File

@ -35,6 +35,37 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_return_route() public void should_return_route()
{
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher"))
.And(x =>x.GivenTheTemplateVariableAndNameFinderReturns(
new OkResponse<List<UrlPathPlaceholderNameAndValue>>(
new List<UrlPathPlaceholderNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("someUpstreamPath")
.Build()
}))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(
new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod("Get")
.Build()
)))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_route_if_upstream_path_and_upstream_template_are_the_same()
{ {
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath"))
.And( .And(
@ -45,7 +76,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamTemplate("someUpstreamPath") .WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("someUpstreamPath") .WithUpstreamTemplatePattern("someUpstreamPath")
.Build() .Build()
@ -58,9 +89,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod("Get")
.Build() .Build()
))) )))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) .And(x => x.ThenTheUrlMatcherIsNotCalled())
.BDDfy(); .BDDfy();
} }
@ -76,13 +108,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath") .WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamTemplate("someUpstreamPath") .WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("") .WithUpstreamTemplatePattern("")
.Build(), .Build(),
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithDownstreamPathTemplate("someDownstreamPathForAPost")
.WithUpstreamTemplate("someUpstreamPath") .WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod("Post") .WithUpstreamHttpMethod("Post")
.WithUpstreamTemplatePattern("") .WithUpstreamTemplatePattern("")
.Build() .Build()
@ -95,6 +127,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithDownstreamPathTemplate("someDownstreamPathForAPost")
.WithUpstreamHttpMethod("Post")
.Build() .Build()
))) )))
.BDDfy(); .BDDfy();
@ -103,12 +136,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_not_return_route() public void should_not_return_route()
{ {
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath"))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute> .And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{ {
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("somPath") .WithDownstreamPathTemplate("somPath")
.WithUpstreamTemplate("somePath") .WithUpstreamPathTemplate("somePath")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("somePath") .WithUpstreamTemplatePattern("somePath")
.Build(), .Build(),
@ -143,7 +176,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void ThenTheUrlMatcherIsCalledCorrectly() private void ThenTheUrlMatcherIsCalledCorrectly()
{ {
_mockMatcher _mockMatcher
.Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamTemplate), Times.Once); .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once);
}
private void ThenTheUrlMatcherIsNotCalled()
{
_mockMatcher
.Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never);
} }
private void GivenTheUrlMatcherReturns(Response<UrlMatch> match) private void GivenTheUrlMatcherReturns(Response<UrlMatch> match)

View File

@ -18,6 +18,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher
_urlMatcher = new RegExUrlMatcher(); _urlMatcher = new RegExUrlMatcher();
} }
[Fact]
public void should_not_match_forward_slash_only_regex()
{
this.Given(x => x.GivenIHaveAUpstreamPath("/working/"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsFalse())
.BDDfy();
}
[Fact]
public void should_match_forward_slash_only_regex()
{
this.Given(x => x.GivenIHaveAUpstreamPath("/"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue())
.BDDfy();
}
[Fact] [Fact]
public void should_find_match_when_template_smaller_than_valid_path() public void should_find_match_when_template_smaller_than_valid_path()
{ {

View File

@ -72,7 +72,13 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
{ {
var hostAndPort = new HostAndPort("127.0.0.1", 80); var hostAndPort = new HostAndPort("127.0.0.1", 80);
this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) this.Given(x => x.GivenTheDownStreamRouteIs(
new DownstreamRoute(
new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("any old string")
.WithUpstreamHttpMethod("Get")
.Build())))
.And(x => x.GivenTheHostAndPortIs(hostAndPort)) .And(x => x.GivenTheHostAndPortIs(hostAndPort))
.And(x => x.TheUrlReplacerReturns("/api/products/1")) .And(x => x.TheUrlReplacerReturns("/api/products/1"))
.And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1")) .And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1"))
@ -101,7 +107,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
{ {
_downstreamPath = new OkResponse<DownstreamPath>(new DownstreamPath(downstreamUrl)); _downstreamPath = new OkResponse<DownstreamPath>(new DownstreamPath(downstreamUrl));
_downstreamUrlTemplateVariableReplacer _downstreamUrlTemplateVariableReplacer
.Setup(x => x.Replace(It.IsAny<DownstreamPathTemplate>(), It.IsAny<List<UrlPathPlaceholderNameAndValue>>())) .Setup(x => x.Replace(It.IsAny<PathTemplate>(), It.IsAny<List<UrlPathPlaceholderNameAndValue>>()))
.Returns(_downstreamPath); .Returns(_downstreamPath);
} }

View File

@ -25,7 +25,12 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
[Fact] [Fact]
public void can_replace_no_template_variables() public void can_replace_no_template_variables()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().Build()))) this.Given(x => x.GivenThereIsAUrlMatch(
new DownstreamRoute(
new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned(""))
.BDDfy(); .BDDfy();
@ -34,7 +39,13 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
[Fact] [Fact]
public void can_replace_no_template_variables_with_slash() public void can_replace_no_template_variables_with_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("/").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(
new DownstreamRoute(
new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("/")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/"))
.BDDfy(); .BDDfy();
@ -43,7 +54,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
[Fact] [Fact]
public void can_replace_url_no_slash() public void can_replace_url_no_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("api").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("api")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api"))
.BDDfy(); .BDDfy();
@ -52,7 +67,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
[Fact] [Fact]
public void can_replace_url_one_slash() public void can_replace_url_one_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("api/").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("api/")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/"))
.BDDfy(); .BDDfy();
@ -61,7 +80,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
[Fact] [Fact]
public void can_replace_url_multiple_slash() public void can_replace_url_multiple_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("api/product/products/").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("api/product/products/")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/"))
.BDDfy(); .BDDfy();
@ -75,7 +98,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
new UrlPathPlaceholderNameAndValue("{productId}", "1") new UrlPathPlaceholderNameAndValue("{productId}", "1")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables,
new ReRouteBuilder()
.WithDownstreamPathTemplate("productservice/products/{productId}/")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/"))
.BDDfy(); .BDDfy();
@ -89,7 +116,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
new UrlPathPlaceholderNameAndValue("{productId}", "1") new UrlPathPlaceholderNameAndValue("{productId}", "1")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables,
new ReRouteBuilder()
.WithDownstreamPathTemplate("productservice/products/{productId}/variants")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants"))
.BDDfy(); .BDDfy();
@ -104,7 +135,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
new UrlPathPlaceholderNameAndValue("{variantId}", "12") new UrlPathPlaceholderNameAndValue("{variantId}", "12")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables,
new ReRouteBuilder()
.WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12"))
.BDDfy(); .BDDfy();
@ -120,7 +155,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer
new UrlPathPlaceholderNameAndValue("{categoryId}", "34") new UrlPathPlaceholderNameAndValue("{categoryId}", "34")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables,
new ReRouteBuilder()
.WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}")
.WithUpstreamHttpMethod("Get")
.Build())))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12"))
.BDDfy(); .BDDfy();

View File

@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.Headers
{ {
new ClaimToThing("UserId", "Subject", "", 0) new ClaimToThing("UserId", "Subject", "", 0)
}) })
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))

View File

@ -24,18 +24,13 @@ namespace Ocelot.UnitTests.LoadBalancer
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object); _factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
} }
private void GivenTheServiceProviderFactoryReturns()
{
_serviceProviderFactory
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()))
.Returns(_serviceProvider.Object);
}
[Fact] [Fact]
public void should_return_no_load_balancer() public void should_return_no_load_balancer()
{ {
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.Build(); .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build())
.WithUpstreamHttpMethod("Get")
.Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
.And(x => x.GivenTheServiceProviderFactoryReturns()) .And(x => x.GivenTheServiceProviderFactoryReturns())
@ -49,6 +44,8 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithLoadBalancer("RoundRobin") .WithLoadBalancer("RoundRobin")
.WithUpstreamHttpMethod("Get")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build())
.Build(); .Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
@ -63,6 +60,8 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithLoadBalancer("LeastConnection") .WithLoadBalancer("LeastConnection")
.WithUpstreamHttpMethod("Get")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build())
.Build(); .Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
@ -77,6 +76,8 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithLoadBalancer("RoundRobin") .WithLoadBalancer("RoundRobin")
.WithUpstreamHttpMethod("Get")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build())
.Build(); .Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
@ -86,6 +87,13 @@ namespace Ocelot.UnitTests.LoadBalancer
.BDDfy(); .BDDfy();
} }
private void GivenTheServiceProviderFactoryReturns()
{
_serviceProviderFactory
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()))
.Returns(_serviceProvider.Object);
}
private void ThenTheServiceProviderIsCalledCorrectly() private void ThenTheServiceProviderIsCalledCorrectly()
{ {
_serviceProviderFactory _serviceProviderFactory

View File

@ -68,6 +68,7 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
@ -84,6 +85,7 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
@ -99,6 +101,7 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))

View File

@ -70,6 +70,7 @@ namespace Ocelot.UnitTests.QueryStrings
{ {
new ClaimToThing("UserId", "Subject", "", 0) new ClaimToThing("UserId", "Subject", "", 0)
}) })
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))

View File

@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.RateLimit
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions(
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429)) new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429))
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
@ -88,6 +89,7 @@ namespace Ocelot.UnitTests.RateLimit
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions(
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429)) new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429))
.WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))

View File

@ -20,6 +20,7 @@ using Ocelot.Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Requester.QoS;
namespace Ocelot.UnitTests.Request namespace Ocelot.UnitTests.Request
{ {
@ -27,6 +28,7 @@ namespace Ocelot.UnitTests.Request
{ {
private readonly Mock<IRequestCreator> _requestBuilder; private readonly Mock<IRequestCreator> _requestBuilder;
private readonly Mock<IRequestScopedDataRepository> _scopedRepository; private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly string _url; private readonly string _url;
private readonly TestServer _server; private readonly TestServer _server;
private readonly HttpClient _client; private readonly HttpClient _client;
@ -38,6 +40,7 @@ namespace Ocelot.UnitTests.Request
public HttpRequestBuilderMiddlewareTests() public HttpRequestBuilderMiddlewareTests()
{ {
_url = "http://localhost:51879"; _url = "http://localhost:51879";
_qosProviderHouse = new Mock<IQosProviderHouse>();
_requestBuilder = new Mock<IRequestCreator>(); _requestBuilder = new Mock<IRequestCreator>();
_scopedRepository = new Mock<IRequestScopedDataRepository>(); _scopedRepository = new Mock<IRequestScopedDataRepository>();
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Request
{ {
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
x.AddLogging(); x.AddLogging();
x.AddSingleton(_qosProviderHouse.Object);
x.AddSingleton(_requestBuilder.Object); x.AddSingleton(_requestBuilder.Object);
x.AddSingleton(_scopedRepository.Object); x.AddSingleton(_scopedRepository.Object);
}) })
@ -68,17 +72,26 @@ namespace Ocelot.UnitTests.Request
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithRequestIdKey("LSRequestId").Build()); .WithRequestIdKey("LSRequestId")
.WithUpstreamHttpMethod("Get")
.Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider())))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new QoSOptions(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider())))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
private void GivenTheQosProviderHouseReturns(Response<IQoSProvider> qosProvider)
{
_qosProviderHouse
.Setup(x => x.Get(It.IsAny<string>()))
.Returns(qosProvider);
}
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
{ {
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
@ -92,7 +105,7 @@ namespace Ocelot.UnitTests.Request
_request = new OkResponse<Ocelot.Request.Request>(request); _request = new OkResponse<Ocelot.Request.Request>(request);
_requestBuilder _requestBuilder
.Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(), .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(),
It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<QoSOptions>())) It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>()))
.ReturnsAsync(_request); .ReturnsAsync(_request);
} }

View File

@ -11,6 +11,7 @@ using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Requester.QoS;
namespace Ocelot.UnitTests.Request namespace Ocelot.UnitTests.Request
{ {
@ -27,7 +28,7 @@ namespace Ocelot.UnitTests.Request
private Response<Ocelot.Request.Request> _result; private Response<Ocelot.Request.Request> _result;
private Ocelot.RequestId.RequestId _requestId; private Ocelot.RequestId.RequestId _requestId;
private bool _isQos; private bool _isQos;
private QoSOptions _qos; private IQoSProvider _qoSProvider;
public RequestBuilderTests() public RequestBuilderTests()
{ {
@ -40,7 +41,7 @@ namespace Ocelot.UnitTests.Request
{ {
this.Given(x => x.GivenIHaveHttpMethod("GET")) this.Given(x => x.GivenIHaveHttpMethod("GET"))
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x=> x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x=> x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/"))
.BDDfy(); .BDDfy();
@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.Request
{ {
this.Given(x => x.GivenIHaveHttpMethod("POST")) this.Given(x => x.GivenIHaveHttpMethod("POST"))
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post))
@ -65,7 +66,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom")))
.And(x => x.GivenTheContentTypeIs("application/json")) .And(x => x.GivenTheContentTypeIs("application/json"))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom")))
@ -79,7 +80,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom")))
.And(x => x.GivenTheContentTypeIs("application/json")) .And(x => x.GivenTheContentTypeIs("application/json"))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary
@ -98,7 +99,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom")))
.And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8"))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary
@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.Request
{ {
{"ChopSticks", "Bubbles" } {"ChopSticks", "Bubbles" }
})) }))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
@ -138,7 +139,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary()))
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId)))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
{ {
@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Request
{"RequestId", "534534gv54gv45g" } {"RequestId", "534534gv54gv45g" }
})) }))
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString())))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
{ {
@ -177,7 +178,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary()))
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue)))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheRequestIdIsNotInTheHeaders()) .And(x => x.ThenTheRequestIdIsNotInTheHeaders())
.BDDfy(); .BDDfy();
@ -188,10 +189,10 @@ namespace Ocelot.UnitTests.Request
_requestId = requestId; _requestId = requestId;
} }
private void GivenTheQos(bool isQos, QoSOptions qos) private void GivenTheQos(bool isQos, IQoSProvider qoSProvider)
{ {
_isQos = isQos; _isQos = isQos;
_qos = qos; _qoSProvider = qoSProvider;
} }
[Fact] [Fact]
@ -304,7 +305,7 @@ namespace Ocelot.UnitTests.Request
private void WhenICreateARequest() private void WhenICreateARequest()
{ {
_result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers,
_cookies, _query, _contentType, _requestId,_isQos,_qos).Result; _cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result;
} }

View File

@ -72,7 +72,9 @@ namespace Ocelot.UnitTests.RequestId
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("any old string") .WithDownstreamPathTemplate("any old string")
.WithRequestIdKey("LSRequestId").Build()); .WithRequestIdKey("LSRequestId")
.WithUpstreamHttpMethod("Get")
.Build());
var requestId = Guid.NewGuid().ToString(); var requestId = Guid.NewGuid().ToString();
@ -89,7 +91,9 @@ namespace Ocelot.UnitTests.RequestId
var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder() new ReRouteBuilder()
.WithDownstreamPathTemplate("any old string") .WithDownstreamPathTemplate("any old string")
.WithRequestIdKey("LSRequestId").Build()); .WithRequestIdKey("LSRequestId")
.WithUpstreamHttpMethod("Get")
.Build());
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())

View File

@ -13,6 +13,7 @@ using Ocelot.Logging;
using Ocelot.QueryStrings.Middleware; using Ocelot.QueryStrings.Middleware;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.Middleware; using Ocelot.Requester.Middleware;
using Ocelot.Requester.QoS;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.Responses; using Ocelot.Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -61,7 +62,7 @@ namespace Ocelot.UnitTests.Requester
[Fact] [Fact]
public void should_call_scoped_data_repository_correctly() public void should_call_scoped_data_repository_correctly()
{ {
this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new Ocelot.Configuration.QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider())))
.And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage()))
.And(x => x.GivenTheScopedRepoReturns()) .And(x => x.GivenTheScopedRepoReturns())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())

View File

@ -0,0 +1,80 @@
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class QoSProviderFactoryTests
{
private readonly IQoSProviderFactory _factory;
private ReRoute _reRoute;
private IQoSProvider _result;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
public QoSProviderFactoryTests()
{
_logger = new Mock<IOcelotLogger>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_loggerFactory
.Setup(x => x.CreateLogger<PollyQoSProvider>())
.Returns(_logger.Object);
_factory = new QoSProviderFactory(_loggerFactory.Object);
}
[Fact]
public void should_return_no_qos_provider()
{
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod("get")
.WithIsQos(false)
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.When(x => x.WhenIGetTheQoSProvider())
.Then(x => x.ThenTheQoSProviderIsReturned<NoQoSProvider>())
.BDDfy();
}
[Fact]
public void should_return_polly_qos_provider()
{
var qosOptions = new QoSOptionsBuilder()
.WithTimeoutValue(100)
.WithDurationOfBreak(100)
.WithExceptionsAllowedBeforeBreaking(100)
.Build();
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod("get")
.WithIsQos(true)
.WithQosOptions(qosOptions)
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.When(x => x.WhenIGetTheQoSProvider())
.Then(x => x.ThenTheQoSProviderIsReturned<PollyQoSProvider>())
.BDDfy();
}
private void GivenAReRoute(ReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenIGetTheQoSProvider()
{
_result = _factory.Get(_reRoute);
}
private void ThenTheQoSProviderIsReturned<T>()
{
_result.ShouldBeOfType<T>();
}
}
}

View File

@ -0,0 +1,117 @@
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class QosProviderHouseTests
{
private IQoSProvider _qoSProvider;
private readonly QosProviderHouse _qosProviderHouse;
private Response _addResult;
private Response<IQoSProvider> _getResult;
private string _key;
public QosProviderHouseTests()
{
_qosProviderHouse = new QosProviderHouse();
}
[Fact]
public void should_store_qos_provider()
{
var key = "test";
this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider()))
.When(x => x.WhenIAddTheQoSProvider())
.Then(x => x.ThenItIsAdded())
.BDDfy();
}
[Fact]
public void should_get_qos_provider()
{
var key = "test";
this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(key))
.Then(x => x.ThenItIsReturned())
.BDDfy();
}
[Fact]
public void should_store_qos_providers_by_key()
{
var key = "test";
var keyTwo = "testTwo";
this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider()))
.And(x => x.GivenThereIsAQoSProvider(keyTwo, new FakePollyQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(key))
.Then(x => x.ThenTheQoSProviderIs<FakeQoSProvider>())
.When(x => x.WhenWeGetTheQoSProvider(keyTwo))
.Then(x => x.ThenTheQoSProviderIs<FakePollyQoSProvider>())
.BDDfy();
}
[Fact]
public void should_return_error_if_no_qos_provider_with_key()
{
this.When(x => x.WhenWeGetTheQoSProvider("test"))
.Then(x => x.ThenAnErrorIsReturned())
.BDDfy();
}
private void ThenAnErrorIsReturned()
{
_getResult.IsError.ShouldBeTrue();
_getResult.Errors[0].ShouldBeOfType<UnableToFindQoSProviderError>();
}
private void ThenTheQoSProviderIs<T>()
{
_getResult.Data.ShouldBeOfType<T>();
}
private void ThenItIsAdded()
{
_addResult.IsError.ShouldBe(false);
_addResult.ShouldBeOfType<OkResponse>();
}
private void WhenIAddTheQoSProvider()
{
_addResult = _qosProviderHouse.Add(_key, _qoSProvider);
}
private void GivenThereIsAQoSProvider(string key, IQoSProvider qoSProvider)
{
_key = key;
_qoSProvider = qoSProvider;
WhenIAddTheQoSProvider();
}
private void WhenWeGetTheQoSProvider(string key)
{
_getResult = _qosProviderHouse.Get(key);
}
private void ThenItIsReturned()
{
_getResult.Data.ShouldBe(_qoSProvider);
}
class FakeQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
class FakePollyQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
}
}

View File

@ -1,9 +1,8 @@
using System.Collections.Generic; using System;
using System.IO; using System.Collections.Generic;
using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Requester;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
@ -23,6 +22,18 @@ namespace Ocelot.UnitTests.Responder
_codeMapper = new ErrorsToHttpStatusCodeMapper(); _codeMapper = new ErrorsToHttpStatusCodeMapper();
} }
[Fact]
public void should_return_timeout()
{
this.Given(x => x.GivenThereAreErrors(new List<Error>
{
new RequestTimedOutError(new Exception())
}))
.When(x => x.WhenIGetErrorStatusCode())
.Then(x => x.ThenTheResponseIsStatusCodeIs(503))
.BDDfy();
}
[Fact] [Fact]
public void should_create_unauthenticated_response_code() public void should_create_unauthenticated_response_code()
{ {

View File

@ -1,4 +1,5 @@
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -20,7 +21,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery
[Fact] [Fact]
public void should_return_no_service_provider() public void should_return_no_service_provider()
{ {
var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter", string.Empty, 0); var serviceConfig = new ServiceProviderConfiguraionBuilder()
.WithDownstreamHost("127.0.0.1")
.WithDownstreamPort(80)
.WithUseServiceDiscovery(false)
.Build();
this.Given(x => x.GivenTheReRoute(serviceConfig)) this.Given(x => x.GivenTheReRoute(serviceConfig))
.When(x => x.WhenIGetTheServiceProvider()) .When(x => x.WhenIGetTheServiceProvider())
@ -31,7 +36,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery
[Fact] [Fact]
public void should_return_consul_service_provider() public void should_return_consul_service_provider()
{ {
var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0); var serviceConfig = new ServiceProviderConfiguraionBuilder()
.WithServiceName("product")
.WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("Consul")
.Build();
this.Given(x => x.GivenTheReRoute(serviceConfig)) this.Given(x => x.GivenTheReRoute(serviceConfig))
.When(x => x.WhenIGetTheServiceProvider()) .When(x => x.WhenIGetTheServiceProvider())