mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 08:38:14 +08:00
Merge branch 'develop' into CircuitBreakerPattern
This commit is contained in:
@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder
|
||||
{
|
||||
public class ReRouteBuilder
|
||||
{
|
||||
private string _loadBalancerKey;
|
||||
private string _downstreamPathTemplate;
|
||||
private string _upstreamTemplate;
|
||||
private string _upstreamTemplatePattern;
|
||||
@ -35,12 +36,22 @@ namespace Ocelot.Configuration.Builder
|
||||
private int _exceptionsAllowedBeforeBreaking;
|
||||
private int _durationOfBreak;
|
||||
private int _timeoutValue;
|
||||
private string _loadBalancer;
|
||||
private string _serviceProviderHost;
|
||||
private int _serviceProviderPort;
|
||||
|
||||
|
||||
public ReRouteBuilder()
|
||||
{
|
||||
_additionalScopes = new List<string>();
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
||||
{
|
||||
_loadBalancer = loadBalancer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
|
||||
{
|
||||
_downstreamScheme = downstreamScheme;
|
||||
@ -213,16 +224,33 @@ namespace Ocelot.Configuration.Builder
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey)
|
||||
{
|
||||
_loadBalancerKey = loadBalancerKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost)
|
||||
{
|
||||
_serviceProviderHost = serviceProviderHost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort)
|
||||
{
|
||||
_serviceProviderPort = serviceProviderPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRoute Build()
|
||||
{
|
||||
Func<HostAndPort> downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort);
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
|
||||
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
|
||||
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
|
||||
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName,
|
||||
_useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme,
|
||||
_exceptionsAllowedBeforeBreaking,_durationOfBreak, _timeoutValue);
|
||||
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer,
|
||||
_downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort),
|
||||
_exceptionsAllowedBeforeBreaking,_durationOfBreak, _timeoutValue);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Parser;
|
||||
using Ocelot.Configuration.Validator;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Utilities;
|
||||
using Ocelot.Values;
|
||||
@ -25,22 +27,28 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
||||
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
||||
private readonly ILoadBalancerFactory _loadBalanceFactory;
|
||||
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||
|
||||
public FileOcelotConfigurationCreator(
|
||||
IOptions<FileConfiguration> options,
|
||||
IConfigurationValidator configurationValidator,
|
||||
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
||||
ILogger<FileOcelotConfigurationCreator> logger)
|
||||
ILogger<FileOcelotConfigurationCreator> logger,
|
||||
ILoadBalancerFactory loadBalancerFactory,
|
||||
ILoadBalancerHouse loadBalancerHouse)
|
||||
{
|
||||
_loadBalanceFactory = loadBalancerFactory;
|
||||
_loadBalancerHouse = loadBalancerHouse;
|
||||
_options = options;
|
||||
_configurationValidator = configurationValidator;
|
||||
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Response<IOcelotConfiguration> Create()
|
||||
public async Task<Response<IOcelotConfiguration>> Create()
|
||||
{
|
||||
var config = SetUpConfiguration();
|
||||
var config = await SetUpConfiguration();
|
||||
|
||||
return new OkResponse<IOcelotConfiguration>(config);
|
||||
}
|
||||
@ -49,7 +57,7 @@ namespace Ocelot.Configuration.Creator
|
||||
/// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see
|
||||
/// will need a refactor at some point as its crap
|
||||
/// </summary>
|
||||
private IOcelotConfiguration SetUpConfiguration()
|
||||
private async Task<IOcelotConfiguration> SetUpConfiguration()
|
||||
{
|
||||
var response = _configurationValidator.IsValid(_options.Value);
|
||||
|
||||
@ -69,64 +77,87 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
foreach (var reRoute in _options.Value.ReRoutes)
|
||||
{
|
||||
var ocelotReRoute = SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
|
||||
var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
|
||||
reRoutes.Add(ocelotReRoute);
|
||||
}
|
||||
|
||||
return new OcelotConfiguration(reRoutes);
|
||||
}
|
||||
|
||||
private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration globalConfiguration)
|
||||
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
||||
|
||||
var upstreamTemplate = BuildUpstreamTemplate(reRoute);
|
||||
var upstreamTemplate = BuildUpstreamTemplate(fileReRoute);
|
||||
|
||||
var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider);
|
||||
var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
|
||||
|
||||
var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0;
|
||||
var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||
|
||||
var isCached = reRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
|
||||
var requestIdKey = globalRequestIdConfiguration
|
||||
? globalConfiguration.RequestIdKey
|
||||
: reRoute.RequestIdKey;
|
||||
: fileReRoute.RequestIdKey;
|
||||
|
||||
var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName)
|
||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address)
|
||||
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
|
||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
|
||||
|
||||
|
||||
Func<HostAndPort> downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort);
|
||||
|
||||
//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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}";
|
||||
|
||||
ReRoute reRoute;
|
||||
|
||||
|
||||
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||
|
||||
var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName,
|
||||
fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host,
|
||||
serviceProviderPort);
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
|
||||
reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName,
|
||||
reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes,
|
||||
reRoute.AuthenticationOptions.ScopeSecret);
|
||||
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(reRoute.AddHeadersToRequest);
|
||||
var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest);
|
||||
var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest);
|
||||
var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest);
|
||||
var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest);
|
||||
var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest);
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
|
||||
fileReRoute.UpstreamTemplate,
|
||||
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
||||
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme,
|
||||
reRoute.ExceptionsAllowedBeforeBreaking, reRoute.DurationOfBreak, reRoute.TimeoutValue);
|
||||
fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)
|
||||
, fileReRoute.DownstreamScheme,
|
||||
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
|
||||
serviceProviderConfiguration, serviceProviderConfiguration ,reRoute.ExceptionsAllowedBeforeBreaking,
|
||||
reRoute.DurationOfBreak, reRoute.TimeoutValue);
|
||||
}
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
||||
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme,
|
||||
reRoute.ExceptionsAllowedBeforeBreaking, reRoute.DurationOfBreak, reRoute.TimeoutValue);
|
||||
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, serviceProviderConfiguration ,reRoute.ExceptionsAllowedBeforeBreaking,
|
||||
reRoute.DurationOfBreak, reRoute.TimeoutValue);
|
||||
}
|
||||
|
||||
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
|
||||
_loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer);
|
||||
return reRoute;
|
||||
}
|
||||
|
||||
private string BuildUpstreamTemplate(FileReRoute reRoute)
|
||||
|
@ -1,9 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public interface IOcelotConfigurationCreator
|
||||
{
|
||||
Response<IOcelotConfiguration> Create();
|
||||
Task<Response<IOcelotConfiguration>> Create();
|
||||
}
|
||||
}
|
@ -32,5 +32,6 @@ namespace Ocelot.Configuration.File
|
||||
public int ExceptionsAllowedBeforeBreaking { get; set; }
|
||||
public int DurationOfBreak { get; set; }
|
||||
public int TimeoutValue { get; set; }
|
||||
public string LoadBalancer {get;set;}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ namespace Ocelot.Configuration.File
|
||||
public class FileServiceDiscoveryProvider
|
||||
{
|
||||
public string Provider {get;set;}
|
||||
public string Address {get;set;}
|
||||
public string Host {get;set;}
|
||||
public int Port { get; set; }
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
using Ocelot.Responses;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public interface IOcelotConfigurationProvider
|
||||
{
|
||||
Response<IOcelotConfiguration> Get();
|
||||
Task<Response<IOcelotConfiguration>> Get();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ocelot.Configuration.Creator;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Responses;
|
||||
|
||||
@ -19,7 +20,7 @@ namespace Ocelot.Configuration.Provider
|
||||
_creator = creator;
|
||||
}
|
||||
|
||||
public Response<IOcelotConfiguration> Get()
|
||||
public async Task<Response<IOcelotConfiguration>> Get()
|
||||
{
|
||||
var repoConfig = _repo.Get();
|
||||
|
||||
@ -30,7 +31,7 @@ namespace Ocelot.Configuration.Provider
|
||||
|
||||
if (repoConfig.Data == null)
|
||||
{
|
||||
var creatorConfig = _creator.Create();
|
||||
var creatorConfig = await _creator.Create();
|
||||
|
||||
if (creatorConfig.IsError)
|
||||
{
|
||||
|
@ -6,13 +6,24 @@ namespace Ocelot.Configuration
|
||||
{
|
||||
public class ReRoute
|
||||
{
|
||||
public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern,
|
||||
bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties,
|
||||
List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries,
|
||||
string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery,
|
||||
string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func<HostAndPort> downstreamHostAndPort, string downstreamScheme,
|
||||
public ReRoute(DownstreamPathTemplate downstreamPathTemplate,
|
||||
string upstreamTemplate, string upstreamHttpMethod,
|
||||
string upstreamTemplatePattern,
|
||||
bool isAuthenticated, AuthenticationOptions authenticationOptions,
|
||||
List<ClaimToThing> configurationHeaderExtractorProperties,
|
||||
List<ClaimToThing> claimsToClaims,
|
||||
Dictionary<string, string> routeClaimsRequirement, bool isAuthorised,
|
||||
List<ClaimToThing> claimsToQueries,
|
||||
string requestIdKey, bool isCached, CacheOptions fileCacheOptions,
|
||||
string downstreamScheme, string loadBalancer, string downstreamHost,
|
||||
int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion,
|
||||
int exceptionsAllowedBeforeBreaking =3, int durationofBreak =8, int timeoutValue = 5000)
|
||||
{
|
||||
LoadBalancerKey = loadBalancerKey;
|
||||
ServiceProviderConfiguraion = serviceProviderConfiguraion;
|
||||
LoadBalancer = loadBalancer;
|
||||
DownstreamHost = downstreamHost;
|
||||
DownstreamPort = downstreamPort;
|
||||
DownstreamPathTemplate = downstreamPathTemplate;
|
||||
UpstreamTemplate = upstreamTemplate;
|
||||
UpstreamHttpMethod = upstreamHttpMethod;
|
||||
@ -30,17 +41,13 @@ namespace Ocelot.Configuration
|
||||
?? new List<ClaimToThing>();
|
||||
ClaimsToHeaders = configurationHeaderExtractorProperties
|
||||
?? new List<ClaimToThing>();
|
||||
ServiceName = serviceName;
|
||||
UseServiceDiscovery = useServiceDiscovery;
|
||||
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
||||
ServiceDiscoveryAddress = serviceDiscoveryAddress;
|
||||
DownstreamHostAndPort = downstreamHostAndPort;
|
||||
DownstreamScheme = downstreamScheme;
|
||||
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
||||
DurationOfBreak = durationofBreak;
|
||||
TimeoutValue = timeoutValue;
|
||||
}
|
||||
|
||||
public string LoadBalancerKey {get;private set;}
|
||||
public DownstreamPathTemplate DownstreamPathTemplate { get; private set; }
|
||||
public string UpstreamTemplate { get; private set; }
|
||||
public string UpstreamTemplatePattern { get; private set; }
|
||||
@ -55,14 +62,13 @@ namespace Ocelot.Configuration
|
||||
public string RequestIdKey { get; private set; }
|
||||
public bool IsCached { get; private set; }
|
||||
public CacheOptions FileCacheOptions { get; private set; }
|
||||
public string ServiceName { get; private set;}
|
||||
public bool UseServiceDiscovery { get; private set;}
|
||||
public string ServiceDiscoveryProvider { get; private set;}
|
||||
public string ServiceDiscoveryAddress { get; private set;}
|
||||
public Func<HostAndPort> DownstreamHostAndPort {get;private set;}
|
||||
public string DownstreamScheme {get;private set;}
|
||||
public int ExceptionsAllowedBeforeBreaking { get; private set; }
|
||||
public int DurationOfBreak { get; private set; }
|
||||
public int TimeoutValue { 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; }
|
||||
}
|
||||
}
|
25
src/Ocelot/Configuration/ServiceProviderConfiguraion.cs
Normal file
25
src/Ocelot/Configuration/ServiceProviderConfiguraion.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class ServiceProviderConfiguraion
|
||||
{
|
||||
public ServiceProviderConfiguraion(string serviceName, string downstreamHost,
|
||||
int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort)
|
||||
{
|
||||
ServiceName = serviceName;
|
||||
DownstreamHost = downstreamHost;
|
||||
DownstreamPort = downstreamPort;
|
||||
UseServiceDiscovery = useServiceDiscovery;
|
||||
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
||||
ServiceProviderHost = serviceProviderHost;
|
||||
ServiceProviderPort = serviceProviderPort;
|
||||
}
|
||||
|
||||
public string ServiceName { get; }
|
||||
public string DownstreamHost { get; }
|
||||
public int DownstreamPort { get; }
|
||||
public bool UseServiceDiscovery { get; }
|
||||
public string ServiceDiscoveryProvider { get; }
|
||||
public string ServiceProviderHost { get; private set; }
|
||||
public int ServiceProviderPort { get; private set; }
|
||||
}
|
||||
}
|
@ -23,11 +23,13 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||
using Ocelot.Headers;
|
||||
using Ocelot.Infrastructure.Claims.Parser;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.QueryStrings;
|
||||
using Ocelot.Request.Builder;
|
||||
using Ocelot.Requester;
|
||||
using Ocelot.Responder;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
@ -59,6 +61,9 @@ namespace Ocelot.DependencyInjection
|
||||
{
|
||||
services.AddMvcCore().AddJsonFormatters();
|
||||
services.AddLogging();
|
||||
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||
services.AddSingleton<IUrlBuilder, UrlBuilder>();
|
||||
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||
using Ocelot.Errors;
|
||||
@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
||||
}
|
||||
|
||||
public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
|
||||
public async Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
|
||||
{
|
||||
var configuration = _configProvider.Get();
|
||||
var configuration = await _configProvider.Get();
|
||||
|
||||
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
using Ocelot.Responses;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
{
|
||||
public interface IDownstreamRouteFinder
|
||||
{
|
||||
Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
|
||||
Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||
|
||||
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath);
|
||||
|
||||
var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
|
||||
var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
|
||||
|
||||
if (downstreamRoute.IsError)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
{
|
||||
@ -45,8 +46,8 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
}
|
||||
|
||||
var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme;
|
||||
|
||||
var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort();
|
||||
|
||||
var dsHostAndPort = HostAndPort;
|
||||
|
||||
var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort);
|
||||
|
||||
|
@ -41,13 +41,13 @@ namespace Ocelot.Errors.Middleware
|
||||
|
||||
var message = CreateMessage(context, e);
|
||||
_logger.LogError(message, e);
|
||||
await SetInternalServerErrorOnResponse(context);
|
||||
SetInternalServerErrorOnResponse(context);
|
||||
}
|
||||
|
||||
_logger.LogDebug("ocelot pipeline finished");
|
||||
}
|
||||
|
||||
private async Task SetInternalServerErrorOnResponse(HttpContext context)
|
||||
private void SetInternalServerErrorOnResponse(HttpContext context)
|
||||
{
|
||||
context.Response.OnStarting(x =>
|
||||
{
|
||||
|
@ -21,6 +21,10 @@
|
||||
DownstreamPathTemplateContainsSchemeError,
|
||||
DownstreamPathNullOrEmptyError,
|
||||
DownstreamSchemeNullOrEmptyError,
|
||||
DownstreamHostNullOrEmptyError
|
||||
DownstreamHostNullOrEmptyError,
|
||||
ServicesAreNullError,
|
||||
ServicesAreEmptyError,
|
||||
UnableToFindServiceDiscoveryProviderError,
|
||||
UnableToFindLoadBalancerError
|
||||
}
|
||||
}
|
||||
|
24
src/Ocelot/Infrastructure/Extensions/StringExtensions.cs
Normal file
24
src/Ocelot/Infrastructure/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Ocelot.Infrastructure.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string TrimStart(this string source, string trim, StringComparison stringComparison = StringComparison.Ordinal)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string s = source;
|
||||
while (s.StartsWith(trim, stringComparison))
|
||||
{
|
||||
s = s.Substring(trim.Length);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
13
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs
Normal file
13
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public interface ILoadBalancer
|
||||
{
|
||||
Task<Response<HostAndPort>> Lease();
|
||||
void Release(HostAndPort hostAndPort);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public interface ILoadBalancerFactory
|
||||
{
|
||||
Task<ILoadBalancer> Get(ReRoute reRoute);
|
||||
}
|
||||
}
|
10
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs
Normal file
10
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public interface ILoadBalancerHouse
|
||||
{
|
||||
Response<ILoadBalancer> Get(string key);
|
||||
Response Add(string key, ILoadBalancer loadBalancer);
|
||||
}
|
||||
}
|
15
src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs
Normal file
15
src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class Lease
|
||||
{
|
||||
public Lease(HostAndPort hostAndPort, int connections)
|
||||
{
|
||||
HostAndPort = hostAndPort;
|
||||
Connections = connections;
|
||||
}
|
||||
public HostAndPort HostAndPort { get; private set; }
|
||||
public int Connections { get; private set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
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 LeastConnectionLoadBalancer : 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 LeastConnectionLoadBalancer(Func<Task<List<Service>>> services, string serviceName)
|
||||
{
|
||||
_services = services;
|
||||
_serviceName = serviceName;
|
||||
_leases = new List<Lease>();
|
||||
}
|
||||
|
||||
public async Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
var services = await _services.Invoke();
|
||||
|
||||
if (services == null)
|
||||
{
|
||||
return new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for {_serviceName}") });
|
||||
}
|
||||
|
||||
if (!services.Any())
|
||||
{
|
||||
return new ErrorResponse<HostAndPort>(new List<Error>() { 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<HostAndPort>(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
|
||||
}
|
||||
}
|
||||
|
||||
public void Release(HostAndPort 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.ToString() == service.HostAndPort.ToString());
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs
Normal file
39
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs
Normal file
@ -0,0 +1,39 @@
|
||||
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(ReRoute reRoute)
|
||||
{
|
||||
var serviceConfig = new 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)
|
||||
{
|
||||
case "RoundRobin":
|
||||
return new RoundRobinLoadBalancer(await serviceProvider.Get());
|
||||
case "LeastConnection":
|
||||
return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName);
|
||||
default:
|
||||
return new NoLoadBalancer(await serviceProvider.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs
Normal file
45
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class LoadBalancerHouse : ILoadBalancerHouse
|
||||
{
|
||||
private readonly Dictionary<string, ILoadBalancer> _loadBalancers;
|
||||
|
||||
public LoadBalancerHouse()
|
||||
{
|
||||
_loadBalancers = new Dictionary<string, ILoadBalancer>();
|
||||
}
|
||||
|
||||
public Response<ILoadBalancer> Get(string key)
|
||||
{
|
||||
ILoadBalancer loadBalancer;
|
||||
|
||||
if(_loadBalancers.TryGetValue(key, out loadBalancer))
|
||||
{
|
||||
return new OkResponse<ILoadBalancer>(_loadBalancers[key]);
|
||||
}
|
||||
|
||||
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||
{
|
||||
new UnableToFindLoadBalancerError($"unabe to find load balancer for {key}")
|
||||
});
|
||||
}
|
||||
|
||||
public Response Add(string key, ILoadBalancer loadBalancer)
|
||||
{
|
||||
if (!_loadBalancers.ContainsKey(key))
|
||||
{
|
||||
_loadBalancers.Add(key, loadBalancer);
|
||||
}
|
||||
|
||||
_loadBalancers.Remove(key);
|
||||
_loadBalancers.Add(key, loadBalancer);
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
}
|
28
src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs
Normal file
28
src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs
Normal file
@ -0,0 +1,28 @@
|
||||
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<HostAndPort>> Lease()
|
||||
{
|
||||
var service = await Task.FromResult(_services.FirstOrDefault());
|
||||
return new OkResponse<HostAndPort>(service.HostAndPort);
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class RoundRobinLoadBalancer : ILoadBalancer
|
||||
{
|
||||
private readonly List<Service> _services;
|
||||
private int _last;
|
||||
|
||||
public RoundRobinLoadBalancer(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
if (_last >= _services.Count)
|
||||
{
|
||||
_last = 0;
|
||||
}
|
||||
|
||||
var next = await Task.FromResult(_services[_last]);
|
||||
_last++;
|
||||
return new OkResponse<HostAndPort>(next.HostAndPort);
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class ServicesAreEmptyError : Error
|
||||
{
|
||||
public ServicesAreEmptyError(string message)
|
||||
: base(message, OcelotErrorCode.ServicesAreEmptyError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class ServicesAreNullError : Error
|
||||
{
|
||||
public ServicesAreNullError(string message)
|
||||
: base(message, OcelotErrorCode.ServicesAreNullError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class UnableToFindLoadBalancerError : Errors.Error
|
||||
{
|
||||
public UnableToFindLoadBalancerError(string message)
|
||||
: base(message, OcelotErrorCode.UnableToFindLoadBalancerError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.QueryStrings.Middleware;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
|
||||
namespace Ocelot.LoadBalancer.Middleware
|
||||
{
|
||||
public class LoadBalancingMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||
|
||||
public LoadBalancingMiddleware(RequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IRequestScopedDataRepository requestScopedDataRepository,
|
||||
ILoadBalancerHouse loadBalancerHouse)
|
||||
: base(requestScopedDataRepository)
|
||||
{
|
||||
_next = next;
|
||||
_logger = loggerFactory.CreateLogger<QueryStringBuilderMiddleware>();
|
||||
_loadBalancerHouse = loadBalancerHouse;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
_logger.LogDebug("started calling load balancing middleware");
|
||||
|
||||
var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey);
|
||||
if(loadBalancer.IsError)
|
||||
{
|
||||
SetPipelineError(loadBalancer.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
var hostAndPort = await loadBalancer.Data.Lease();
|
||||
if(hostAndPort.IsError)
|
||||
{
|
||||
SetPipelineError(hostAndPort.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
SetHostAndPortForThisRequest(hostAndPort.Data);
|
||||
|
||||
_logger.LogDebug("calling next middleware");
|
||||
|
||||
try
|
||||
{
|
||||
await _next.Invoke(context);
|
||||
|
||||
loadBalancer.Data.Release(hostAndPort.Data);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
loadBalancer.Data.Release(hostAndPort.Data);
|
||||
|
||||
_logger.LogDebug("error calling next middleware, exception will be thrown to global handler");
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogDebug("succesfully called next middleware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Ocelot.LoadBalancer.Middleware
|
||||
{
|
||||
public static class LoadBalancingMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<LoadBalancingMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Net.Http;
|
||||
using Ocelot.DownstreamRouteFinder;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
@ -69,6 +70,20 @@ namespace Ocelot.Middleware
|
||||
}
|
||||
}
|
||||
|
||||
public HostAndPort HostAndPort
|
||||
{
|
||||
get
|
||||
{
|
||||
var hostAndPort = _requestScopedDataRepository.Get<HostAndPort>("HostAndPort");
|
||||
return hostAndPort.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHostAndPortForThisRequest(HostAndPort hostAndPort)
|
||||
{
|
||||
_requestScopedDataRepository.Add("HostAndPort", hostAndPort);
|
||||
}
|
||||
|
||||
public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute)
|
||||
{
|
||||
_requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute);
|
||||
|
@ -18,6 +18,8 @@ namespace Ocelot.Middleware
|
||||
using System.Threading.Tasks;
|
||||
using Authorisation.Middleware;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Ocelot.LoadBalancer.Middleware;
|
||||
|
||||
public static class OcelotMiddlewareExtensions
|
||||
{
|
||||
@ -28,6 +30,7 @@ namespace Ocelot.Middleware
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder)
|
||||
{
|
||||
CreateConfiguration(builder);
|
||||
builder.UseOcelot(new OcelotMiddlewareConfiguration());
|
||||
return builder;
|
||||
}
|
||||
@ -40,6 +43,8 @@ namespace Ocelot.Middleware
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
|
||||
{
|
||||
CreateConfiguration(builder);
|
||||
|
||||
// This is registered to catch any global exceptions that are not handled
|
||||
builder.UseExceptionHandlerMiddleware();
|
||||
|
||||
@ -98,6 +103,9 @@ namespace Ocelot.Middleware
|
||||
// Now we can run any query string transformation logic
|
||||
builder.UseQueryStringBuilderMiddleware();
|
||||
|
||||
// Get the load balancer for this request
|
||||
builder.UseLoadBalancingMiddleware();
|
||||
|
||||
// This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
|
||||
builder.UseDownstreamUrlCreatorMiddleware();
|
||||
|
||||
@ -114,6 +122,18 @@ namespace Ocelot.Middleware
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void CreateConfiguration(IApplicationBuilder builder)
|
||||
{
|
||||
var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider));
|
||||
|
||||
var config = configProvider.Get();
|
||||
|
||||
if(config == null)
|
||||
{
|
||||
throw new Exception("Unable to start Ocelot: configuration was null");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UseIfNotNull(this IApplicationBuilder builder, Func<HttpContext, Func<Task>, Task> middleware)
|
||||
{
|
||||
if (middleware != null)
|
||||
|
@ -24,7 +24,7 @@ namespace Ocelot.Responder
|
||||
_removeOutputHeaders = removeOutputHeaders;
|
||||
}
|
||||
|
||||
public async Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
||||
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
||||
{
|
||||
_removeOutputHeaders.Remove(response.Headers);
|
||||
|
||||
@ -56,7 +56,6 @@ namespace Ocelot.Responder
|
||||
{
|
||||
await stream.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair<string, IEnumerable<string>> httpResponseHeader)
|
||||
@ -67,14 +66,13 @@ namespace Ocelot.Responder
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Response> SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||
public void SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||
{
|
||||
context.Response.OnStarting(x =>
|
||||
{
|
||||
context.Response.StatusCode = statusCode;
|
||||
return Task.CompletedTask;
|
||||
}, context);
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Responder
|
||||
{
|
||||
public interface IHttpResponder
|
||||
{
|
||||
Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response);
|
||||
Task<Response> SetErrorResponseOnContext(HttpContext context, int statusCode);
|
||||
Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response);
|
||||
void SetErrorResponseOnContext(HttpContext context, int statusCode);
|
||||
}
|
||||
}
|
||||
|
@ -46,34 +46,27 @@ namespace Ocelot.Responder.Middleware
|
||||
|
||||
_logger.LogDebug("received errors setting error response");
|
||||
|
||||
await SetErrorResponse(context, errors);
|
||||
SetErrorResponse(context, errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("no pipeline error, setting response");
|
||||
|
||||
var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
|
||||
|
||||
if (setResponse.IsError)
|
||||
{
|
||||
_logger.LogDebug("error setting response, returning error to client");
|
||||
|
||||
await SetErrorResponse(context, setResponse.Errors);
|
||||
}
|
||||
await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetErrorResponse(HttpContext context, List<Error> errors)
|
||||
private void SetErrorResponse(HttpContext context, List<Error> errors)
|
||||
{
|
||||
var statusCode = _codeMapper.Map(errors);
|
||||
|
||||
if (!statusCode.IsError)
|
||||
{
|
||||
await _responder.SetErrorResponseOnContext(context, statusCode.Data);
|
||||
_responder.SetErrorResponseOnContext(context, statusCode.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _responder.SetErrorResponseOnContext(context, 500);
|
||||
_responder.SetErrorResponseOnContext(context, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs
Normal file
21
src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ConfigurationServiceProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly List<Service> _services;
|
||||
|
||||
public ConfigurationServiceProvider(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<List<Service>> Get()
|
||||
{
|
||||
return await Task.FromResult(_services);
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs
Normal file
16
src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ConsulRegistryConfiguration
|
||||
{
|
||||
public ConsulRegistryConfiguration(string hostName, int port, string serviceName)
|
||||
{
|
||||
HostName = hostName;
|
||||
Port = port;
|
||||
ServiceName = serviceName;
|
||||
}
|
||||
|
||||
public string ServiceName { get; private set; }
|
||||
public string HostName { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly ConsulRegistryConfiguration _configuration;
|
||||
private readonly ConsulClient _consul;
|
||||
private const string VersionPrefix = "version-";
|
||||
|
||||
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration)
|
||||
{
|
||||
var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName;
|
||||
var consulPort = consulRegistryConfiguration?.Port ?? 8500;
|
||||
_configuration = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.ServiceName);
|
||||
|
||||
_consul = new ConsulClient(config =>
|
||||
{
|
||||
config.Address = new Uri($"http://{_configuration.HostName}:{_configuration.Port}");
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<Service>> Get()
|
||||
{
|
||||
var queryResult = await _consul.Health.Service(_configuration.ServiceName, string.Empty, true);
|
||||
|
||||
var services = queryResult.Response.Select(BuildService);
|
||||
|
||||
return services.ToList();
|
||||
}
|
||||
|
||||
private Service BuildService(ServiceEntry serviceEntry)
|
||||
{
|
||||
return new Service(
|
||||
serviceEntry.Service.Service,
|
||||
new HostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
|
||||
serviceEntry.Service.ID,
|
||||
GetVersionFromStrings(serviceEntry.Service.Tags),
|
||||
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
private string GetVersionFromStrings(IEnumerable<string> strings)
|
||||
{
|
||||
return strings
|
||||
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
|
||||
.TrimStart(VersionPrefix);
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs
Normal file
11
src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public interface IServiceDiscoveryProvider
|
||||
{
|
||||
Task<List<Service>> Get();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using Ocelot.Configuration;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public interface IServiceDiscoveryProviderFactory
|
||||
{
|
||||
IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
||||
{
|
||||
public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig)
|
||||
{
|
||||
if (serviceConfig.UseServiceDiscovery)
|
||||
{
|
||||
return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort);
|
||||
}
|
||||
|
||||
var services = new List<Service>()
|
||||
{
|
||||
new Service(serviceConfig.ServiceName,
|
||||
new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort),
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
new string[0])
|
||||
};
|
||||
|
||||
return new ConfigurationServiceProvider(services);
|
||||
}
|
||||
|
||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName, string providerHostName, int providerPort)
|
||||
{
|
||||
var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, serviceName);
|
||||
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class UnableToFindServiceDiscoveryProviderError : Error
|
||||
{
|
||||
public UnableToFindServiceDiscoveryProviderError(string message)
|
||||
: base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
{
|
||||
public HostAndPort(string downstreamHost, int downstreamPort)
|
||||
{
|
||||
DownstreamHost = downstreamHost;
|
||||
DownstreamHost = downstreamHost?.Trim('/');
|
||||
DownstreamPort = downstreamPort;
|
||||
}
|
||||
|
||||
|
29
src/Ocelot/Values/Service.cs
Normal file
29
src/Ocelot/Values/Service.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Values
|
||||
{
|
||||
public class Service
|
||||
{
|
||||
public Service(string name,
|
||||
HostAndPort hostAndPort,
|
||||
string id,
|
||||
string version,
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
Name = name;
|
||||
HostAndPort = hostAndPort;
|
||||
Id = id;
|
||||
Version = version;
|
||||
Tags = tags;
|
||||
}
|
||||
public string Id { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Version { get; private set; }
|
||||
|
||||
public IEnumerable<string> Tags { get; private set; }
|
||||
|
||||
public HostAndPort HostAndPort { get; private set; }
|
||||
}
|
||||
}
|
@ -1,42 +1,42 @@
|
||||
{
|
||||
"version": "1.0.0-*",
|
||||
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
|
||||
"Microsoft.Extensions.Configuration.Json": "1.1.0",
|
||||
"Microsoft.Extensions.Logging": "1.1.0",
|
||||
"Microsoft.Extensions.Logging.Console": "1.1.0",
|
||||
"Microsoft.Extensions.Logging.Debug": "1.1.0",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
|
||||
"Microsoft.AspNetCore.Http": "1.1.0",
|
||||
"System.Text.RegularExpressions": "4.3.0",
|
||||
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Google": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Facebook": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Twitter": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication": "1.1.0",
|
||||
"IdentityServer4.AccessTokenValidation": "1.0.2",
|
||||
"Microsoft.AspNetCore.Mvc": "1.1.0",
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
|
||||
"Microsoft.NETCore.App": "1.1.0",
|
||||
"CacheManager.Core": "0.9.2",
|
||||
"CacheManager.Microsoft.Extensions.Configuration": "0.9.2",
|
||||
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
|
||||
"Polly": "5.0.3"
|
||||
},
|
||||
"version": "0.0.0-dev",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
|
||||
"Microsoft.Extensions.Configuration.Json": "1.1.0",
|
||||
"Microsoft.Extensions.Logging": "1.1.0",
|
||||
"Microsoft.Extensions.Logging.Console": "1.1.0",
|
||||
"Microsoft.Extensions.Logging.Debug": "1.1.0",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
|
||||
"Microsoft.AspNetCore.Http": "1.1.0",
|
||||
"System.Text.RegularExpressions": "4.3.0",
|
||||
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Google": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Facebook": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.Twitter": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0",
|
||||
"Microsoft.AspNetCore.Authentication": "1.1.0",
|
||||
"IdentityServer4.AccessTokenValidation": "1.0.2",
|
||||
"Microsoft.AspNetCore.Mvc": "1.1.0",
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
|
||||
"Microsoft.NETCore.App": "1.1.0",
|
||||
"CacheManager.Core": "0.9.2",
|
||||
"CacheManager.Microsoft.Extensions.Configuration": "0.9.2",
|
||||
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
|
||||
"Consul": "0.7.2.1",
|
||||
"Polly": "5.0.3"
|
||||
},
|
||||
"runtimes": {
|
||||
"win10-x64": {},
|
||||
"osx.10.11-x64":{},
|
||||
"win7-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.4": {
|
||||
"netcoreapp1.1": {
|
||||
"imports": [
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user