Feature/sticky sessions (#336)

* started messing around with sticky sessions idea

* more tests for sticky session thing

* more faffing cant make up my mind how to do this

* +semver: breaking added sticky session load balancer and changed way load balancer configuration is set by user

* #336 made tests BDDFy
This commit is contained in:
Tom Pallister
2018-04-30 18:55:11 +01:00
committed by GitHub
parent 97e7d32d14
commit 6793278597
35 changed files with 4776 additions and 3801 deletions

View File

@ -24,7 +24,7 @@ namespace Ocelot.Configuration.Builder
private bool _isCached;
private CacheOptions _fileCacheOptions;
private string _downstreamScheme;
private string _loadBalancer;
private LoadBalancerOptions _loadBalancerOptions;
private bool _useQos;
private QoSOptions _qosOptions;
private HttpHandlerOptions _httpHandlerOptions;
@ -41,6 +41,7 @@ namespace Ocelot.Configuration.Builder
private List<AddHeader> _addHeadersToDownstream;
private List<AddHeader> _addHeadersToUpstream;
private bool _dangerousAcceptAnyServerCertificateValidator;
private string _qosKey;
public DownstreamReRouteBuilder()
{
@ -62,9 +63,9 @@ namespace Ocelot.Configuration.Builder
return this;
}
public DownstreamReRouteBuilder WithLoadBalancer(string loadBalancer)
public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions)
{
_loadBalancer = loadBalancer;
_loadBalancerOptions = loadBalancerOptions;
return this;
}
@ -170,6 +171,12 @@ namespace Ocelot.Configuration.Builder
return this;
}
public DownstreamReRouteBuilder WithQosKey(string qosKey)
{
_qosKey = qosKey;
return this;
}
public DownstreamReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions)
{
_authenticationOptions = authenticationOptions;
@ -266,7 +273,7 @@ namespace Ocelot.Configuration.Builder
_requestIdHeaderKey,
_isCached,
_fileCacheOptions,
_loadBalancer,
_loadBalancerOptions,
_rateLimitOptions,
_routeClaimRequirement,
_claimToQueries,
@ -280,7 +287,8 @@ namespace Ocelot.Configuration.Builder
_delegatingHandlers,
_addHeadersToDownstream,
_addHeadersToUpstream,
_dangerousAcceptAnyServerCertificateValidator);
_dangerousAcceptAnyServerCertificateValidator,
_qosKey);
}
}
}

View File

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

View File

