mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 14:48:14 +08:00
Merge branch 'develop' into feature/config_grow_when_merged
# Conflicts: # src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs # test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs
This commit is contained in:
@ -1,61 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Authentication.Middleware
|
||||
{
|
||||
public class AuthenticationMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
|
||||
public AuthenticationMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
: base(loggerFactory.CreateLogger<AuthenticationMiddleware>())
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
if (IsAuthenticatedRoute(context.DownstreamReRoute))
|
||||
{
|
||||
Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");
|
||||
|
||||
var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
|
||||
|
||||
context.HttpContext.User = result.Principal;
|
||||
|
||||
if (context.HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}");
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = new UnauthenticatedError(
|
||||
$"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated");
|
||||
|
||||
Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}");
|
||||
|
||||
SetPipelineError(context, error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}");
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute)
|
||||
{
|
||||
return reRoute.IsAuthenticated;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Authentication.Middleware
|
||||
{
|
||||
public class AuthenticationMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
|
||||
public AuthenticationMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
: base(loggerFactory.CreateLogger<AuthenticationMiddleware>())
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
if (IsAuthenticatedRoute(context.DownstreamReRoute))
|
||||
{
|
||||
Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");
|
||||
|
||||
var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
|
||||
|
||||
context.HttpContext.User = result.Principal;
|
||||
|
||||
if (context.HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}");
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = new UnauthenticatedError(
|
||||
$"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated");
|
||||
|
||||
Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}");
|
||||
|
||||
SetPipelineError(context, error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}");
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute)
|
||||
{
|
||||
return reRoute.IsAuthenticated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,47 @@
|
||||
using IdentityModel;
|
||||
using Ocelot.Responses;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ocelot.Authorisation
|
||||
{
|
||||
using Infrastructure.Claims.Parser;
|
||||
|
||||
public class ScopesAuthoriser : IScopesAuthoriser
|
||||
{
|
||||
private readonly IClaimsParser _claimsParser;
|
||||
|
||||
public ScopesAuthoriser(IClaimsParser claimsParser)
|
||||
{
|
||||
_claimsParser = claimsParser;
|
||||
}
|
||||
|
||||
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes)
|
||||
{
|
||||
if (routeAllowedScopes == null || routeAllowedScopes.Count == 0)
|
||||
{
|
||||
return new OkResponse<bool>(true);
|
||||
}
|
||||
|
||||
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope);
|
||||
|
||||
if (values.IsError)
|
||||
{
|
||||
return new ErrorResponse<bool>(values.Errors);
|
||||
}
|
||||
|
||||
var userScopes = values.Data;
|
||||
|
||||
var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList();
|
||||
|
||||
if (matchesScopes.Count == 0)
|
||||
{
|
||||
return new ErrorResponse<bool>(
|
||||
new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'"));
|
||||
}
|
||||
|
||||
return new OkResponse<bool>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Ocelot.Responses;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ocelot.Authorisation
|
||||
{
|
||||
using Infrastructure.Claims.Parser;
|
||||
|
||||
public class ScopesAuthoriser : IScopesAuthoriser
|
||||
{
|
||||
private readonly IClaimsParser _claimsParser;
|
||||
private readonly string _scope = "scope";
|
||||
|
||||
public ScopesAuthoriser(IClaimsParser claimsParser)
|
||||
{
|
||||
_claimsParser = claimsParser;
|
||||
}
|
||||
|
||||
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes)
|
||||
{
|
||||
if (routeAllowedScopes == null || routeAllowedScopes.Count == 0)
|
||||
{
|
||||
return new OkResponse<bool>(true);
|
||||
}
|
||||
|
||||
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope);
|
||||
|
||||
if (values.IsError)
|
||||
{
|
||||
return new ErrorResponse<bool>(values.Errors);
|
||||
}
|
||||
|
||||
var userScopes = values.Data;
|
||||
|
||||
var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList();
|
||||
|
||||
if (matchesScopes.Count == 0)
|
||||
{
|
||||
return new ErrorResponse<bool>(
|
||||
new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'"));
|
||||
}
|
||||
|
||||
return new OkResponse<bool>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Cache
|
||||
{
|
||||
@ -9,5 +8,25 @@ namespace Ocelot.Cache
|
||||
void AddAndDelete(string key, T value, TimeSpan ttl, string region);
|
||||
T Get(string key, string region);
|
||||
void ClearRegion(string region);
|
||||
}
|
||||
|
||||
public class NoCache<T> : IOcelotCache<T>
|
||||
{
|
||||
public void Add(string key, T value, TimeSpan ttl, string region)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
|
||||
{
|
||||
}
|
||||
|
||||
public void ClearRegion(string region)
|
||||
{
|
||||
}
|
||||
|
||||
public T Get(string key, string region)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using System.IO;
|
||||
using Ocelot.Middleware.Multiplexer;
|
||||
|
||||
namespace Ocelot.Cache.Middleware
|
||||
namespace Ocelot.Cache.Middleware
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using System.IO;
|
||||
|
||||
public class OutputCacheMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
private readonly IOcelotCache<CachedResponse> _outputCache;
|
||||
private readonly IRegionCreator _regionCreator;
|
||||
|
||||
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IOcelotCache<CachedResponse> outputCache,
|
||||
IRegionCreator regionCreator)
|
||||
IOcelotCache<CachedResponse> outputCache)
|
||||
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
||||
{
|
||||
_next = next;
|
||||
_outputCache = outputCache;
|
||||
_regionCreator = regionCreator;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
|
@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CacheManager.Core;
|
||||
|
||||
namespace Ocelot.Cache
|
||||
{
|
||||
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
|
||||
{
|
||||
private readonly ICacheManager<T> _cacheManager;
|
||||
|
||||
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
|
||||
{
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
public void Add(string key, T value, TimeSpan ttl, string region)
|
||||
{
|
||||
_cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
|
||||
}
|
||||
|
||||
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
|
||||
{
|
||||
var exists = _cacheManager.Get(key);
|
||||
|
||||
if (exists != null)
|
||||
{
|
||||
_cacheManager.Remove(key);
|
||||
}
|
||||
|
||||
Add(key, value, ttl, region);
|
||||
}
|
||||
|
||||
public T Get(string key, string region)
|
||||
{
|
||||
return _cacheManager.Get<T>(key, region);
|
||||
}
|
||||
|
||||
public void ClearRegion(string region)
|
||||
{
|
||||
_cacheManager.ClearRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Cache
|
||||
{
|
||||
public class Regions
|
||||
{
|
||||
public Regions(List<string> value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public List<string> Value {get;private set;}
|
||||
}
|
||||
namespace Ocelot.Cache
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class Regions
|
||||
{
|
||||
public Regions(List<string> value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public List<string> Value { get; }
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
private int _durationOfBreak;
|
||||
|
||||
private int _timeoutValue;
|
||||
private int _timeoutValue;
|
||||
|
||||
private string _key;
|
||||
|
||||
|
@ -8,7 +8,8 @@
|
||||
Region = region;
|
||||
}
|
||||
|
||||
public int TtlSeconds { get; private set; }
|
||||
public string Region {get;private set;}
|
||||
public int TtlSeconds { get; private set; }
|
||||
|
||||
public string Region { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using Ocelot.Configuration.Validator;
|
||||
using Ocelot.DependencyInjection;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
@ -49,14 +50,14 @@ namespace Ocelot.Configuration.Creator
|
||||
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
||||
IRegionCreator regionCreator,
|
||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
||||
IAdministrationPath adminPath,
|
||||
IServiceProvider serviceProvider,
|
||||
IHeaderFindAndReplaceCreator headerFAndRCreator,
|
||||
IDownstreamAddressesCreator downstreamAddressesCreator
|
||||
)
|
||||
{
|
||||
_downstreamAddressesCreator = downstreamAddressesCreator;
|
||||
_headerFAndRCreator = headerFAndRCreator;
|
||||
_adminPath = adminPath;
|
||||
_adminPath = serviceProvider.GetService<IAdministrationPath>();
|
||||
_regionCreator = regionCreator;
|
||||
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
||||
_requestIdKeyCreator = requestIdKeyCreator;
|
||||
@ -103,6 +104,12 @@ namespace Ocelot.Configuration.Creator
|
||||
reRoutes.Add(ocelotReRoute);
|
||||
}
|
||||
|
||||
foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes)
|
||||
{
|
||||
var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration);
|
||||
reRoutes.Add(reRoute);
|
||||
}
|
||||
|
||||
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
||||
|
||||
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
|
||||
@ -111,8 +118,10 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions);
|
||||
|
||||
var adminPath = _adminPath != null ? _adminPath.Path : null;
|
||||
|
||||
var config = new InternalConfiguration(reRoutes,
|
||||
_adminPath.Path,
|
||||
adminPath,
|
||||
serviceProviderConfiguration,
|
||||
fileConfiguration.GlobalConfiguration.RequestIdKey,
|
||||
lbOptions,
|
||||
@ -124,7 +133,24 @@ namespace Ocelot.Configuration.Creator
|
||||
return new OkResponse<IInternalConfiguration>(config);
|
||||
}
|
||||
|
||||
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
||||
private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
|
||||
|
||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||
.WithEnableRateLimiting(true)
|
||||
.WithRateLimitOptions(rateLimitOption)
|
||||
.WithServiceName(fileDynamicReRoute.ServiceName)
|
||||
.Build();
|
||||
|
||||
var reRoute = new ReRouteBuilder()
|
||||
.WithDownstreamReRoute(downstreamReRoute)
|
||||
.Build();
|
||||
|
||||
return reRoute;
|
||||
}
|
||||
|
||||
private ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
var applicableReRoutes = reRoutes
|
||||
.SelectMany(x => x.DownstreamReRoute)
|
||||
@ -186,7 +212,7 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
|
||||
|
||||
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
|
||||
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
|
||||
|
||||
var region = _regionCreator.Create(fileReRoute);
|
||||
|
||||
|
@ -1,24 +1,25 @@
|
||||
using Butterfly.Client.Tracing;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Requester;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
|
||||
public HttpHandlerOptionsCreator(IServiceTracer tracer)
|
||||
{
|
||||
_tracer = tracer;
|
||||
}
|
||||
|
||||
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
||||
{
|
||||
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing;
|
||||
|
||||
return new HttpHandlerOptions(options.AllowAutoRedirect,
|
||||
options.UseCookieContainer, useTracing, options.UseProxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
using System;
|
||||
using Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
|
||||
{
|
||||
private readonly ITracer _tracer;
|
||||
|
||||
public HttpHandlerOptionsCreator(IServiceProvider services)
|
||||
{
|
||||
_tracer = services.GetService<ITracer>();
|
||||
}
|
||||
|
||||
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
||||
{
|
||||
var useTracing = _tracer!= null && options.UseTracing;
|
||||
|
||||
return new HttpHandlerOptions(options.AllowAutoRedirect,
|
||||
options.UseCookieContainer, useTracing, options.UseProxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public interface IRateLimitOptionsCreator
|
||||
{
|
||||
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting);
|
||||
}
|
||||
}
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public interface IRateLimitOptionsCreator
|
||||
{
|
||||
RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public static class IdentityServerConfigurationCreator
|
||||
{
|
||||
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
|
||||
{
|
||||
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
||||
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
||||
|
||||
return new IdentityServerConfiguration(
|
||||
"admin",
|
||||
false,
|
||||
secret,
|
||||
new List<string> { "admin", "openid", "offline_access" },
|
||||
credentialsSigningCertificateLocation,
|
||||
credentialsSigningCertificatePassword
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,32 @@
|
||||
using System;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class RateLimitOptionsCreator : IRateLimitOptionsCreator
|
||||
{
|
||||
public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting)
|
||||
{
|
||||
RateLimitOptions rateLimitOption = null;
|
||||
|
||||
if (enableRateLimiting)
|
||||
{
|
||||
rateLimitOption = new RateLimitOptionsBuilder()
|
||||
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
|
||||
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist)
|
||||
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
|
||||
.WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting)
|
||||
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
|
||||
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
|
||||
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
|
||||
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
|
||||
fileReRoute.RateLimitOptions.PeriodTimespan,
|
||||
fileReRoute.RateLimitOptions.Limit))
|
||||
.Build();
|
||||
}
|
||||
|
||||
return rateLimitOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class RateLimitOptionsCreator : IRateLimitOptionsCreator
|
||||
{
|
||||
public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
RateLimitOptions rateLimitOption = null;
|
||||
|
||||
if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting)
|
||||
{
|
||||
rateLimitOption = new RateLimitOptionsBuilder()
|
||||
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
|
||||
.WithClientWhiteList(fileRateLimitRule.ClientWhitelist)
|
||||
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
|
||||
.WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting)
|
||||
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
|
||||
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
|
||||
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
|
||||
.WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period,
|
||||
fileRateLimitRule.PeriodTimespan,
|
||||
fileRateLimitRule.Limit))
|
||||
.Build();
|
||||
}
|
||||
|
||||
return rateLimitOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,45 @@
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class ReRouteOptionsCreator : IReRouteOptionsCreator
|
||||
{
|
||||
public ReRouteOptions Create(FileReRoute fileReRoute)
|
||||
{
|
||||
var isAuthenticated = IsAuthenticated(fileReRoute);
|
||||
var isAuthorised = IsAuthorised(fileReRoute);
|
||||
var isCached = IsCached(fileReRoute);
|
||||
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
|
||||
|
||||
var options = new ReRouteOptionsBuilder()
|
||||
.WithIsAuthenticated(isAuthenticated)
|
||||
.WithIsAuthorised(isAuthorised)
|
||||
.WithIsCached(isCached)
|
||||
.WithRateLimiting(enableRateLimiting)
|
||||
.Build();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
|
||||
{
|
||||
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
|
||||
}
|
||||
|
||||
private bool IsAuthenticated(FileReRoute fileReRoute)
|
||||
{
|
||||
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey);
|
||||
}
|
||||
|
||||
private bool IsAuthorised(FileReRoute fileReRoute)
|
||||
{
|
||||
return fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||
}
|
||||
|
||||
private bool IsCached(FileReRoute fileReRoute)
|
||||
{
|
||||
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
}
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class ReRouteOptionsCreator : IReRouteOptionsCreator
|
||||
{
|
||||
public ReRouteOptions Create(FileReRoute fileReRoute)
|
||||
{
|
||||
var isAuthenticated = IsAuthenticated(fileReRoute);
|
||||
var isAuthorised = IsAuthorised(fileReRoute);
|
||||
var isCached = IsCached(fileReRoute);
|
||||
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
|
||||
|
||||
var options = new ReRouteOptionsBuilder()
|
||||
.WithIsAuthenticated(isAuthenticated)
|
||||
.WithIsAuthorised(isAuthorised)
|
||||
.WithIsCached(isCached)
|
||||
.WithRateLimiting(enableRateLimiting)
|
||||
.Build();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
|
||||
{
|
||||
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
|
||||
}
|
||||
|
||||
private bool IsAuthenticated(FileReRoute fileReRoute)
|
||||
{
|
||||
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey);
|
||||
}
|
||||
|
||||
private bool IsAuthorised(FileReRoute fileReRoute)
|
||||
{
|
||||
return fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||
}
|
||||
|
||||
private bool IsCached(FileReRoute fileReRoute)
|
||||
{
|
||||
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileConfiguration
|
||||
{
|
||||
public FileConfiguration()
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>();
|
||||
GlobalConfiguration = new FileGlobalConfiguration();
|
||||
Aggregates = new List<FileAggregateReRoute>();
|
||||
}
|
||||
|
||||
public List<FileReRoute> ReRoutes { get; set; }
|
||||
|
||||
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
|
||||
public List<FileAggregateReRoute> Aggregates { get;set; }
|
||||
public FileGlobalConfiguration GlobalConfiguration { get; set; }
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileConfiguration
|
||||
{
|
||||
public FileConfiguration()
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>();
|
||||
GlobalConfiguration = new FileGlobalConfiguration();
|
||||
Aggregates = new List<FileAggregateReRoute>();
|
||||
DynamicReRoutes = new List<FileDynamicReRoute>();
|
||||
}
|
||||
|
||||
public List<FileReRoute> ReRoutes { get; set; }
|
||||
public List<FileDynamicReRoute> DynamicReRoutes { get; set; }
|
||||
|
||||
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
|
||||
public List<FileAggregateReRoute> Aggregates { get;set; }
|
||||
public FileGlobalConfiguration GlobalConfiguration { get; set; }
|
||||
}
|
||||
}
|
||||
|
8
src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Normal file
8
src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileDynamicReRoute
|
||||
{
|
||||
public string ServiceName { get; set; }
|
||||
public FileRateLimitRule RateLimitRule { get; set; }
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileIdentityServerConfig
|
||||
{
|
||||
public string ProviderRootUrl { get; set; }
|
||||
public string ApiName { get; set; }
|
||||
public bool RequireHttps { get; set; }
|
||||
public string ApiSecret { get; set; }
|
||||
}
|
||||
}
|
@ -1,37 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileRateLimitOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
|
||||
/// </summary>
|
||||
public string ClientIdHeader { get; set; } = "ClientId";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
|
||||
/// If none specified the default will be:
|
||||
/// API calls quota exceeded! maximum admitted {0} per {1}
|
||||
/// </summary>
|
||||
public string QuotaExceededMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
|
||||
/// </summary>
|
||||
public string RateLimitCounterPrefix { get; set; } = "ocelot";
|
||||
|
||||
/// <summary>
|
||||
/// Disables X-Rate-Limit and Rety-After headers
|
||||
/// </summary>
|
||||
public bool DisableRateLimitHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
||||
/// </summary>
|
||||
public int HttpStatusCode { get; set; } = 429;
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileRateLimitOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
|
||||
/// </summary>
|
||||
public string ClientIdHeader { get; set; } = "ClientId";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
|
||||
/// If none specified the default will be:
|
||||
/// API calls quota exceeded! maximum admitted {0} per {1}
|
||||
/// </summary>
|
||||
public string QuotaExceededMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
|
||||
/// </summary>
|
||||
public string RateLimitCounterPrefix { get; set; } = "ocelot";
|
||||
|
||||
/// <summary>
|
||||
/// Disables X-Rate-Limit and Rety-After headers
|
||||
/// </summary>
|
||||
public bool DisableRateLimitHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
||||
/// </summary>
|
||||
public int HttpStatusCode { get; set; } = 429;
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileRateLimitRule
|
||||
{
|
||||
public FileRateLimitRule()
|
||||
{
|
||||
ClientWhitelist = new List<string>();
|
||||
}
|
||||
|
||||
public List<string> ClientWhitelist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables endpoint rate limiting based URL path and HTTP verb
|
||||
/// </summary>
|
||||
public bool EnableRateLimiting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rate limit period as in 1s, 1m, 1h
|
||||
/// </summary>
|
||||
public string Period { get; set; }
|
||||
|
||||
public double PeriodTimespan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of requests that a client can make in a defined period
|
||||
/// </summary>
|
||||
public long Limit { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (!EnableRateLimiting)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(
|
||||
$"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:[");
|
||||
|
||||
sb.AppendJoin(',', ClientWhitelist);
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileRateLimitRule
|
||||
{
|
||||
public FileRateLimitRule()
|
||||
{
|
||||
ClientWhitelist = new List<string>();
|
||||
}
|
||||
|
||||
public List<string> ClientWhitelist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables endpoint rate limiting based URL path and HTTP verb
|
||||
/// </summary>
|
||||
public bool EnableRateLimiting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rate limit period as in 1s, 1m, 1h
|
||||
/// </summary>
|
||||
public string Period { get; set; }
|
||||
|
||||
public double PeriodTimespan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of requests that a client can make in a defined period
|
||||
/// </summary>
|
||||
public long Limit { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (!EnableRateLimiting)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(
|
||||
$"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:[");
|
||||
|
||||
sb.AppendJoin(',', ClientWhitelist);
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,9 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Raft;
|
||||
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
using Rafty.Concensus.Node;
|
||||
using Repository;
|
||||
|
||||
[Authorize]
|
||||
@ -44,20 +42,6 @@ namespace Ocelot.Configuration
|
||||
{
|
||||
try
|
||||
{
|
||||
//todo - this code is a bit shit sort it out..
|
||||
var test = _provider.GetService(typeof(INode));
|
||||
if (test != null)
|
||||
{
|
||||
var node = (INode)test;
|
||||
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||
if (result.GetType() == typeof(Rafty.Infrastructure.ErrorResponse<UpdateFileConfiguration>))
|
||||
{
|
||||
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
|
||||
}
|
||||
|
||||
return new OkObjectResult(result.Command.Configuration);
|
||||
}
|
||||
|
||||
var response = await _setter.Set(fileConfiguration);
|
||||
|
||||
if (response.IsError)
|
||||
|
@ -1,14 +0,0 @@
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface IIdentityServerConfiguration
|
||||
{
|
||||
string ApiName { get; }
|
||||
string ApiSecret { get; }
|
||||
bool RequireHttps { get; }
|
||||
List<string> AllowedScopes { get; }
|
||||
string CredentialsSigningCertificateLocation { get; }
|
||||
string CredentialsSigningCertificatePassword { get; }
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class IdentityServerConfiguration : IIdentityServerConfiguration
|
||||
{
|
||||
public IdentityServerConfiguration(
|
||||
string apiName,
|
||||
bool requireHttps,
|
||||
string apiSecret,
|
||||
List<string> allowedScopes,
|
||||
string credentialsSigningCertificateLocation,
|
||||
string credentialsSigningCertificatePassword)
|
||||
{
|
||||
ApiName = apiName;
|
||||
RequireHttps = requireHttps;
|
||||
ApiSecret = apiSecret;
|
||||
AllowedScopes = allowedScopes;
|
||||
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
||||
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
||||
}
|
||||
|
||||
public string ApiName { get; }
|
||||
public bool RequireHttps { get; }
|
||||
public List<string> AllowedScopes { get; }
|
||||
public string ApiSecret { get; }
|
||||
public string CredentialsSigningCertificateLocation { get; }
|
||||
public string CredentialsSigningCertificatePassword { get; }
|
||||
}
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class ReRouteOptions
|
||||
{
|
||||
public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting)
|
||||
{
|
||||
IsAuthenticated = isAuthenticated;
|
||||
IsAuthorised = isAuthorised;
|
||||
IsCached = isCached;
|
||||
EnableRateLimiting = isEnableRateLimiting;
|
||||
}
|
||||
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
public bool IsAuthorised { get; private set; }
|
||||
public bool IsCached { get; private set; }
|
||||
public bool EnableRateLimiting { get; private set; }
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class ReRouteOptions
|
||||
{
|
||||
public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting)
|
||||
{
|
||||
IsAuthenticated = isAuthenticated;
|
||||
IsAuthorised = isAuthorised;
|
||||
IsCached = isCached;
|
||||
EnableRateLimiting = isEnableRateLimiting;
|
||||
}
|
||||
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
public bool IsAuthorised { get; private set; }
|
||||
public bool IsCached { get; private set; }
|
||||
public bool EnableRateLimiting { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,97 +0,0 @@
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
|
||||
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
||||
{
|
||||
private readonly IConsulClient _consul;
|
||||
private readonly string _configurationKey;
|
||||
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public ConsulFileConfigurationRepository(
|
||||
Cache.IOcelotCache<FileConfiguration> cache,
|
||||
IInternalConfigurationRepository repo,
|
||||
IConsulClientFactory factory,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
||||
_cache = cache;
|
||||
|
||||
var internalConfig = repo.Get();
|
||||
|
||||
_configurationKey = "InternalConfiguration";
|
||||
|
||||
string token = null;
|
||||
|
||||
if (!internalConfig.IsError)
|
||||
{
|
||||
token = internalConfig.Data.ServiceProviderConfiguration.Token;
|
||||
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
|
||||
internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
|
||||
}
|
||||
|
||||
var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
|
||||
internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
|
||||
|
||||
_consul = factory.Get(config);
|
||||
}
|
||||
|
||||
public async Task<Response<FileConfiguration>> Get()
|
||||
{
|
||||
var config = _cache.Get(_configurationKey, _configurationKey);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(config);
|
||||
}
|
||||
|
||||
var queryResult = await _consul.KV.Get(_configurationKey);
|
||||
|
||||
if (queryResult.Response == null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(null);
|
||||
}
|
||||
|
||||
var bytes = queryResult.Response.Value;
|
||||
|
||||
var json = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
||||
|
||||
return new OkResponse<FileConfiguration>(consulConfig);
|
||||
}
|
||||
|
||||
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var kvPair = new KVPair(_configurationKey)
|
||||
{
|
||||
Value = bytes
|
||||
};
|
||||
|
||||
var result = await _consul.KV.Put(kvPair);
|
||||
|
||||
if (result.Response)
|
||||
{
|
||||
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
||||
}
|
||||
}
|
||||
}
|
@ -9,15 +9,16 @@ namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public class DiskFileConfigurationRepository : IFileConfigurationRepository
|
||||
{
|
||||
private readonly string _configFilePath;
|
||||
|
||||
private readonly string _environmentFilePath;
|
||||
private readonly string _ocelotFilePath;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
private const string ConfigurationFileName = "ocelot";
|
||||
|
||||
public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
|
||||
_environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
|
||||
|
||||
_ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
|
||||
}
|
||||
|
||||
public Task<Response<FileConfiguration>> Get()
|
||||
@ -26,7 +27,7 @@ namespace Ocelot.Configuration.Repository
|
||||
|
||||
lock(_lock)
|
||||
{
|
||||
jsonConfiguration = System.IO.File.ReadAllText(_configFilePath);
|
||||
jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath);
|
||||
}
|
||||
|
||||
var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration);
|
||||
@ -40,12 +41,19 @@ namespace Ocelot.Configuration.Repository
|
||||
|
||||
lock(_lock)
|
||||
{
|
||||
if (System.IO.File.Exists(_configFilePath))
|
||||
if (System.IO.File.Exists(_environmentFilePath))
|
||||
{
|
||||
System.IO.File.Delete(_configFilePath);
|
||||
System.IO.File.Delete(_environmentFilePath);
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllText(_configFilePath, jsonConfiguration);
|
||||
System.IO.File.WriteAllText(_environmentFilePath, jsonConfiguration);
|
||||
|
||||
if (System.IO.File.Exists(_ocelotFilePath))
|
||||
{
|
||||
System.IO.File.Delete(_ocelotFilePath);
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
|
||||
}
|
||||
|
||||
return Task.FromResult<Response>(new OkResponse());
|
||||
|
@ -1,84 +1,112 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Logging;
|
||||
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public class ConsulFileConfigurationPoller : IDisposable
|
||||
{
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IFileConfigurationRepository _repo;
|
||||
private readonly IFileConfigurationSetter _setter;
|
||||
private string _previousAsJson;
|
||||
private readonly Timer _timer;
|
||||
private bool _polling;
|
||||
private readonly IConsulPollerConfiguration _config;
|
||||
|
||||
public ConsulFileConfigurationPoller(
|
||||
IOcelotLoggerFactory factory,
|
||||
IFileConfigurationRepository repo,
|
||||
IFileConfigurationSetter setter,
|
||||
IConsulPollerConfiguration config)
|
||||
{
|
||||
_setter = setter;
|
||||
_config = config;
|
||||
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
||||
_repo = repo;
|
||||
_previousAsJson = "";
|
||||
_timer = new Timer(async x =>
|
||||
{
|
||||
if(_polling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_polling = true;
|
||||
await Poll();
|
||||
_polling = false;
|
||||
}, null, _config.Delay, _config.Delay);
|
||||
}
|
||||
|
||||
private async Task Poll()
|
||||
{
|
||||
_logger.LogInformation("Started polling consul");
|
||||
|
||||
var fileConfig = await _repo.Get();
|
||||
|
||||
if(fileConfig.IsError)
|
||||
{
|
||||
_logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
|
||||
return;
|
||||
}
|
||||
|
||||
var asJson = ToJson(fileConfig.Data);
|
||||
|
||||
if(!fileConfig.IsError && asJson != _previousAsJson)
|
||||
{
|
||||
await _setter.Set(fileConfig.Data);
|
||||
_previousAsJson = asJson;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished polling consul");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||
/// </summary>
|
||||
/// <returns>hash of the config</returns>
|
||||
private string ToJson(FileConfiguration config)
|
||||
{
|
||||
var currentHash = JsonConvert.SerializeObject(config);
|
||||
return currentHash;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Logging;
|
||||
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public class FileConfigurationPoller : IHostedService, IDisposable
|
||||
{
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IFileConfigurationRepository _repo;
|
||||
private string _previousAsJson;
|
||||
private Timer _timer;
|
||||
private bool _polling;
|
||||
private readonly IFileConfigurationPollerOptions _options;
|
||||
private readonly IInternalConfigurationRepository _internalConfigRepo;
|
||||
private readonly IInternalConfigurationCreator _internalConfigCreator;
|
||||
|
||||
public FileConfigurationPoller(
|
||||
IOcelotLoggerFactory factory,
|
||||
IFileConfigurationRepository repo,
|
||||
IFileConfigurationPollerOptions options,
|
||||
IInternalConfigurationRepository internalConfigRepo,
|
||||
IInternalConfigurationCreator internalConfigCreator)
|
||||
{
|
||||
_internalConfigRepo = internalConfigRepo;
|
||||
_internalConfigCreator = internalConfigCreator;
|
||||
_options = options;
|
||||
_logger = factory.CreateLogger<FileConfigurationPoller>();
|
||||
_repo = repo;
|
||||
_previousAsJson = "";
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting.");
|
||||
|
||||
_timer = new Timer(async x =>
|
||||
{
|
||||
if(_polling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_polling = true;
|
||||
await Poll();
|
||||
_polling = false;
|
||||
}, null, _options.Delay, _options.Delay);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is stopping.");
|
||||
|
||||
_timer?.Change(Timeout.Infinite, 0);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Poll()
|
||||
{
|
||||
_logger.LogInformation("Started polling");
|
||||
|
||||
var fileConfig = await _repo.Get();
|
||||
|
||||
if(fileConfig.IsError)
|
||||
{
|
||||
_logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
|
||||
return;
|
||||
}
|
||||
|
||||
var asJson = ToJson(fileConfig.Data);
|
||||
|
||||
if(!fileConfig.IsError && asJson != _previousAsJson)
|
||||
{
|
||||
var config = await _internalConfigCreator.Create(fileConfig.Data);
|
||||
|
||||
if(!config.IsError)
|
||||
{
|
||||
_internalConfigRepo.AddOrReplace(config.Data);
|
||||
}
|
||||
|
||||
_previousAsJson = asJson;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished polling");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||
/// </summary>
|
||||
/// <returns>hash of the config</returns>
|
||||
private string ToJson(FileConfiguration config)
|
||||
{
|
||||
var currentHash = JsonConvert.SerializeObject(config);
|
||||
return currentHash;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public interface IConsulPollerConfiguration
|
||||
public interface IFileConfigurationPollerOptions
|
||||
{
|
||||
int Delay { get; }
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration
|
||||
public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions
|
||||
{
|
||||
public int Delay => 1000;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public class UnableToSetConfigInConsulError : Error
|
||||
{
|
||||
public UnableToSetConfigInConsulError(string message)
|
||||
: base(message, OcelotErrorCode.UnableToSetConfigInConsulError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter
|
||||
{
|
||||
public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
|
||||
{
|
||||
private readonly IInternalConfigurationRepository _configRepo;
|
||||
private readonly IInternalConfigurationRepository internalConfigRepo;
|
||||
private readonly IInternalConfigurationCreator _configCreator;
|
||||
private readonly IFileConfigurationRepository _repo;
|
||||
|
||||
@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Setter
|
||||
IInternalConfigurationCreator configCreator,
|
||||
IFileConfigurationRepository repo)
|
||||
{
|
||||
_configRepo = configRepo;
|
||||
internalConfigRepo = configRepo;
|
||||
_configCreator = configCreator;
|
||||
_repo = repo;
|
||||
}
|
||||
@ -35,7 +35,7 @@ namespace Ocelot.Configuration.Setter
|
||||
|
||||
if(!config.IsError)
|
||||
{
|
||||
_configRepo.AddOrReplace(config.Data);
|
||||
internalConfigRepo.AddOrReplace(config.Data);
|
||||
}
|
||||
|
||||
return new ErrorResponse(config.Errors);
|
||||
|
@ -30,6 +30,11 @@ namespace Ocelot.DependencyInjection
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IHostingEnvironment env = null)
|
||||
{
|
||||
return builder.AddOcelot(".", env);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IHostingEnvironment env = null)
|
||||
{
|
||||
const string primaryConfigFile = "ocelot.json";
|
||||
|
||||
@ -41,7 +46,7 @@ namespace Ocelot.DependencyInjection
|
||||
|
||||
var reg = new Regex(subConfigPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
|
||||
var files = new DirectoryInfo(".")
|
||||
var files = new DirectoryInfo(folder)
|
||||
.EnumerateFiles()
|
||||
.Where(fi => reg.IsMatch(fi.Name) && (fi.Name != excludeConfigName))
|
||||
.ToList();
|
||||
@ -72,7 +77,7 @@ namespace Ocelot.DependencyInjection
|
||||
|
||||
File.WriteAllText(primaryConfigFile, json);
|
||||
|
||||
builder.AddJsonFile(primaryConfigFile);
|
||||
builder.AddJsonFile(primaryConfigFile, false, false);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public interface IOcelotAdministrationBuilder
|
||||
{
|
||||
IOcelotAdministrationBuilder AddRafty();
|
||||
IServiceCollection Services { get; }
|
||||
IConfiguration ConfigurationRoot { get; }
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,23 @@
|
||||
using Butterfly.Client.AspNetCore;
|
||||
using CacheManager.Core;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using Ocelot.Middleware.Multiplexer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
public interface IOcelotBuilder
|
||||
{
|
||||
IOcelotBuilder AddStoreOcelotConfigurationInConsul();
|
||||
IServiceCollection Services { get; }
|
||||
|
||||
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
||||
|
||||
IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings);
|
||||
|
||||
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
||||
|
||||
IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configOptions);
|
||||
IConfiguration Configuration { get; }
|
||||
|
||||
IOcelotBuilder AddDelegatingHandler<T>(bool global = false)
|
||||
where T : DelegatingHandler;
|
||||
|
||||
IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
||||
where T : class, IDefinedAggregator;
|
||||
|
||||
IOcelotBuilder AddTransientDefinedAggregator<T>()
|
||||
where T : class, IDefinedAggregator;
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
public class NullAdministrationPath : IAdministrationPath
|
||||
{
|
||||
public NullAdministrationPath()
|
||||
{
|
||||
Path = null;
|
||||
}
|
||||
|
||||
public string Path {get;private set;}
|
||||
}
|
||||
}
|
@ -1,36 +1,17 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Infrastructure;
|
||||
using Rafty.Log;
|
||||
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
using Rafty.Concensus.Node;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
private readonly IConfiguration _configurationRoot;
|
||||
|
||||
public IServiceCollection Services { get; }
|
||||
public IConfiguration ConfigurationRoot { get; }
|
||||
|
||||
public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
||||
{
|
||||
_configurationRoot = configurationRoot;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public IOcelotAdministrationBuilder AddRafty()
|
||||
{
|
||||
var settings = new InMemorySettings(4000, 6000, 100, 10000);
|
||||
_services.AddSingleton<ILog, SqlLiteLog>();
|
||||
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
||||
_services.AddSingleton<ISettings>(settings);
|
||||
_services.AddSingleton<IPeersProvider, FilePeersProvider>();
|
||||
_services.AddSingleton<INode, Node>();
|
||||
_services.Configure<FilePeers>(_configurationRoot);
|
||||
return this;
|
||||
ConfigurationRoot = configurationRoot;
|
||||
Services = services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
using CacheManager.Core;
|
||||
using IdentityServer4.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -32,183 +30,125 @@ namespace Ocelot.DependencyInjection
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Ocelot.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Net.Http;
|
||||
using Butterfly.Client.AspNetCore;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Butterfly.Client.Tracing;
|
||||
using Ocelot.Middleware.Multiplexer;
|
||||
using ServiceDiscovery.Providers;
|
||||
using Steeltoe.Common.Discovery;
|
||||
using Pivotal.Discovery.Client;
|
||||
using Ocelot.Request.Creator;
|
||||
|
||||
public class OcelotBuilder : IOcelotBuilder
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
private readonly IConfiguration _configurationRoot;
|
||||
public IServiceCollection Services { get; }
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
||||
{
|
||||
_configurationRoot = configurationRoot;
|
||||
_services = services;
|
||||
|
||||
//add default cache settings...
|
||||
Action<ConfigurationBuilderCachePart> defaultCachingSettings = x =>
|
||||
{
|
||||
x.WithDictionaryHandle();
|
||||
};
|
||||
Configuration = configurationRoot;
|
||||
Services = services;
|
||||
|
||||
AddCacheManager(defaultCachingSettings);
|
||||
Services.Configure<FileConfiguration>(configurationRoot);
|
||||
|
||||
//default no caches...
|
||||
Services.TryAddSingleton<IOcelotCache<FileConfiguration>, NoCache<FileConfiguration>>();
|
||||
Services.TryAddSingleton<IOcelotCache<CachedResponse>, NoCache<CachedResponse>>();
|
||||
|
||||
//add ocelot services...
|
||||
_services.Configure<FileConfiguration>(configurationRoot);
|
||||
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
|
||||
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
|
||||
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
|
||||
_services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
|
||||
_services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
|
||||
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
||||
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
||||
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
||||
_services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
||||
_services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
|
||||
_services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
|
||||
_services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
|
||||
_services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
|
||||
_services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
|
||||
_services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
|
||||
_services.TryAddSingleton<IRegionCreator, RegionCreator>();
|
||||
_services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
|
||||
_services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
|
||||
_services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
|
||||
_services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
|
||||
_services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||
_services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||
_services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||
_services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||
_services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||
_services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
||||
_services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
|
||||
_services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
|
||||
_services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
|
||||
_services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
|
||||
_services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
|
||||
_services.TryAddSingleton<IClaimsParser, ClaimsParser>();
|
||||
_services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
||||
_services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
||||
_services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
||||
_services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
|
||||
_services.AddSingleton<IDownstreamRouteProvider, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>();
|
||||
_services.TryAddSingleton<IDownstreamRouteProviderFactory, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteProviderFactory>();
|
||||
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||
_services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
|
||||
_services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
|
||||
_services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
|
||||
_services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();
|
||||
_services.TryAddSingleton<IRequestMapper, RequestMapper>();
|
||||
_services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
|
||||
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
|
||||
_services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
|
||||
|
||||
if (UsingEurekaServiceDiscoveryProvider(configurationRoot))
|
||||
{
|
||||
_services.AddDiscoveryClient(configurationRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
_services.TryAddSingleton<IDiscoveryClient, FakeEurekaDiscoveryClient>();
|
||||
}
|
||||
|
||||
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||
Services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
|
||||
Services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
|
||||
Services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
|
||||
Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
|
||||
Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
|
||||
Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
||||
Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
||||
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
||||
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
||||
Services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
|
||||
Services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
|
||||
Services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
|
||||
Services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
|
||||
Services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
|
||||
Services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
|
||||
Services.TryAddSingleton<IRegionCreator, RegionCreator>();
|
||||
Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
|
||||
Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
|
||||
Services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
|
||||
Services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
|
||||
Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||
Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||
Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||
Services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||
Services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||
Services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
||||
Services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
|
||||
Services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
|
||||
Services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
|
||||
Services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
|
||||
Services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
|
||||
Services.TryAddSingleton<IClaimsParser, ClaimsParser>();
|
||||
Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
||||
Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
||||
Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
||||
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
|
||||
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();
|
||||
Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();
|
||||
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||
Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
|
||||
Services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
|
||||
Services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
|
||||
Services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();
|
||||
Services.TryAddSingleton<IRequestMapper, RequestMapper>();
|
||||
Services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
|
||||
Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
|
||||
Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
|
||||
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||
|
||||
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
||||
// could maybe use a scoped data repository
|
||||
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
_services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
|
||||
_services.AddMemoryCache();
|
||||
_services.TryAddSingleton<OcelotDiagnosticListener>();
|
||||
Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
Services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
|
||||
Services.AddMemoryCache();
|
||||
Services.TryAddSingleton<OcelotDiagnosticListener>();
|
||||
|
||||
//add asp.net services..
|
||||
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
|
||||
|
||||
_services.AddMvcCore()
|
||||
Services.AddMvcCore()
|
||||
.AddApplicationPart(assembly)
|
||||
.AddControllersAsServices()
|
||||
.AddAuthorization()
|
||||
.AddJsonFormatters();
|
||||
|
||||
_services.AddLogging();
|
||||
_services.AddMiddlewareAnalysis();
|
||||
_services.AddWebEncoders();
|
||||
_services.AddSingleton<IAdministrationPath>(new NullAdministrationPath());
|
||||
Services.AddLogging();
|
||||
Services.AddMiddlewareAnalysis();
|
||||
Services.AddWebEncoders();
|
||||
|
||||
_services.TryAddSingleton<IMultiplexer, Multiplexer>();
|
||||
_services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
|
||||
_services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
|
||||
|
||||
// We add this here so that we can always inject something into the factory for IoC..
|
||||
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
||||
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
|
||||
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||
_services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
||||
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
||||
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
||||
_services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
||||
_services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
|
||||
}
|
||||
|
||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||
{
|
||||
var administrationPath = new AdministrationPath(path);
|
||||
|
||||
//add identity server for admin area
|
||||
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
|
||||
|
||||
if (identityServerConfiguration != null)
|
||||
{
|
||||
AddIdentityServer(identityServerConfiguration, administrationPath);
|
||||
}
|
||||
|
||||
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
|
||||
_services.Replace(descriptor);
|
||||
return new OcelotAdministrationBuilder(_services, _configurationRoot);
|
||||
}
|
||||
|
||||
public IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configureOptions)
|
||||
{
|
||||
var administrationPath = new AdministrationPath(path);
|
||||
|
||||
if (configureOptions != null)
|
||||
{
|
||||
AddIdentityServer(configureOptions);
|
||||
}
|
||||
|
||||
//todo - hack because we add this earlier so it always exists for some reason...investigate..
|
||||
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
|
||||
_services.Replace(descriptor);
|
||||
return new OcelotAdministrationBuilder(_services, _configurationRoot);
|
||||
Services.TryAddSingleton<IMultiplexer, Multiplexer>();
|
||||
Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
|
||||
Services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
|
||||
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
||||
Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||
Services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||
Services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
||||
Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
||||
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
||||
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
||||
where T : class, IDefinedAggregator
|
||||
{
|
||||
_services.AddSingleton<IDefinedAggregator, T>();
|
||||
Services.AddSingleton<IDefinedAggregator, T>();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddTransientDefinedAggregator<T>()
|
||||
where T : class, IDefinedAggregator
|
||||
{
|
||||
_services.AddTransient<IDefinedAggregator, T>();
|
||||
Services.AddTransient<IDefinedAggregator, T>();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -217,142 +157,18 @@ namespace Ocelot.DependencyInjection
|
||||
{
|
||||
if(global)
|
||||
{
|
||||
_services.AddTransient<THandler>();
|
||||
_services.AddTransient<GlobalDelegatingHandler>(s => {
|
||||
Services.AddTransient<THandler>();
|
||||
Services.AddTransient<GlobalDelegatingHandler>(s => {
|
||||
var service = s.GetService<THandler>();
|
||||
return new GlobalDelegatingHandler(service);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_services.AddTransient<DelegatingHandler, THandler>();
|
||||
Services.AddTransient<DelegatingHandler, THandler>();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
|
||||
{
|
||||
// Earlier we add FakeServiceTracer and need to remove it here before we add butterfly
|
||||
_services.RemoveAll<IServiceTracer>();
|
||||
_services.AddButterfly(settings);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
||||
{
|
||||
_services.AddSingleton<ConsulFileConfigurationPoller>();
|
||||
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings)
|
||||
{
|
||||
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
|
||||
var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
|
||||
|
||||
_services.RemoveAll(typeof(ICacheManager<CachedResponse>));
|
||||
_services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
|
||||
_services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
|
||||
_services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
|
||||
|
||||
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
|
||||
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
|
||||
_services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
|
||||
_services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
|
||||
_services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
|
||||
_services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
|
||||
|
||||
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
|
||||
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
|
||||
_services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
|
||||
_services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
|
||||
_services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
|
||||
_services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions)
|
||||
{
|
||||
_services
|
||||
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddIdentityServerAuthentication(configOptions);
|
||||
}
|
||||
|
||||
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
|
||||
{
|
||||
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||
var identityServerBuilder = _services
|
||||
.AddIdentityServer(o => {
|
||||
o.IssuerUri = "Ocelot";
|
||||
})
|
||||
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
||||
.AddInMemoryClients(Client(identityServerConfiguration));
|
||||
|
||||
var urlFinder = new BaseUrlFinder(_configurationRoot);
|
||||
var baseSchemeUrlAndPort = urlFinder.Find();
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
|
||||
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddIdentityServerAuthentication(o =>
|
||||
{
|
||||
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
|
||||
o.ApiName = identityServerConfiguration.ApiName;
|
||||
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
||||
o.SupportedTokens = SupportedTokens.Both;
|
||||
o.ApiSecret = identityServerConfiguration.ApiSecret;
|
||||
});
|
||||
|
||||
//todo - refactor naming..
|
||||
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
|
||||
{
|
||||
identityServerBuilder.AddDeveloperSigningCredential();
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo - refactor so calls method?
|
||||
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
|
||||
identityServerBuilder.AddSigningCredential(cert);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
return new List<ApiResource>
|
||||
{
|
||||
new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
|
||||
{
|
||||
ApiSecrets = new List<Secret>
|
||||
{
|
||||
new Secret
|
||||
{
|
||||
Value = identityServerConfiguration.ApiSecret.Sha256()
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
return new List<Client>
|
||||
{
|
||||
new Client
|
||||
{
|
||||
ClientId = identityServerConfiguration.ApiName,
|
||||
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
||||
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
||||
AllowedScopes = { identityServerConfiguration.ApiName }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot)
|
||||
{
|
||||
var type = configurationRoot.GetValue<string>("GlobalConfiguration:ServiceDiscoveryProvider:Type",
|
||||
string.Empty);
|
||||
|
||||
return type.ToLower() == "eureka";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
{
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Configuration;
|
||||
using Configuration.Builder;
|
||||
using Configuration.Creator;
|
||||
@ -43,7 +44,7 @@
|
||||
|
||||
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
|
||||
|
||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||
var downstreamReRouteBuilder = new DownstreamReRouteBuilder()
|
||||
.WithServiceName(serviceName)
|
||||
.WithLoadBalancerKey(loadBalancerKey)
|
||||
.WithDownstreamPathTemplate(downstreamPath)
|
||||
@ -51,8 +52,22 @@
|
||||
.WithHttpHandlerOptions(configuration.HttpHandlerOptions)
|
||||
.WithQosOptions(qosOptions)
|
||||
.WithDownstreamScheme(configuration.DownstreamScheme)
|
||||
.WithLoadBalancerOptions(configuration.LoadBalancerOptions)
|
||||
.Build();
|
||||
.WithLoadBalancerOptions(configuration.LoadBalancerOptions);
|
||||
|
||||
var rateLimitOptions = configuration.ReRoutes != null
|
||||
? configuration.ReRoutes
|
||||
.SelectMany(x => x.DownstreamReRoute)
|
||||
.FirstOrDefault(x => x.ServiceName == serviceName)
|
||||
: null;
|
||||
|
||||
if(rateLimitOptions != null)
|
||||
{
|
||||
downstreamReRouteBuilder
|
||||
.WithRateLimitOptions(rateLimitOptions.RateLimitOptions)
|
||||
.WithEnableRateLimiting(true);
|
||||
}
|
||||
|
||||
var downstreamReRoute = downstreamReRouteBuilder.Build();
|
||||
|
||||
var reRoute = new ReRouteBuilder()
|
||||
.WithDownstreamReRoute(downstreamReRoute)
|
||||
|
@ -20,7 +20,9 @@
|
||||
|
||||
public IDownstreamRouteProvider Get(IInternalConfiguration config)
|
||||
{
|
||||
if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration))
|
||||
//todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have
|
||||
//an upstream path template which means they are dyanmic and service discovery is on...
|
||||
if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamPathTemplate.Value))) && IsServiceDiscovery(config.ServiceProviderConfiguration))
|
||||
{
|
||||
_logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request");
|
||||
return _providers[nameof(DownstreamRouteCreator)];
|
||||
|
@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||
}
|
||||
|
||||
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
|
||||
|
||||
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
||||
|
||||
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
|
||||
|
@ -52,7 +52,15 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
if(ContainsQueryString(dsPath))
|
||||
{
|
||||
context.DownstreamRequest.AbsolutePath = GetPath(dsPath);
|
||||
context.DownstreamRequest.Query = GetQueryString(dsPath);
|
||||
|
||||
if (string.IsNullOrEmpty(context.DownstreamRequest.Query))
|
||||
{
|
||||
context.DownstreamRequest.Query = GetQueryString(dsPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.DownstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,104 +1,103 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Errors.Middleware
|
||||
{
|
||||
using Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
|
||||
/// </summary>
|
||||
public class ExceptionHandlerMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
private readonly IInternalConfigurationRepository _configRepo;
|
||||
private readonly IRequestScopedDataRepository _repo;
|
||||
|
||||
public ExceptionHandlerMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IInternalConfigurationRepository configRepo,
|
||||
IRequestScopedDataRepository repo)
|
||||
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
|
||||
{
|
||||
_configRepo = configRepo;
|
||||
_repo = repo;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
//try and get the global request id and set it for logs...
|
||||
//should this basically be immutable per request...i guess it should!
|
||||
//first thing is get config
|
||||
var configuration = _configRepo.Get();
|
||||
|
||||
if (configuration.IsError)
|
||||
{
|
||||
throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
|
||||
}
|
||||
namespace Ocelot.Errors.Middleware
|
||||
{
|
||||
using Configuration;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
|
||||
/// </summary>
|
||||
public class ExceptionHandlerMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
private readonly IInternalConfigurationRepository _configRepo;
|
||||
private readonly IRequestScopedDataRepository _repo;
|
||||
|
||||
TrySetGlobalRequestId(context, configuration.Data);
|
||||
|
||||
context.Configuration = configuration.Data;
|
||||
|
||||
Logger.LogDebug("ocelot pipeline started");
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogDebug("error calling middleware");
|
||||
|
||||
var message = CreateMessage(context, e);
|
||||
|
||||
Logger.LogError(message, e);
|
||||
|
||||
SetInternalServerErrorOnResponse(context);
|
||||
}
|
||||
|
||||
Logger.LogDebug("ocelot pipeline finished");
|
||||
}
|
||||
|
||||
private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration)
|
||||
{
|
||||
var key = configuration.RequestId;
|
||||
|
||||
if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
|
||||
{
|
||||
context.HttpContext.TraceIdentifier = upstreamRequestIds.First();
|
||||
}
|
||||
|
||||
_repo.Add("RequestId", context.HttpContext.TraceIdentifier);
|
||||
}
|
||||
|
||||
private void SetInternalServerErrorOnResponse(DownstreamContext context)
|
||||
{
|
||||
if (!context.HttpContext.Response.HasStarted)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = 500;
|
||||
}
|
||||
}
|
||||
|
||||
private string CreateMessage(DownstreamContext context, Exception e)
|
||||
{
|
||||
var message =
|
||||
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
|
||||
|
||||
if (e.InnerException != null)
|
||||
{
|
||||
message =
|
||||
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
|
||||
}
|
||||
|
||||
return $"{message} RequestId: {context.HttpContext.TraceIdentifier}";
|
||||
}
|
||||
}
|
||||
}
|
||||
public ExceptionHandlerMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IInternalConfigurationRepository configRepo,
|
||||
IRequestScopedDataRepository repo)
|
||||
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
|
||||
{
|
||||
_configRepo = configRepo;
|
||||
_repo = repo;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
//try and get the global request id and set it for logs...
|
||||
//should this basically be immutable per request...i guess it should!
|
||||
//first thing is get config
|
||||
var configuration = _configRepo.Get();
|
||||
|
||||
if (configuration.IsError)
|
||||
{
|
||||
throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
|
||||
}
|
||||
|
||||
TrySetGlobalRequestId(context, configuration.Data);
|
||||
|
||||
context.Configuration = configuration.Data;
|
||||
|
||||
Logger.LogDebug("ocelot pipeline started");
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogDebug("error calling middleware");
|
||||
|
||||
var message = CreateMessage(context, e);
|
||||
|
||||
Logger.LogError(message, e);
|
||||
|
||||
SetInternalServerErrorOnResponse(context);
|
||||
}
|
||||
|
||||
Logger.LogDebug("ocelot pipeline finished");
|
||||
}
|
||||
|
||||
private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration)
|
||||
{
|
||||
var key = configuration.RequestId;
|
||||
|
||||
if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
|
||||
{
|
||||
context.HttpContext.TraceIdentifier = upstreamRequestIds.First();
|
||||
}
|
||||
|
||||
_repo.Add("RequestId", context.HttpContext.TraceIdentifier);
|
||||
}
|
||||
|
||||
private void SetInternalServerErrorOnResponse(DownstreamContext context)
|
||||
{
|
||||
if (!context.HttpContext.Response.HasStarted)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = 500;
|
||||
}
|
||||
}
|
||||
|
||||
private string CreateMessage(DownstreamContext context, Exception e)
|
||||
{
|
||||
var message =
|
||||
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
|
||||
|
||||
if (e.InnerException != null)
|
||||
{
|
||||
message =
|
||||
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
|
||||
}
|
||||
|
||||
return $"{message} RequestId: {context.HttpContext.TraceIdentifier}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,6 @@
|
||||
UnableToFindLoadBalancerError,
|
||||
RequestTimedOutError,
|
||||
UnableToFindQoSProviderError,
|
||||
UnableToSetConfigInConsulError,
|
||||
UnmappableRequestError,
|
||||
RateLimitOptionsError,
|
||||
PathTemplateDoesntStartWithForwardSlash,
|
||||
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using Consul;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
|
||||
namespace Ocelot.Infrastructure.Consul
|
||||
{
|
||||
public class ConsulClientFactory : IConsulClientFactory
|
||||
{
|
||||
public IConsulClient Get(ConsulRegistryConfiguration config)
|
||||
{
|
||||
return new ConsulClient(c =>
|
||||
{
|
||||
c.Address = new Uri($"http://{config.Host}:{config.Port}");
|
||||
|
||||
if (!string.IsNullOrEmpty(config?.Token))
|
||||
{
|
||||
c.Token = config.Token;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using Consul;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
|
||||
namespace Ocelot.Infrastructure.Consul
|
||||
{
|
||||
public interface IConsulClientFactory
|
||||
{
|
||||
IConsulClient Get(ConsulRegistryConfiguration config);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.Linq;
|
||||
|
||||
namespace Ocelot.Infrastructure.Extensions
|
||||
{
|
||||
internal static class StringValuesExtensions
|
||||
public static class StringValuesExtensions
|
||||
{
|
||||
public static string GetValue(this StringValues stringValues)
|
||||
{
|
||||
|
19
src/Ocelot/Logging/ITracer.cs
Normal file
19
src/Ocelot/Logging/ITracer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace Ocelot.Logging
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
public interface ITracer
|
||||
{
|
||||
void Event(HttpContext httpContext, string @event);
|
||||
|
||||
Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken,
|
||||
Action<string> addTraceIdToRepo,
|
||||
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync);
|
||||
}
|
||||
}
|
@ -1,96 +1,67 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DiagnosticAdapter;
|
||||
using Butterfly.Client.AspNetCore;
|
||||
using Butterfly.OpenTracing;
|
||||
using Ocelot.Middleware;
|
||||
using Butterfly.Client.Tracing;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Requester;
|
||||
|
||||
namespace Ocelot.Logging
|
||||
{
|
||||
public class OcelotDiagnosticListener
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer)
|
||||
{
|
||||
_tracer = tracer;
|
||||
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
|
||||
}
|
||||
|
||||
[DiagnosticName("Ocelot.MiddlewareException")]
|
||||
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
|
||||
{
|
||||
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};");
|
||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Ocelot.MiddlewareStarted")]
|
||||
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
|
||||
{
|
||||
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Ocelot.MiddlewareFinished")]
|
||||
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
|
||||
{
|
||||
_logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
||||
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
|
||||
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
|
||||
{
|
||||
_logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
||||
Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")]
|
||||
public virtual void OnMiddlewareException(Exception exception, string name)
|
||||
{
|
||||
_logger.LogTrace($"MiddlewareException: {name}; {exception.Message};");
|
||||
}
|
||||
|
||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")]
|
||||
public virtual void OnMiddlewareFinished(HttpContext httpContext, string name)
|
||||
{
|
||||
_logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
||||
Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
||||
}
|
||||
|
||||
private void Event(HttpContext httpContext, string @event)
|
||||
{
|
||||
// todo - if the user isnt using tracing the code gets here and will blow up on
|
||||
// _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario
|
||||
// so sticking it here as well..I guess we need a factory for this but cba to do it at
|
||||
// the moment
|
||||
if(_tracer.GetType() == typeof(FakeServiceTracer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var span = httpContext.GetSpan();
|
||||
|
||||
if(span == null)
|
||||
{
|
||||
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
|
||||
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
|
||||
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
|
||||
{
|
||||
spanBuilder.AsChildOf(spanContext);
|
||||
}
|
||||
|
||||
span = _tracer.Start(spanBuilder);
|
||||
httpContext.SetSpan(span);
|
||||
}
|
||||
|
||||
span?.Log(LogField.CreateNew().Event(@event));
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DiagnosticAdapter;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Logging
|
||||
{
|
||||
public class OcelotDiagnosticListener
|
||||
{
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly ITracer _tracer;
|
||||
|
||||
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
|
||||
_tracer = serviceProvider.GetService<ITracer>();
|
||||
|
||||
}
|
||||
|
||||
[DiagnosticName("Ocelot.MiddlewareException")]
|
||||
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
|
||||
{
|
||||
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};");
|
||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Ocelot.MiddlewareStarted")]
|
||||
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
|
||||
{
|
||||
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Ocelot.MiddlewareFinished")]
|
||||
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
|
||||
{
|
||||
_logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
||||
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
|
||||
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
|
||||
{
|
||||
_logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
||||
Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
||||
}
|
||||
|
||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")]
|
||||
public virtual void OnMiddlewareException(Exception exception, string name)
|
||||
{
|
||||
_logger.LogTrace($"MiddlewareException: {name}; {exception.Message};");
|
||||
}
|
||||
|
||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")]
|
||||
public virtual void OnMiddlewareFinished(HttpContext httpContext, string name)
|
||||
{
|
||||
_logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
||||
Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
||||
}
|
||||
|
||||
private void Event(HttpContext httpContext, string @event)
|
||||
{
|
||||
_tracer?.Event(httpContext, @event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
public delegate Task OcelotMiddlewareConfigurationDelegate(IApplicationBuilder builder);
|
||||
}
|
@ -1,250 +1,162 @@
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Logging;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.Infrastructure;
|
||||
using Ocelot.Middleware.Pipeline;
|
||||
using Pivotal.Discovery.Client;
|
||||
using Rafty.Concensus.Node;
|
||||
|
||||
public static class OcelotMiddlewareExtensions
|
||||
{
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
||||
{
|
||||
await builder.UseOcelot(new OcelotPipelineConfiguration());
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
||||
{
|
||||
var configuration = await CreateConfiguration(builder);
|
||||
|
||||
CreateAdministrationArea(builder, configuration);
|
||||
|
||||
if(UsingRafty(builder))
|
||||
{
|
||||
SetUpRafty(builder);
|
||||
}
|
||||
|
||||
if (UsingEurekaServiceDiscoveryProvider(configuration))
|
||||
{
|
||||
builder.UseDiscoveryClient();
|
||||
}
|
||||
|
||||
ConfigureDiagnosticListener(builder);
|
||||
|
||||
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
|
||||
|
||||
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
|
||||
|
||||
var firstDelegate = pipelineBuilder.Build();
|
||||
|
||||
/*
|
||||
inject first delegate into first piece of asp.net middleware..maybe not like this
|
||||
then because we are updating the http context in ocelot it comes out correct for
|
||||
rest of asp.net..
|
||||
*/
|
||||
|
||||
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
|
||||
|
||||
builder.Use(async (context, task) =>
|
||||
{
|
||||
var downstreamContext = new DownstreamContext(context);
|
||||
await firstDelegate.Invoke(downstreamContext);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
|
||||
{
|
||||
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
|
||||
}
|
||||
|
||||
private static bool UsingRafty(IApplicationBuilder builder)
|
||||
{
|
||||
var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode;
|
||||
if(possible != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetUpRafty(IApplicationBuilder builder)
|
||||
{
|
||||
var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime));
|
||||
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
||||
var node = (INode)builder.ApplicationServices.GetService(typeof(INode));
|
||||
var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId));
|
||||
node.Start(nodeId);
|
||||
}
|
||||
|
||||
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
||||
{
|
||||
// make configuration from file system?
|
||||
// earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
|
||||
var fileConfig = (IOptions<FileConfiguration>)builder.ApplicationServices.GetService(typeof(IOptions<FileConfiguration>));
|
||||
|
||||
// now create the config
|
||||
var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator));
|
||||
var internalConfig = await internalConfigCreator.Create(fileConfig.Value);
|
||||
|
||||
// now save it in memory
|
||||
var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository));
|
||||
internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||
|
||||
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
|
||||
|
||||
var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository));
|
||||
|
||||
if (UsingConsul(fileConfigRepo))
|
||||
{
|
||||
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetFileConfig(fileConfigSetter, fileConfig);
|
||||
}
|
||||
|
||||
return GetOcelotConfigAndReturn(internalConfigRepo);
|
||||
}
|
||||
|
||||
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
|
||||
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
|
||||
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
|
||||
{
|
||||
// get the config from consul.
|
||||
var fileConfigFromConsul = await fileConfigRepo.Get();
|
||||
|
||||
if (IsError(fileConfigFromConsul))
|
||||
{
|
||||
ThrowToStopOcelotStarting(fileConfigFromConsul);
|
||||
}
|
||||
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
|
||||
{
|
||||
//there was no config in consul set the file in config in consul
|
||||
await fileConfigRepo.Set(fileConfig.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// create the internal config from consul data
|
||||
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
|
||||
|
||||
if (IsError(internalConfig))
|
||||
{
|
||||
ThrowToStopOcelotStarting(internalConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add the internal config to the internal repo
|
||||
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||
|
||||
if (IsError(response))
|
||||
{
|
||||
ThrowToStopOcelotStarting(response);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsError(internalConfig))
|
||||
{
|
||||
ThrowToStopOcelotStarting(internalConfig);
|
||||
}
|
||||
}
|
||||
|
||||
//todo - this starts the poller if it has been registered...please this is so bad.
|
||||
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
|
||||
}
|
||||
|
||||
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig)
|
||||
{
|
||||
Response response;
|
||||
response = await fileConfigSetter.Set(fileConfig.Value);
|
||||
|
||||
if (IsError(response))
|
||||
{
|
||||
ThrowToStopOcelotStarting(response);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ConfigNotStoredInConsul(Responses.Response<FileConfiguration> fileConfigFromConsul)
|
||||
{
|
||||
return fileConfigFromConsul.Data == null;
|
||||
}
|
||||
|
||||
private static bool IsError(Response response)
|
||||
{
|
||||
return response == null || response.IsError;
|
||||
}
|
||||
|
||||
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
|
||||
{
|
||||
var ocelotConfiguration = provider.Get();
|
||||
|
||||
if(ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
|
||||
{
|
||||
ThrowToStopOcelotStarting(ocelotConfiguration);
|
||||
}
|
||||
|
||||
return ocelotConfiguration.Data;
|
||||
}
|
||||
|
||||
private static void ThrowToStopOcelotStarting(Response config)
|
||||
{
|
||||
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
|
||||
}
|
||||
|
||||
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
||||
{
|
||||
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
|
||||
}
|
||||
|
||||
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(configuration.AdministrationPath))
|
||||
{
|
||||
builder.Map(configuration.AdministrationPath, app =>
|
||||
{
|
||||
//todo - hack so we know that we are using internal identity server
|
||||
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
|
||||
if (identityServerConfiguration != null)
|
||||
{
|
||||
app.UseIdentityServer();
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseMvc();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
||||
{
|
||||
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
||||
var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener));
|
||||
var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener));
|
||||
diagnosticListener.SubscribeWithAdapter(listener);
|
||||
}
|
||||
|
||||
private static void OnShutdown(IApplicationBuilder app)
|
||||
{
|
||||
var node = (INode)app.ApplicationServices.GetService(typeof(INode));
|
||||
node.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Diagnostics;
|
||||
using DependencyInjection;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware.Pipeline;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class OcelotMiddlewareExtensions
|
||||
{
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
||||
{
|
||||
await builder.UseOcelot(new OcelotPipelineConfiguration());
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration)
|
||||
{
|
||||
var config = new OcelotPipelineConfiguration();
|
||||
pipelineConfiguration?.Invoke(config);
|
||||
return await builder.UseOcelot(config);
|
||||
}
|
||||
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
||||
{
|
||||
var configuration = await CreateConfiguration(builder);
|
||||
|
||||
ConfigureDiagnosticListener(builder);
|
||||
|
||||
return CreateOcelotPipeline(builder, pipelineConfiguration);
|
||||
}
|
||||
|
||||
private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
||||
{
|
||||
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
|
||||
|
||||
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
|
||||
|
||||
var firstDelegate = pipelineBuilder.Build();
|
||||
|
||||
/*
|
||||
inject first delegate into first piece of asp.net middleware..maybe not like this
|
||||
then because we are updating the http context in ocelot it comes out correct for
|
||||
rest of asp.net..
|
||||
*/
|
||||
|
||||
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
|
||||
|
||||
builder.Use(async (context, task) =>
|
||||
{
|
||||
var downstreamContext = new DownstreamContext(context);
|
||||
await firstDelegate.Invoke(downstreamContext);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
||||
{
|
||||
// make configuration from file system?
|
||||
// earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
|
||||
var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
|
||||
|
||||
// now create the config
|
||||
var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
|
||||
var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);
|
||||
//Configuration error, throw error message
|
||||
if (internalConfig.IsError)
|
||||
{
|
||||
ThrowToStopOcelotStarting(internalConfig);
|
||||
}
|
||||
|
||||
// now save it in memory
|
||||
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||
internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||
|
||||
fileConfig.OnChange(async (config) =>
|
||||
{
|
||||
var newInternalConfig = await internalConfigCreator.Create(config);
|
||||
internalConfigRepo.AddOrReplace(newInternalConfig.Data);
|
||||
});
|
||||
|
||||
var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>();
|
||||
|
||||
var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>();
|
||||
|
||||
// Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern?
|
||||
foreach (var configuration in configurations)
|
||||
{
|
||||
await configuration(builder);
|
||||
}
|
||||
|
||||
if(AdministrationApiInUse(adminPath))
|
||||
{
|
||||
//We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the
|
||||
//admin api it works...boy this is getting a spit spags boll.
|
||||
var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>();
|
||||
|
||||
await SetFileConfig(fileConfigSetter, fileConfig);
|
||||
}
|
||||
|
||||
return GetOcelotConfigAndReturn(internalConfigRepo);
|
||||
}
|
||||
|
||||
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
|
||||
{
|
||||
return adminPath != null;
|
||||
}
|
||||
|
||||
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor<FileConfiguration> fileConfig)
|
||||
{
|
||||
var response = await fileConfigSetter.Set(fileConfig.CurrentValue);
|
||||
|
||||
if (IsError(response))
|
||||
{
|
||||
ThrowToStopOcelotStarting(response);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsError(Response response)
|
||||
{
|
||||
return response == null || response.IsError;
|
||||
}
|
||||
|
||||
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
|
||||
{
|
||||
var ocelotConfiguration = provider.Get();
|
||||
|
||||
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
|
||||
{
|
||||
ThrowToStopOcelotStarting(ocelotConfiguration);
|
||||
}
|
||||
|
||||
return ocelotConfiguration.Data;
|
||||
}
|
||||
|
||||
private static void ThrowToStopOcelotStarting(Response config)
|
||||
{
|
||||
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
|
||||
}
|
||||
|
||||
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
||||
{
|
||||
var env = builder.ApplicationServices.GetService<IHostingEnvironment>();
|
||||
var listener = builder.ApplicationServices.GetService<OcelotDiagnosticListener>();
|
||||
var diagnosticListener = builder.ApplicationServices.GetService<DiagnosticListener>();
|
||||
diagnosticListener.SubscribeWithAdapter(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
using Ocelot.Middleware.Pipeline;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class OcelotPipelineConfiguration
|
||||
@ -37,6 +39,10 @@
|
||||
/// <summary>
|
||||
/// This allows the user to implement there own query string manipulation logic
|
||||
/// </summary>
|
||||
public Func<DownstreamContext, Func<Task>, Task> PreQueryStringBuilderMiddleware { get; set; }
|
||||
public Func<DownstreamContext, Func<Task>, Task> PreQueryStringBuilderMiddleware { get; set; }
|
||||
/// <summary>
|
||||
/// This is an extension that will branch to different pipes
|
||||
/// </summary>
|
||||
public List<Func<IOcelotPipelineBuilder, Func<DownstreamContext, bool>>> MapWhenOcelotPipeline { get; } = new List<Func<IOcelotPipelineBuilder, Func<DownstreamContext, bool>>>();
|
||||
}
|
||||
}
|
||||
|
@ -80,13 +80,14 @@ namespace Ocelot.Middleware.Pipeline
|
||||
var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener));
|
||||
var middlewareName = ocelotDelegate.Target.GetType().Name;
|
||||
|
||||
OcelotRequestDelegate wrapped = context => {
|
||||
OcelotRequestDelegate wrapped = context =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context);
|
||||
return ocelotDelegate(context);
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context);
|
||||
throw ex;
|
||||
@ -117,7 +118,7 @@ namespace Ocelot.Middleware.Pipeline
|
||||
|
||||
private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context)
|
||||
{
|
||||
if(diagnosticListener != null)
|
||||
if (diagnosticListener != null)
|
||||
{
|
||||
diagnosticListener.Write(message, new { name = middlewareName, context = context });
|
||||
}
|
||||
@ -125,7 +126,7 @@ namespace Ocelot.Middleware.Pipeline
|
||||
|
||||
private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context)
|
||||
{
|
||||
if(diagnosticListener != null)
|
||||
if (diagnosticListener != null)
|
||||
{
|
||||
diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception });
|
||||
}
|
||||
@ -160,6 +161,28 @@ namespace Ocelot.Middleware.Pipeline
|
||||
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
|
||||
}
|
||||
|
||||
public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Func<IOcelotPipelineBuilder, Predicate> pipelineBuilderFunc)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
if (pipelineBuilderFunc == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pipelineBuilderFunc));
|
||||
}
|
||||
var branchBuilder = app.New();
|
||||
var predicate = pipelineBuilderFunc.Invoke(branchBuilder);
|
||||
var branch = branchBuilder.Build();
|
||||
|
||||
var options = new MapWhenOptions
|
||||
{
|
||||
Predicate = predicate,
|
||||
Branch = branch
|
||||
};
|
||||
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
|
||||
}
|
||||
|
||||
private static Func<T, DownstreamContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
|
||||
{
|
||||
var middleware = typeof(T);
|
||||
|
@ -48,6 +48,15 @@ namespace Ocelot.Middleware.Pipeline
|
||||
// Then we get the downstream route information
|
||||
builder.UseDownstreamRouteFinderMiddleware();
|
||||
|
||||
//Expand other branch pipes
|
||||
if (pipelineConfiguration.MapWhenOcelotPipeline != null)
|
||||
{
|
||||
foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
|
||||
{
|
||||
builder.MapWhen(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have the ds route we can transform headers and stuff?
|
||||
builder.UseHttpHeadersTransformationMiddleware();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
@ -12,6 +12,7 @@
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
@ -25,12 +26,7 @@
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentValidation" Version="7.6.104" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.1.1" />
|
||||
@ -49,13 +45,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
||||
<PackageReference Include="CacheManager.Core" Version="1.1.2" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
|
||||
<PackageReference Include="Consul" Version="0.7.2.5" />
|
||||
<PackageReference Include="Polly" Version="6.0.1" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
|
||||
<PackageReference Include="Rafty" Version="0.4.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,17 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
internal class BearerToken
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonProperty("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)]
|
||||
public class ExcludeFromCoverageAttribute : Attribute{}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FakeCommand : ICommand
|
||||
{
|
||||
public FakeCommand(string value)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public string Value { get; private set; }
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FilePeer
|
||||
{
|
||||
public string HostAndPort { get; set; }
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FilePeers
|
||||
{
|
||||
public FilePeers()
|
||||
{
|
||||
Peers = new List<FilePeer>();
|
||||
}
|
||||
|
||||
public List<FilePeer> Peers {get; set;}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Middleware;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.Infrastructure;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
using Rafty.Concensus.Peers;
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public class FilePeersProvider : IPeersProvider
|
||||
{
|
||||
private readonly IOptions<FilePeers> _options;
|
||||
private readonly List<IPeer> _peers;
|
||||
private IBaseUrlFinder _finder;
|
||||
private IInternalConfigurationRepository _repo;
|
||||
private IIdentityServerConfiguration _identityServerConfig;
|
||||
|
||||
public FilePeersProvider(IOptions<FilePeers> options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig)
|
||||
{
|
||||
_identityServerConfig = identityServerConfig;
|
||||
_repo = repo;
|
||||
_finder = finder;
|
||||
_options = options;
|
||||
_peers = new List<IPeer>();
|
||||
|
||||
var config = _repo.Get();
|
||||
foreach (var item in _options.Value.Peers)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
|
||||
//todo what if this errors?
|
||||
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig);
|
||||
_peers.Add(httpPeer);
|
||||
}
|
||||
}
|
||||
|
||||
public List<IPeer> Get()
|
||||
{
|
||||
return _peers;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Middleware;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
using Rafty.Concensus.Messages;
|
||||
using Rafty.Concensus.Peers;
|
||||
using Rafty.Infrastructure;
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public class HttpPeer : IPeer
|
||||
{
|
||||
private readonly string _hostAndPort;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private BearerToken _token;
|
||||
private readonly IInternalConfiguration _config;
|
||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_config = config;
|
||||
Id = hostAndPort;
|
||||
_hostAndPort = hostAndPort;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = finder.Find();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public async Task<RequestVoteResponse> Request(RequestVote requestVote)
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
await SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
return new RequestVoteResponse(false, requestVote.Term);
|
||||
}
|
||||
|
||||
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
await SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Response<T>> Request<T>(T command)
|
||||
where T : ICommand
|
||||
{
|
||||
Console.WriteLine("SENDING REQUEST....");
|
||||
if(_token == null)
|
||||
{
|
||||
await SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine("REQUEST OK....");
|
||||
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
return new OkResponse<T>((T)okResponse.Command);
|
||||
}
|
||||
|
||||
Console.WriteLine("REQUEST NOT OK....");
|
||||
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
|
||||
}
|
||||
|
||||
private async Task SetToken()
|
||||
{
|
||||
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
||||
var formData = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
||||
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||
};
|
||||
var content = new FormUrlEncodedContent(formData);
|
||||
var response = await _httpClient.PostAsync(tokenUrl, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
response.EnsureSuccessStatusCode();
|
||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Log;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
||||
{
|
||||
private readonly IFileConfigurationSetter _setter;
|
||||
|
||||
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
||||
{
|
||||
_setter = setter;
|
||||
}
|
||||
|
||||
public async Task Handle(LogEntry log)
|
||||
{
|
||||
//todo - handle an error
|
||||
//hack it to just cast as at the moment we know this is the only command :P
|
||||
var hack = (UpdateFileConfiguration)log.CommandData;
|
||||
await _setter.Set(hack.Configuration);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
using Rafty.Concensus.Messages;
|
||||
using Rafty.Concensus.Node;
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
[Authorize]
|
||||
[Route("raft")]
|
||||
public class RaftController : Controller
|
||||
{
|
||||
private readonly INode _node;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private readonly JsonSerializerSettings _jsonSerialiserSettings;
|
||||
|
||||
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
|
||||
{
|
||||
_jsonSerialiserSettings = new JsonSerializerSettings {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = finder.Find();
|
||||
_logger = loggerFactory.CreateLogger<RaftController>();
|
||||
_node = node;
|
||||
}
|
||||
|
||||
[Route("appendentries")]
|
||||
public async Task<IActionResult> AppendEntries()
|
||||
{
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||
{
|
||||
var json = await reader.ReadToEndAsync();
|
||||
|
||||
var appendEntries = JsonConvert.DeserializeObject<AppendEntries>(json, _jsonSerialiserSettings);
|
||||
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
|
||||
|
||||
var appendEntriesResponse = await _node.Handle(appendEntries);
|
||||
|
||||
return new OkObjectResult(appendEntriesResponse);
|
||||
}
|
||||
}
|
||||
|
||||
[Route("requestvote")]
|
||||
public async Task<IActionResult> RequestVote()
|
||||
{
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||
{
|
||||
var json = await reader.ReadToEndAsync();
|
||||
|
||||
var requestVote = JsonConvert.DeserializeObject<RequestVote>(json, _jsonSerialiserSettings);
|
||||
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
|
||||
|
||||
var requestVoteResponse = await _node.Handle(requestVote);
|
||||
|
||||
return new OkObjectResult(requestVoteResponse);
|
||||
}
|
||||
}
|
||||
|
||||
[Route("command")]
|
||||
public async Task<IActionResult> Command()
|
||||
{
|
||||
try
|
||||
{
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||
{
|
||||
var json = await reader.ReadToEndAsync();
|
||||
|
||||
var command = JsonConvert.DeserializeObject<ICommand>(json, _jsonSerialiserSettings);
|
||||
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
|
||||
|
||||
var commandResponse = await _node.Accept(command);
|
||||
|
||||
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
|
||||
|
||||
return StatusCode(200, json);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Rafty.Infrastructure;
|
||||
using Rafty.Log;
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public class SqlLiteLog : ILog
|
||||
{
|
||||
private readonly string _path;
|
||||
private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly ILogger _logger;
|
||||
private readonly NodeId _nodeId;
|
||||
|
||||
public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<SqlLiteLog>();
|
||||
_nodeId = nodeId;
|
||||
_path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db";
|
||||
_sempaphore.Wait();
|
||||
|
||||
if (!File.Exists(_path))
|
||||
{
|
||||
var fs = File.Create(_path);
|
||||
|
||||
fs.Dispose();
|
||||
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
const string sql = @"create table logs (
|
||||
id integer primary key,
|
||||
data text not null
|
||||
)";
|
||||
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = command.ExecuteNonQuery();
|
||||
|
||||
_logger.LogInformation(result == 0
|
||||
? $"id: {_nodeId.Id} create database, result: {result}"
|
||||
: $"id: {_nodeId.Id} did not create database., result: {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
}
|
||||
|
||||
public async Task<int> LastLogIndex()
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
var result = 1;
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select id from logs order by id desc limit 1";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var index = Convert.ToInt32(await command.ExecuteScalarAsync());
|
||||
if (index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<long> LastLogTerm()
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
long result = 0;
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select data from logs order by id desc limit 1";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if (log != null && log.Term > result)
|
||||
{
|
||||
result = log.Term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> Count()
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
var result = 0;
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select count(id) from logs";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var index = Convert.ToInt32(await command.ExecuteScalarAsync());
|
||||
if (index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> Apply(LogEntry log)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"insert into logs (data) values ('{data}')";
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}");
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = await command.ExecuteNonQueryAsync();
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}");
|
||||
}
|
||||
|
||||
sql = "select last_insert_rowid()";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore");
|
||||
_sempaphore.Release();
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite");
|
||||
return Convert.ToInt32(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index};";
|
||||
_logger.LogInformation($"id: {_nodeId.Id} sql: {sql}");
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
|
||||
_logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}");
|
||||
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if (logEntry != null && log != null && logEntry.Term != log.Term)
|
||||
{
|
||||
//todo - sql injection dont copy this..
|
||||
var deleteSql = $"delete from logs where id >= {index};";
|
||||
_logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}");
|
||||
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
}
|
||||
|
||||
public async Task<bool> IsDuplicate(int index, LogEntry logEntry)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index};";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
|
||||
if (logEntry != null && log != null && logEntry.Term == log.Term)
|
||||
{
|
||||
_sempaphore.Release();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<LogEntry> Get(int index)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index}";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
_sempaphore.Release();
|
||||
return log;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
var logsToReturn = new List<(int, LogEntry)>();
|
||||
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select id, data from logs where id >= {index}";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
using (var reader = await command.ExecuteReaderAsync())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = Convert.ToInt32(reader[0]);
|
||||
var data = (string)reader[1];
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
logsToReturn.Add((id, log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return logsToReturn;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> GetTermAtIndex(int index)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
long result = 0;
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index}";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if (log != null && log.Term > result)
|
||||
{
|
||||
result = log.Term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task Remove(int indexOfCommand)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
||||
_logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}");
|
||||
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using Ocelot.Configuration.File;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
public class UpdateFileConfiguration : ICommand
|
||||
{
|
||||
public UpdateFileConfiguration(FileConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public FileConfiguration Configuration {get;private set;}
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
namespace Ocelot.Request.Mapper
|
||||
{
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Responses;
|
||||
|
||||
public interface IRequestMapper
|
||||
{
|
||||
Task<Response<HttpRequestMessage>> Map(HttpRequest request);
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Request.Mapper
|
||||
{
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Responses;
|
||||
|
||||
public interface IRequestMapper
|
||||
{
|
||||
Task<Response<HttpRequestMessage>> Map(HttpRequest request);
|
||||
}
|
||||
}
|
||||
|
@ -1,108 +1,112 @@
|
||||
namespace Ocelot.Request.Mapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Ocelot.Responses;
|
||||
|
||||
public class RequestMapper : IRequestMapper
|
||||
{
|
||||
private readonly string[] _unsupportedHeaders = { "host" };
|
||||
|
||||
public async Task<Response<HttpRequestMessage>> Map(HttpRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage()
|
||||
{
|
||||
Content = await MapContent(request),
|
||||
Method = MapMethod(request),
|
||||
RequestUri = MapUri(request)
|
||||
};
|
||||
|
||||
MapHeaders(request, requestMessage);
|
||||
|
||||
return new OkResponse<HttpRequestMessage>(requestMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ErrorResponse<HttpRequestMessage>(new UnmappableRequestError(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpContent> MapContent(HttpRequest request)
|
||||
{
|
||||
if (request.Body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = new ByteArrayContent(await ToByteArray(request.Body));
|
||||
|
||||
content.Headers
|
||||
.TryAddWithoutValidation("Content-Type", new[] {request.ContentType});
|
||||
|
||||
AddHeaderIfExistsOnRequest("Content-Language", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Location", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Range", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-MD5", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Disposition", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Encoding", content, request);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request)
|
||||
{
|
||||
if(request.Headers.ContainsKey(key))
|
||||
{
|
||||
content.Headers
|
||||
.TryAddWithoutValidation(key, request.Headers[key].ToList());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpMethod MapMethod(HttpRequest request)
|
||||
{
|
||||
return new HttpMethod(request.Method);
|
||||
}
|
||||
|
||||
private Uri MapUri(HttpRequest request)
|
||||
{
|
||||
return new Uri(request.GetEncodedUrl());
|
||||
}
|
||||
|
||||
private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage)
|
||||
{
|
||||
foreach (var header in request.Headers)
|
||||
{
|
||||
if (IsSupportedHeader(header))
|
||||
{
|
||||
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> ToByteArray(Stream stream)
|
||||
{
|
||||
using (stream)
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(memStream);
|
||||
return memStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSupportedHeader(KeyValuePair<string, StringValues> header)
|
||||
{
|
||||
return !_unsupportedHeaders.Contains(header.Key.ToLower());
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Request.Mapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Ocelot.Responses;
|
||||
|
||||
public class RequestMapper : IRequestMapper
|
||||
{
|
||||
private readonly string[] _unsupportedHeaders = { "host" };
|
||||
|
||||
public async Task<Response<HttpRequestMessage>> Map(HttpRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage()
|
||||
{
|
||||
Content = await MapContent(request),
|
||||
Method = MapMethod(request),
|
||||
RequestUri = MapUri(request)
|
||||
};
|
||||
|
||||
MapHeaders(request, requestMessage);
|
||||
|
||||
return new OkResponse<HttpRequestMessage>(requestMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ErrorResponse<HttpRequestMessage>(new UnmappableRequestError(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpContent> MapContent(HttpRequest request)
|
||||
{
|
||||
if (request.Body == null || (request.Body.CanSeek && request.Body.Length <= 0))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Never change this to StreamContent again, I forgot it doesnt work in #464.
|
||||
var content = new ByteArrayContent(await ToByteArray(request.Body));
|
||||
|
||||
if(!string.IsNullOrEmpty(request.ContentType))
|
||||
{
|
||||
content.Headers
|
||||
.TryAddWithoutValidation("Content-Type", new[] {request.ContentType});
|
||||
}
|
||||
|
||||
AddHeaderIfExistsOnRequest("Content-Language", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Location", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Range", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-MD5", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Disposition", content, request);
|
||||
AddHeaderIfExistsOnRequest("Content-Encoding", content, request);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request)
|
||||
{
|
||||
if(request.Headers.ContainsKey(key))
|
||||
{
|
||||
content.Headers
|
||||
.TryAddWithoutValidation(key, request.Headers[key].ToList());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpMethod MapMethod(HttpRequest request)
|
||||
{
|
||||
return new HttpMethod(request.Method);
|
||||
}
|
||||
|
||||
private Uri MapUri(HttpRequest request)
|
||||
{
|
||||
return new Uri(request.GetEncodedUrl());
|
||||
}
|
||||
|
||||
private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage)
|
||||
{
|
||||
foreach (var header in request.Headers)
|
||||
{
|
||||
if (IsSupportedHeader(header))
|
||||
{
|
||||
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSupportedHeader(KeyValuePair<string, StringValues> header)
|
||||
{
|
||||
return !_unsupportedHeaders.Contains(header.Key.ToLower());
|
||||
}
|
||||
|
||||
private async Task<byte[]> ToByteArray(Stream stream)
|
||||
{
|
||||
using(stream)
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(memStream);
|
||||
return memStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Butterfly.Client.Tracing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
|
@ -1,17 +0,0 @@
|
||||
using Butterfly.Client.Tracing;
|
||||
using Butterfly.OpenTracing;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public class FakeServiceTracer : IServiceTracer
|
||||
{
|
||||
public ITracer Tracer { get; }
|
||||
public string ServiceName { get; }
|
||||
public string Environment { get; }
|
||||
public string Identity { get; }
|
||||
public ISpan Start(ISpanBuilder spanBuilder)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Butterfly.Client.Tracing;
|
||||
using Butterfly.OpenTracing;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
using Logging;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
|
||||
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
private readonly ITracer _tracer;
|
||||
private readonly IRequestScopedDataRepository _repo;
|
||||
private const string PrefixSpanId = "ot-spanId";
|
||||
|
||||
public OcelotHttpTracingHandler(
|
||||
IServiceTracer tracer,
|
||||
ITracer tracer,
|
||||
IRequestScopedDataRepository repo,
|
||||
HttpMessageHandler httpMessageHandler = null)
|
||||
{
|
||||
@ -28,46 +26,8 @@ namespace Ocelot.Requester
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
|
||||
}
|
||||
|
||||
protected virtual async Task<HttpResponseMessage> TracingSendAsync(
|
||||
ISpan span,
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Headers.Contains(PrefixSpanId))
|
||||
{
|
||||
request.Headers.Remove(PrefixSpanId);
|
||||
request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId);
|
||||
}
|
||||
|
||||
_repo.Add("TraceId", span.SpanContext.TraceId);
|
||||
|
||||
span.Tags.Client().Component("HttpClient")
|
||||
.HttpMethod(request.Method.Method)
|
||||
.HttpUrl(request.RequestUri.OriginalString)
|
||||
.HttpHost(request.RequestUri.Host)
|
||||
.HttpPath(request.RequestUri.PathAndQuery)
|
||||
.PeerAddress(request.RequestUri.OriginalString)
|
||||
.PeerHostName(request.RequestUri.Host)
|
||||
.PeerPort(request.RequestUri.Port);
|
||||
|
||||
_tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) =>
|
||||
{
|
||||
if (!c.Contains(k))
|
||||
{
|
||||
c.Add(k, v);
|
||||
}
|
||||
});
|
||||
|
||||
span.Log(LogField.CreateNew().ClientSend());
|
||||
|
||||
var responseMessage = await base.SendAsync(request, cancellationToken);
|
||||
|
||||
span.Log(LogField.CreateNew().ClientReceive());
|
||||
|
||||
return responseMessage;
|
||||
return _tracer.SendAsync(request, cancellationToken, x => _repo.Add("TraceId", x), (r,c) => base.SendAsync(r, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
using Butterfly.Client.Tracing;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
using System;
|
||||
using Logging;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public class TracingHandlerFactory : ITracingHandlerFactory
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
private readonly ITracer _tracer;
|
||||
private readonly IRequestScopedDataRepository _repo;
|
||||
|
||||
public TracingHandlerFactory(
|
||||
IServiceTracer tracer,
|
||||
IServiceProvider services,
|
||||
IRequestScopedDataRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
_tracer = tracer;
|
||||
_tracer = services.GetService<ITracer>();
|
||||
}
|
||||
|
||||
public ITracingHandler Get()
|
||||
|
@ -1,74 +1,74 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Ocelot.Headers;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Responder
|
||||
{
|
||||
/// <summary>
|
||||
/// Cannot unit test things in this class due to methods not being implemented
|
||||
/// on .net concretes used for testing
|
||||
/// </summary>
|
||||
public class HttpContextResponder : IHttpResponder
|
||||
{
|
||||
private readonly IRemoveOutputHeaders _removeOutputHeaders;
|
||||
|
||||
public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders)
|
||||
{
|
||||
_removeOutputHeaders = removeOutputHeaders;
|
||||
}
|
||||
|
||||
public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response)
|
||||
{
|
||||
_removeOutputHeaders.Remove(response.Headers);
|
||||
|
||||
foreach (var httpResponseHeader in response.Headers)
|
||||
{
|
||||
AddHeaderIfDoesntExist(context, httpResponseHeader);
|
||||
}
|
||||
|
||||
foreach (var httpResponseHeader in response.Content.Headers)
|
||||
{
|
||||
AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value));
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) );
|
||||
|
||||
context.Response.OnStarting(state =>
|
||||
{
|
||||
var httpContext = (HttpContext)state;
|
||||
|
||||
httpContext.Response.StatusCode = (int)response.StatusCode;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}, context);
|
||||
|
||||
using (Stream stream = new MemoryStream(content))
|
||||
{
|
||||
if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0)
|
||||
{
|
||||
await stream.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Ocelot.Headers;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Responder
|
||||
{
|
||||
/// <summary>
|
||||
/// Cannot unit test things in this class due to methods not being implemented
|
||||
/// on .net concretes used for testing
|
||||
/// </summary>
|
||||
public class HttpContextResponder : IHttpResponder
|
||||
{
|
||||
private readonly IRemoveOutputHeaders _removeOutputHeaders;
|
||||
|
||||
public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders)
|
||||
{
|
||||
context.Response.StatusCode = statusCode;
|
||||
}
|
||||
|
||||
private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader)
|
||||
{
|
||||
if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key))
|
||||
{
|
||||
context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_removeOutputHeaders = removeOutputHeaders;
|
||||
}
|
||||
|
||||
public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response)
|
||||
{
|
||||
_removeOutputHeaders.Remove(response.Headers);
|
||||
|
||||
foreach (var httpResponseHeader in response.Headers)
|
||||
{
|
||||
AddHeaderIfDoesntExist(context, httpResponseHeader);
|
||||
}
|
||||
|
||||
foreach (var httpResponseHeader in response.Content.Headers)
|
||||
{
|
||||
AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value));
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStreamAsync();
|
||||
|
||||
AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) );
|
||||
|
||||
context.Response.OnStarting(state =>
|
||||
{
|
||||
var httpContext = (HttpContext)state;
|
||||
|
||||
httpContext.Response.StatusCode = (int)response.StatusCode;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}, context);
|
||||
|
||||
using(content)
|
||||
{
|
||||
if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0)
|
||||
{
|
||||
await content.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||
{
|
||||
context.Response.StatusCode = statusCode;
|
||||
}
|
||||
|
||||
private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader)
|
||||
{
|
||||
if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key))
|
||||
{
|
||||
context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,55 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
|
||||
namespace Ocelot.Responder.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead.
|
||||
/// </summary>
|
||||
public class ResponderMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
private readonly IHttpResponder _responder;
|
||||
private readonly IErrorsToHttpStatusCodeMapper _codeMapper;
|
||||
|
||||
public ResponderMiddleware(OcelotRequestDelegate next,
|
||||
IHttpResponder responder,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IErrorsToHttpStatusCodeMapper codeMapper
|
||||
)
|
||||
:base(loggerFactory.CreateLogger<ResponderMiddleware>())
|
||||
{
|
||||
_next = next;
|
||||
_responder = responder;
|
||||
_codeMapper = codeMapper;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
await _next.Invoke(context);
|
||||
|
||||
if (context.IsError)
|
||||
{
|
||||
Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}");
|
||||
|
||||
SetErrorResponse(context.HttpContext, context.Errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("no pipeline errors, setting and returning completed response");
|
||||
await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetErrorResponse(HttpContext context, List<Error> errors)
|
||||
{
|
||||
var statusCode = _codeMapper.Map(errors);
|
||||
_responder.SetErrorResponseOnContext(context, statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
|
||||
namespace Ocelot.Responder.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead.
|
||||
/// </summary>
|
||||
public class ResponderMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly OcelotRequestDelegate _next;
|
||||
private readonly IHttpResponder _responder;
|
||||
private readonly IErrorsToHttpStatusCodeMapper _codeMapper;
|
||||
|
||||
public ResponderMiddleware(OcelotRequestDelegate next,
|
||||
IHttpResponder responder,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IErrorsToHttpStatusCodeMapper codeMapper
|
||||
)
|
||||
:base(loggerFactory.CreateLogger<ResponderMiddleware>())
|
||||
{
|
||||
_next = next;
|
||||
_responder = responder;
|
||||
_codeMapper = codeMapper;
|
||||
}
|
||||
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
await _next.Invoke(context);
|
||||
|
||||
if (context.IsError)
|
||||
{
|
||||
Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}");
|
||||
|
||||
SetErrorResponse(context.HttpContext, context.Errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("no pipeline errors, setting and returning completed response");
|
||||
await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetErrorResponse(HttpContext context, List<Error> errors)
|
||||
{
|
||||
var statusCode = _codeMapper.Map(errors);
|
||||
_responder.SetErrorResponseOnContext(context, statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
namespace Ocelot.ServiceDiscovery.Configuration
|
||||
{
|
||||
public class ConsulRegistryConfiguration
|
||||
{
|
||||
public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token)
|
||||
{
|
||||
Host = string.IsNullOrEmpty(host) ? "localhost" : host;
|
||||
Port = port > 0 ? port : 8500;
|
||||
KeyOfServiceInConsul = keyOfServiceInConsul;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public string KeyOfServiceInConsul { get; }
|
||||
public string Host { get; }
|
||||
public int Port { get; }
|
||||
public string Token { get; }
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery.Errors
|
||||
{
|
||||
public class UnableToFindServiceDiscoveryProviderError : Error
|
||||
{
|
||||
public UnableToFindServiceDiscoveryProviderError(string message)
|
||||
: base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery.Providers
|
||||
{
|
||||
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly ConsulRegistryConfiguration _config;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IConsulClient _consul;
|
||||
private const string VersionPrefix = "version-";
|
||||
|
||||
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
|
||||
{;
|
||||
_logger = factory.CreateLogger<ConsulServiceDiscoveryProvider>();
|
||||
|
||||
_config = config;
|
||||
_consul = clientFactory.Get(_config);
|
||||
}
|
||||
|
||||
public async Task<List<Service>> Get()
|
||||
{
|
||||
var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);
|
||||
|
||||
var services = new List<Service>();
|
||||
|
||||
foreach (var serviceEntry in queryResult.Response)
|
||||
{
|
||||
if (IsValid(serviceEntry))
|
||||
{
|
||||
services.Add(BuildService(serviceEntry));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
|
||||
}
|
||||
}
|
||||
|
||||
return services.ToList();
|
||||
}
|
||||
|
||||
private Service BuildService(ServiceEntry serviceEntry)
|
||||
{
|
||||
return new Service(
|
||||
serviceEntry.Service.Service,
|
||||
new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
|
||||
serviceEntry.Service.ID,
|
||||
GetVersionFromStrings(serviceEntry.Service.Tags),
|
||||
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
private bool IsValid(ServiceEntry serviceEntry)
|
||||
{
|
||||
if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetVersionFromStrings(IEnumerable<string> strings)
|
||||
{
|
||||
return strings
|
||||
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
|
||||
.TrimStart(VersionPrefix);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
namespace Ocelot.ServiceDiscovery.Providers
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Steeltoe.Common.Discovery;
|
||||
using Values;
|
||||
|
||||
public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly IDiscoveryClient _client;
|
||||
private readonly string _serviceName;
|
||||
|
||||
public EurekaServiceDiscoveryProvider(string serviceName, IDiscoveryClient client)
|
||||
{
|
||||
_client = client;
|
||||
_serviceName = serviceName;
|
||||
}
|
||||
|
||||
public Task<List<Service>> Get()
|
||||
{
|
||||
var services = new List<Service>();
|
||||
|
||||
var instances = _client.GetInstances(_serviceName);
|
||||
|
||||
if (instances != null && instances.Any())
|
||||
{
|
||||
services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List<string>())));
|
||||
}
|
||||
|
||||
return Task.FromResult(services);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
namespace Ocelot.ServiceDiscovery.Providers
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Steeltoe.Common.Discovery;
|
||||
|
||||
public class FakeEurekaDiscoveryClient : IDiscoveryClient
|
||||
{
|
||||
public IServiceInstance GetLocalServiceInstance()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IList<IServiceInstance> GetInstances(string serviceId)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task ShutdownAsync()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public string Description { get; }
|
||||
public IList<string> Services { get; }
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery.Providers
|
||||
{
|
||||
public class PollingConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
|
||||
private readonly Timer _timer;
|
||||
private bool _polling;
|
||||
private List<Service> _services;
|
||||
private string _keyOfServiceInConsul;
|
||||
|
||||
public PollingConsulServiceDiscoveryProvider(int pollingInterval, string keyOfServiceInConsul, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
|
||||
{;
|
||||
_logger = factory.CreateLogger<PollingConsulServiceDiscoveryProvider>();
|
||||
_keyOfServiceInConsul = keyOfServiceInConsul;
|
||||
_consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
|
||||
_services = new List<Service>();
|
||||
|
||||
_timer = new Timer(async x =>
|
||||
{
|
||||
if(_polling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_polling = true;
|
||||
await Poll();
|
||||
_polling = false;
|
||||
|
||||
}, null, pollingInterval, pollingInterval);
|
||||
}
|
||||
|
||||
public Task<List<Service>> Get()
|
||||
{
|
||||
return Task.FromResult(_services);
|
||||
}
|
||||
|
||||
private async Task Poll()
|
||||
{
|
||||
_services = await _consulServiceDiscoveryProvider.Get();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
using System;
|
||||
using Ocelot.Configuration;
|
||||
using Providers;
|
||||
|
||||
public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key);
|
||||
}
|
@ -1,26 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
using Ocelot.ServiceDiscovery.Providers;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
using Steeltoe.Common.Discovery;
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
using Ocelot.ServiceDiscovery.Providers;
|
||||
using Ocelot.Values;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
||||
{
|
||||
private readonly IOcelotLoggerFactory _factory;
|
||||
private readonly IConsulClientFactory _consulFactory;
|
||||
private readonly IDiscoveryClient _eurekaClient;
|
||||
private readonly List<ServiceDiscoveryFinderDelegate> _delegates;
|
||||
private readonly IServiceProvider _provider;
|
||||
|
||||
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient)
|
||||
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider)
|
||||
{
|
||||
_factory = factory;
|
||||
_consulFactory = consulFactory;
|
||||
_eurekaClient = eurekaClient;
|
||||
_provider = provider;
|
||||
_delegates = provider
|
||||
.GetServices<ServiceDiscoveryFinderDelegate>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
||||
@ -42,29 +44,24 @@ namespace Ocelot.ServiceDiscovery
|
||||
return new ConfigurationServiceProvider(services);
|
||||
}
|
||||
|
||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName)
|
||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key)
|
||||
{
|
||||
if (serviceConfig.Type?.ToLower() == "servicefabric")
|
||||
if (config.Type?.ToLower() == "servicefabric")
|
||||
{
|
||||
var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName);
|
||||
return new ServiceFabricServiceDiscoveryProvider(config);
|
||||
var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key);
|
||||
return new ServiceFabricServiceDiscoveryProvider(sfConfig);
|
||||
}
|
||||
|
||||
if (serviceConfig.Type?.ToLower() == "eureka")
|
||||
foreach (var serviceDiscoveryFinderDelegate in _delegates)
|
||||
{
|
||||
return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient);
|
||||
}
|
||||
|
||||
var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token);
|
||||
|
||||
var consulServiceDiscoveryProvider = new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
|
||||
|
||||
if (serviceConfig.Type?.ToLower() == "pollconsul")
|
||||
{
|
||||
return new PollingConsulServiceDiscoveryProvider(serviceConfig.PollingInterval, consulRegistryConfiguration.KeyOfServiceInConsul, _factory, consulServiceDiscoveryProvider);
|
||||
var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key);
|
||||
if (provider != null)
|
||||
{
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
return consulServiceDiscoveryProvider;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace Ocelot.WebSockets.Middleware
|
||||
|
||||
public WebSocketsProxyMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
:base(loggerFactory.CreateLogger<WebSocketsProxyMiddleware>())
|
||||
: base(loggerFactory.CreateLogger<WebSocketsProxyMiddleware>())
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
@ -29,6 +29,17 @@ namespace Ocelot.WebSockets.Middleware
|
||||
var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var wsToDownstreamService = new ClientWebSocket();
|
||||
|
||||
foreach (var requestHeader in context.Request.Headers)
|
||||
{
|
||||
// Do not copy the Sec-Websocket headers because it is specified by the own connection it will fail when you copy this one.
|
||||
if (requestHeader.Key.StartsWith("Sec-WebSocket"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
wsToDownstreamService.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
|
||||
}
|
||||
|
||||
var uri = new Uri(serverEndpoint);
|
||||
await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None);
|
||||
|
||||
|
Reference in New Issue
Block a user