Merge branch 'develop' into CircuitBreakerPattern

This commit is contained in:
geffzhang
2017-02-07 18:41:11 +08:00
committed by GitHub
96 changed files with 3067 additions and 232 deletions

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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();
}
}

View File

@ -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;}
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}

View File

@ -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)
{

View File

@ -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; }
}
}

View 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; }
}
}

View File

@ -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>();

View File

@ -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));

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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 =>
{

View File

@ -21,6 +21,10 @@
DownstreamPathTemplateContainsSchemeError,
DownstreamPathNullOrEmptyError,
DownstreamSchemeNullOrEmptyError,
DownstreamHostNullOrEmptyError
DownstreamHostNullOrEmptyError,
ServicesAreNullError,
ServicesAreEmptyError,
UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError
}
}

View 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;
}
}
}

View 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);
}
}

View File

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

View 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);
}
}

View 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; }
}
}

View File

@ -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();
}
}
}

View 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());
}
}
}
}

View 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();
}
}
}

View 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)
{
}
}
}

View File

@ -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)
{
}
}
}

View File

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

View File

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

View File

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

View File

@ -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");
}
}
}

View File

@ -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>();
}
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View 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);
}
}
}

View 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; }
}
}

View File

@ -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);
}
}
}

View 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();
}
}

View File

@ -0,0 +1,10 @@
using System;
using Ocelot.Configuration;
namespace Ocelot.ServiceDiscovery
{
public interface IServiceDiscoveryProviderFactory
{
IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig);
}
}

View File

@ -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);
}
}
}

View File

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

View File

@ -4,7 +4,7 @@
{
public HostAndPort(string downstreamHost, int downstreamPort)
{
DownstreamHost = downstreamHost;
DownstreamHost = downstreamHost?.Trim('/');
DownstreamPort = downstreamPort;
}

View 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; }
}
}

View File

@ -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": [
]
}