@ -1,42 +1,43 @@
using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using Ocelot.Values;
namespace Ocelot.Configuration
{
using System.Collections.Generic;
using Creator;
using Values;
public class DownstreamReRoute
{
public DownstreamReRoute(
string key,
PathTemplate upstreamPathTemplate,
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
List<DownstreamHostAndPort> downstreamAddresses,
string serviceName,
HttpHandlerOptions httpHandlerOptions,
bool useServiceDiscovery,
bool enableEndpointEndpointRateLimiting,
bool isQos,
QoSOptions qosOptionsOptions,
string downstreamScheme,
string requestIdKey,
bool isCached,
CacheOptions cacheOptions,
string loadBalancer,
RateLimitOptions rateLimitOptions,
Dictionary<string, string> routeClaimsRequirement,
List<ClaimToThing> claimsToQueries,
List<ClaimToThing> claimsToHeaders,
List<ClaimToThing> claimsToClaims,
bool isAuthenticated,
bool isAuthorised,
AuthenticationOptions authenticationOptions,
PathTemplate downstreamPathTemplate,
string reRouteKey,
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
List<DownstreamHostAndPort> downstreamAddresses,
string serviceName,
HttpHandlerOptions httpHandlerOptions,
bool useServiceDiscovery,
bool enableEndpointEndpointRateLimiting,
bool isQos,
QoSOptions qosOptionsOptions,
string downstreamScheme,
string requestIdKey,
bool isCached,
CacheOptions cacheOptions,
LoadBalancerOptions loadBalancerOptions,
RateLimitOptions rateLimitOptions,
Dictionary<string, string> routeClaimsRequirement,
List<ClaimToThing> claimsToQueries,
List<ClaimToThing> claimsToHeaders,
List<ClaimToThing> claimsToClaims,
bool isAuthenticated,
bool isAuthorised,
AuthenticationOptions authenticationOptions,
PathTemplate downstreamPathTemplate,
string loadBalancerKey,
List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream,
bool dangerousAcceptAnyServerCertificateValidator)
bool dangerousAcceptAnyServerCertificateValidator,
string qosKey)
{
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream;
@ -56,7 +57,7 @@ namespace Ocelot.Configuration
RequestIdKey = requestIdKey;
IsCached = isCached;
CacheOptions = cacheOptions;
LoadBalancer = loadBalancer;
LoadBalancerOptions = loadBalancerOptions;
RateLimitOptions = rateLimitOptions;
RouteClaimsRequirement = routeClaimsRequirement;
ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
@ -66,39 +67,41 @@ namespace Ocelot.Configuration
IsAuthorised = isAuthorised;
AuthenticationOptions = authenticationOptions;
DownstreamPathTemplate = downstreamPathTemplate;
ReRouteKey = reRouteKey;
LoadBalancerKey = loadBalancerKey;
AddHeadersToUpstream = addHeadersToUpstream;
QosKey = qosKey;
}
public string Key { get; private set; }
public PathTemplate UpstreamPathTemplate { get;private set; }
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;}
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; private set; }
public List<DownstreamHostAndPort> DownstreamAddresses { get; private set; }
public string ServiceName { get; private set; }
public HttpHandlerOptions HttpHandlerOptions { get; private set; }
public bool UseServiceDiscovery { get; private set; }
public bool EnableEndpointEndpointRateLimiting { get; private set; }
public bool IsQos { get; private set; }
public QoSOptions QosOptionsOptions { get; private set; }
public string DownstreamScheme { get; private set; }
public string RequestIdKey { get; private set; }
public bool IsCached { get; private set; }
public CacheOptions CacheOptions { get; private set; }
public string LoadBalancer { get; private set; }
public RateLimitOptions RateLimitOptions { get; private set; }
public Dictionary<string, string> RouteClaimsRequirement { 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 bool IsAuthenticated { get; private set; }
public bool IsAuthorised { get; private set; }
public AuthenticationOptions AuthenticationOptions { get; private set; }
public PathTemplate DownstreamPathTemplate { get; private set; }
public string ReRouteKey { get; private set; }
public List<string> DelegatingHandlers {get;private set;}
public List<AddHeader> AddHeadersToDownstream {get;private set;}
public List<AddHeader> AddHeadersToUpstream { get; private set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; private set; }
public string QosKey { get; }
public string Key { get; }
public PathTemplate UpstreamPathTemplate { get; }
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace { get; }
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; }
public List<DownstreamHostAndPort> DownstreamAddresses { get; }
public string ServiceName { get; }
public HttpHandlerOptions HttpHandlerOptions { get; }
public bool UseServiceDiscovery { get; }
public bool EnableEndpointEndpointRateLimiting { get; }
public bool IsQos { get; }
public QoSOptions QosOptionsOptions { get; }
public string DownstreamScheme { get; }
public string RequestIdKey { get; }
public bool IsCached { get; }
public CacheOptions CacheOptions { get; }
public LoadBalancerOptions LoadBalancerOptions { get; }
public RateLimitOptions RateLimitOptions { get; }
public Dictionary<string, string> RouteClaimsRequirement { get; }
public List<ClaimToThing> ClaimsToQueries { get; }
public List<ClaimToThing> ClaimsToHeaders { get; }
public List<ClaimToThing> ClaimsToClaims { get; }
public bool IsAuthenticated { get; }
public bool IsAuthorised { get; }
public AuthenticationOptions AuthenticationOptions { get; }
public PathTemplate DownstreamPathTemplate { get; }
public string LoadBalancerKey { get; }
public List<string> DelegatingHandlers { get; }
public List<AddHeader> AddHeadersToDownstream { get; }
public List<AddHeader> AddHeadersToUpstream { get; }
public bool DangerousAcceptAnyServerCertificateValidator { get; }
}
}

View File

@ -0,0 +1,9 @@
namespace Ocelot.Configuration.File
{
public class FileLoadBalancerOptions
{
public string Type { get; set; }
public string Key { get; set; }
public int Expiry { get; set; }
}
}

View File

@ -1,54 +1,55 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.File
{
public class FileReRoute : IReRoute
{
public FileReRoute()
{
UpstreamHttpMethod = new List<string>();
AddHeadersToRequest = new Dictionary<string, string>();
AddClaimsToRequest = new Dictionary<string, string>();
RouteClaimsRequirement = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
DownstreamHeaderTransform = new Dictionary<string, string>();
FileCacheOptions = new FileCacheOptions();
QoSOptions = new FileQoSOptions();
RateLimitOptions = new FileRateLimitRule();
AuthenticationOptions = new FileAuthenticationOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
UpstreamHeaderTransform = new Dictionary<string, string>();
DownstreamHostAndPorts = new List<FileHostAndPort>();
DelegatingHandlers = new List<string>();
Priority = 1;
}
public string DownstreamPathTemplate { get; set; }
public string UpstreamPathTemplate { get; set; }
public List<string> UpstreamHttpMethod { get; set; }
public Dictionary<string, string> AddHeadersToRequest { get; set; }
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
public Dictionary<string, string> AddClaimsToRequest { get; set; }
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
public Dictionary<string, string> AddQueriesToRequest { get; set; }
public string RequestIdKey { get; set; }
public FileCacheOptions FileCacheOptions { get; set; }
public bool ReRouteIsCaseSensitive { get; set; }
public string ServiceName { get; set; }
public string DownstreamScheme {get;set;}
public FileQoSOptions QoSOptions { get; set; }
public string LoadBalancer { get;set; }
public FileRateLimitRule RateLimitOptions { get; set; }
public FileAuthenticationOptions AuthenticationOptions { get; set; }
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public bool UseServiceDiscovery { get;set; }
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
public string UpstreamHost { get; set; }
public string Key { get;set; }
public List<string> DelegatingHandlers {get;set;}
public int Priority { get;set; }
public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
}
}
using System.Collections.Generic;
namespace Ocelot.Configuration.File
{
public class FileReRoute : IReRoute
{
public FileReRoute()
{
UpstreamHttpMethod = new List<string>();
AddHeadersToRequest = new Dictionary<string, string>();
AddClaimsToRequest = new Dictionary<string, string>();
RouteClaimsRequirement = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
DownstreamHeaderTransform = new Dictionary<string, string>();
FileCacheOptions = new FileCacheOptions();
QoSOptions = new FileQoSOptions();
RateLimitOptions = new FileRateLimitRule();
AuthenticationOptions = new FileAuthenticationOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
UpstreamHeaderTransform = new Dictionary<string, string>();
DownstreamHostAndPorts = new List<FileHostAndPort>();
DelegatingHandlers = new List<string>();
LoadBalancerOptions = new FileLoadBalancerOptions();
Priority = 1;
}
public string DownstreamPathTemplate { get; set; }
public string UpstreamPathTemplate { get; set; }
public List<string> UpstreamHttpMethod { get; set; }
public Dictionary<string, string> AddHeadersToRequest { get; set; }
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
public Dictionary<string, string> AddClaimsToRequest { get; set; }
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
public Dictionary<string, string> AddQueriesToRequest { get; set; }
public string RequestIdKey { get; set; }
public FileCacheOptions FileCacheOptions { get; set; }
public bool ReRouteIsCaseSensitive { get; set; }
public string ServiceName { get; set; }
public string DownstreamScheme {get;set;}
public FileQoSOptions QoSOptions { get; set; }
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
public FileRateLimitRule RateLimitOptions { get; set; }
public FileAuthenticationOptions AuthenticationOptions { get; set; }
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public bool UseServiceDiscovery { get;set; }
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
public string UpstreamHost { get; set; }
public string Key { get;set; }
public List<string> DelegatingHandlers {get;set;}
public int Priority { get;set; }
public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
}
}

View File

@ -0,0 +1,18 @@
namespace Ocelot.Configuration
{
public class LoadBalancerOptions
{
public LoadBalancerOptions(string type, string key, int expiryInMs)
{
Type = type;
Key = key;
ExpiryInMs = expiryInMs;
}
public string Type { get; }
public string Key { get; }
public int ExpiryInMs { get; }
}
}

View File

@ -0,0 +1,93 @@
namespace Ocelot.LoadBalancer.LoadBalancers
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Ocelot.Middleware;
using Responses;
using Values;
public class CookieStickySessions : ILoadBalancer, IDisposable
{
private readonly int _expiryInMs;
private readonly string _key;
private readonly ILoadBalancer _loadBalancer;
private readonly ConcurrentDictionary<string, StickySession> _stored;
private readonly Timer _timer;
private bool _expiring;
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int expiryInMs)
{
_key = key;
_expiryInMs = expiryInMs;
_loadBalancer = loadBalancer;
_stored = new ConcurrentDictionary<string, StickySession>();
_timer = new Timer(x =>
{
if (_expiring)
{
return;
}
_expiring = true;
Expire();
_expiring = false;
}, null, 0, 50);
}
public void Dispose()
{
_timer?.Dispose();
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
{
var value = context.HttpContext.Request.Cookies[_key];
if (!string.IsNullOrEmpty(value) && _stored.ContainsKey(value))
{
var cached = _stored[value];
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
_stored[value] = updated;
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
}
var next = await _loadBalancer.Lease(context);
if (next.IsError)
{
return new ErrorResponse<ServiceHostAndPort>(next.Errors);
}
if (!string.IsNullOrEmpty(value) && !_stored.ContainsKey(value))
{
_stored[value] = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
}
return new OkResponse<ServiceHostAndPort>(next.Data);
}
public void Release(ServiceHostAndPort hostAndPort)
{
}
private void Expire()
{
var expired = _stored.Where(x => x.Value.Expiry < DateTime.UtcNow);
foreach (var expire in expired)
{
_stored.Remove(expire.Key, out _);
_loadBalancer.Release(expire.Value.HostAndPort);
}
}
}
}

View File

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

View File

@ -1,10 +1,10 @@
using System.Threading.Tasks;
using Ocelot.Configuration;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public interface ILoadBalancerFactory
{
Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
}
}
using System.Threading.Tasks;
using Ocelot.Configuration;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public interface ILoadBalancerFactory
{
Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
}
}

View File

@ -1,11 +1,11 @@
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public interface ILoadBalancerHouse
{
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
}
}
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public interface ILoadBalancerHouse
{
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
}
}

View File

@ -1,145 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Errors;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class LeastConnection : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
private readonly List<Lease> _leases;
private readonly string _serviceName;
private static readonly object _syncLock = new object();
public LeastConnection(Func<Task<List<Service>>> services, string serviceName)
{
_services = services;
_serviceName = serviceName;
_leases = new List<Lease>();
}
public async Task<Response<ServiceHostAndPort>> Lease()
{
var services = await _services.Invoke();
if (services == null)
{
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreNullError($"services were null for {_serviceName}") );
}
if (!services.Any())
{
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError($"services were empty for {_serviceName}"));
}
lock(_syncLock)
{
//todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something?
UpdateServices(services);
var leaseWithLeastConnections = GetLeaseWithLeastConnections();
_leases.Remove(leaseWithLeastConnections);
leaseWithLeastConnections = AddConnection(leaseWithLeastConnections);
_leases.Add(leaseWithLeastConnections);
return new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
}
}
public void Release(ServiceHostAndPort hostAndPort)
{
lock(_syncLock)
{
var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost
&& l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort);
if (matchingLease != null)
{
var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1);
_leases.Remove(matchingLease);
_leases.Add(replacementLease);
}
}
}
private Lease AddConnection(Lease lease)
{
return new Lease(lease.HostAndPort, lease.Connections + 1);
}
private Lease GetLeaseWithLeastConnections()
{
//now get the service with the least connections?
Lease leaseWithLeastConnections = null;
for (var i = 0; i < _leases.Count; i++)
{
if (i == 0)
{
leaseWithLeastConnections = _leases[i];
}
else
{
if (_leases[i].Connections < leaseWithLeastConnections.Connections)
{
leaseWithLeastConnections = _leases[i];
}
}
}
return leaseWithLeastConnections;
}
private Response UpdateServices(List<Service> services)
{
if (_leases.Count > 0)
{
var leasesToRemove = new List<Lease>();
foreach (var lease in _leases)
{
var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost
&& s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort);
if (match == null)
{
leasesToRemove.Add(lease);
}
}
foreach (var lease in leasesToRemove)
{
_leases.Remove(lease);
}
foreach (var service in services)
{
var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort);
if (exists == null)
{
_leases.Add(new Lease(service.HostAndPort, 0));
}
}
}
else
{
foreach (var service in services)
{
_leases.Add(new Lease(service.HostAndPort, 0));
}
}
return new OkResponse();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Errors;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class LeastConnection : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
private readonly List<Lease> _leases;
private readonly string _serviceName;
private static readonly object _syncLock = new object();
public LeastConnection(Func<Task<List<Service>>> services, string serviceName)
{
_services = services;
_serviceName = serviceName;
_leases = new List<Lease>();
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
{
var services = await _services.Invoke();
if (services == null)
{
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreNullError($"services were null for {_serviceName}") );
}
if (!services.Any())
{
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError($"services were empty for {_serviceName}"));
}
lock(_syncLock)
{
//todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something?
UpdateServices(services);
var leaseWithLeastConnections = GetLeaseWithLeastConnections();
_leases.Remove(leaseWithLeastConnections);
leaseWithLeastConnections = AddConnection(leaseWithLeastConnections);
_leases.Add(leaseWithLeastConnections);
return new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
}
}
public void Release(ServiceHostAndPort hostAndPort)
{
lock(_syncLock)
{
var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost
&& l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort);
if (matchingLease != null)
{
var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1);
_leases.Remove(matchingLease);
_leases.Add(replacementLease);
}
}
}
private Lease AddConnection(Lease lease)
{
return new Lease(lease.HostAndPort, lease.Connections + 1);
}
private Lease GetLeaseWithLeastConnections()
{
//now get the service with the least connections?
Lease leaseWithLeastConnections = null;
for (var i = 0; i < _leases.Count; i++)
{
if (i == 0)
{
leaseWithLeastConnections = _leases[i];
}
else
{
if (_leases[i].Connections < leaseWithLeastConnections.Connections)
{
leaseWithLeastConnections = _leases[i];
}
}
}
return leaseWithLeastConnections;
}
private Response UpdateServices(List<Service> services)
{
if (_leases.Count > 0)
{
var leasesToRemove = new List<Lease>();
foreach (var lease in _leases)
{
var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost
&& s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort);
if (match == null)
{
leasesToRemove.Add(lease);
}
}
foreach (var lease in leasesToRemove)
{
_leases.Remove(lease);
}
foreach (var service in services)
{
var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort);
if (exists == null)
{
_leases.Add(new Lease(service.HostAndPort, 0));
}
}
}
else
{
foreach (var service in services)
{
_leases.Add(new Lease(service.HostAndPort, 0));
}
}
return new OkResponse();
}
}
}

View File

@ -1,30 +1,34 @@
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.ServiceDiscovery;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class LoadBalancerFactory : ILoadBalancerFactory
{
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
{
_serviceProviderFactory = serviceProviderFactory;
}
public async Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
{
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
switch (reRoute.LoadBalancer)
{
case "RoundRobin":
return new RoundRobin(async () => await serviceProvider.Get());
case "LeastConnection":
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
default:
return new NoLoadBalancer(await serviceProvider.Get());
}
}
}
}
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.ServiceDiscovery;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class LoadBalancerFactory : ILoadBalancerFactory
{
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
{
_serviceProviderFactory = serviceProviderFactory;
}
public async Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
{
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
switch (reRoute.LoadBalancerOptions?.Type)
{
case nameof(RoundRobin):
return new RoundRobin(async () => await serviceProvider.Get());
case nameof(LeastConnection):
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
case nameof(CookieStickySessions):
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs);
default:
return new NoLoadBalancer(await serviceProvider.Get());
}
}
}
}

View File

@ -1,56 +1,56 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class LoadBalancerHouse : ILoadBalancerHouse
{
private readonly ILoadBalancerFactory _factory;
private readonly ConcurrentDictionary<string, ILoadBalancer> _loadBalancers;
public LoadBalancerHouse(ILoadBalancerFactory factory)
{
_factory = factory;
_loadBalancers = new ConcurrentDictionary<string, ILoadBalancer>();
}
public async Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
{
try
{
if(_loadBalancers.TryGetValue(reRoute.ReRouteKey, out var loadBalancer))
{
loadBalancer = _loadBalancers[reRoute.ReRouteKey];
if(reRoute.LoadBalancer != loadBalancer.GetType().Name)
{
loadBalancer = await _factory.Get(reRoute, config);
AddLoadBalancer(reRoute.ReRouteKey, loadBalancer);
}
return new OkResponse<ILoadBalancer>(loadBalancer);
}
loadBalancer = await _factory.Get(reRoute, config);
AddLoadBalancer(reRoute.ReRouteKey, loadBalancer);
return new OkResponse<ILoadBalancer>(loadBalancer);
}
catch(Exception ex)
{
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
{
new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.ReRouteKey} exception is {ex}")
});
}
}
private void AddLoadBalancer(string key, ILoadBalancer loadBalancer)
{
_loadBalancers.AddOrUpdate(key, loadBalancer, (x, y) => loadBalancer);
}
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class LoadBalancerHouse : ILoadBalancerHouse
{
private readonly ILoadBalancerFactory _factory;
private readonly ConcurrentDictionary<string, ILoadBalancer> _loadBalancers;
public LoadBalancerHouse(ILoadBalancerFactory factory)
{
_factory = factory;
_loadBalancers = new ConcurrentDictionary<string, ILoadBalancer>();
}
public async Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
{
try
{
if(_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer))
{
loadBalancer = _loadBalancers[reRoute.LoadBalancerKey];
if(reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name)
{
loadBalancer = await _factory.Get(reRoute, config);
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
}
return new OkResponse<ILoadBalancer>(loadBalancer);
}
loadBalancer = await _factory.Get(reRoute, config);
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
return new OkResponse<ILoadBalancer>(loadBalancer);
}
catch(Exception ex)
{
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
{
new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.LoadBalancerKey} exception is {ex}")
});
}
}
private void AddLoadBalancer(string key, ILoadBalancer loadBalancer)
{
_loadBalancers.AddOrUpdate(key, loadBalancer, (x, y) => loadBalancer);
}
}
}

View File

@ -1,34 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class NoLoadBalancer : ILoadBalancer
{
private readonly List<Service> _services;
public NoLoadBalancer(List<Service> services)
{
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease()
{
//todo no point spinning a task up here, also first or default could be null..
if (_services == null || _services.Count == 0)
{
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError("There were no services in NoLoadBalancer"));
}
var service = await Task.FromResult(_services.FirstOrDefault());
return new OkResponse<ServiceHostAndPort>(service.HostAndPort);
}
public void Release(ServiceHostAndPort hostAndPort)
{
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class NoLoadBalancer : ILoadBalancer
{
private readonly List<Service> _services;
public NoLoadBalancer(List<Service> services)
{
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
{
//todo no point spinning a task up here, also first or default could be null..
if (_services == null || _services.Count == 0)
{
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError("There were no services in NoLoadBalancer"));
}
var service = await Task.FromResult(_services.FirstOrDefault());
return new OkResponse<ServiceHostAndPort>(service.HostAndPort);
}
public void Release(ServiceHostAndPort hostAndPort)
{
}
}
}

View File

@ -1,37 +1,38 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Responses;
using Ocelot.Values;
using System;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class RoundRobin : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
private int _last;
public RoundRobin(Func<Task<List<Service>>> services)
{
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease()
{
var services = await _services.Invoke();
if (_last >= services.Count)
{
_last = 0;
}
var next = await Task.FromResult(services[_last]);
_last++;
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
}
public void Release(ServiceHostAndPort hostAndPort)
{
}
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using Ocelot.Middleware;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class RoundRobin : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
private int _last;
public RoundRobin(Func<Task<List<Service>>> services)
{
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
{
var services = await _services.Invoke();
if (_last >= services.Count)
{
_last = 0;
}
var next = await Task.FromResult(services[_last]);
_last++;
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
}
public void Release(ServiceHostAndPort hostAndPort)
{
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using Ocelot.Values;
namespace Ocelot.LoadBalancer.LoadBalancers
{
public class StickySession
{
public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry)
{
HostAndPort = hostAndPort;
Expiry = expiry;
}
public ServiceHostAndPort HostAndPort { get; }
public DateTime Expiry { get; }
}
}

View File

@ -1,63 +1,63 @@
using System;
using System.Threading.Tasks;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
namespace Ocelot.LoadBalancer.Middleware
{
public class LoadBalancingMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly ILoadBalancerHouse _loadBalancerHouse;
public LoadBalancingMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
ILoadBalancerHouse loadBalancerHouse)
:base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
{
_next = next;
_loadBalancerHouse = loadBalancerHouse;
}
public async Task Invoke(DownstreamContext context)
{
var loadBalancer = await _loadBalancerHouse.Get(context.DownstreamReRoute, context.ServiceProviderConfiguration);
if(loadBalancer.IsError)
{
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
SetPipelineError(context, loadBalancer.Errors);
return;
}
var hostAndPort = await loadBalancer.Data.Lease();
if(hostAndPort.IsError)
{
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
SetPipelineError(context, hostAndPort.Errors);
return;
}
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
if (hostAndPort.Data.DownstreamPort > 0)
{
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
}
try
{
await _next.Invoke(context);
}
catch (Exception)
{
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
throw;
}
finally
{
loadBalancer.Data.Release(hostAndPort.Data);
}
}
}
}
using System;
using System.Threading.Tasks;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
namespace Ocelot.LoadBalancer.Middleware
{
public class LoadBalancingMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly ILoadBalancerHouse _loadBalancerHouse;
public LoadBalancingMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
ILoadBalancerHouse loadBalancerHouse)
:base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
{
_next = next;
_loadBalancerHouse = loadBalancerHouse;
}
public async Task Invoke(DownstreamContext context)
{
var loadBalancer = await _loadBalancerHouse.Get(context.DownstreamReRoute, context.ServiceProviderConfiguration);
if(loadBalancer.IsError)
{
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
SetPipelineError(context, loadBalancer.Errors);
return;
}
var hostAndPort = await loadBalancer.Data.Lease(context);
if(hostAndPort.IsError)
{
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
SetPipelineError(context, hostAndPort.Errors);
return;
}
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
if (hostAndPort.Data.DownstreamPort > 0)
{
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
}
try
{
await _next.Invoke(context);
}
catch (Exception)
{
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
throw;
}
finally
{
loadBalancer.Data.Release(hostAndPort.Data);
}
}
}
}

View File

@ -21,26 +21,26 @@ namespace Ocelot.Requester.QoS
{
try
{
if (_qoSProviders.TryGetValue(reRoute.ReRouteKey, out var qosProvider))
if (_qoSProviders.TryGetValue(reRoute.QosKey, out var qosProvider))
{
if (reRoute.IsQos && qosProvider.CircuitBreaker == null)
{
qosProvider = _qoSProviderFactory.Get(reRoute);
Add(reRoute.ReRouteKey, qosProvider);
Add(reRoute.QosKey, qosProvider);
}
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.ReRouteKey]);
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.QosKey]);
}
qosProvider = _qoSProviderFactory.Get(reRoute);
Add(reRoute.ReRouteKey, qosProvider);
Add(reRoute.QosKey, qosProvider);
return new OkResponse<IQoSProvider>(qosProvider);
}
catch (Exception ex)
{
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
{
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.ReRouteKey}, exception was {ex}")
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.QosKey}, exception was {ex}")
});
}
}