Merge pull request #3 from ThreeMammals/master

Master merge
This commit is contained in:
jlukawska
2020-03-11 21:51:47 +01:00
committed by GitHub
103 changed files with 3101 additions and 1578 deletions

View File

@ -27,7 +27,13 @@
}
app.UseAuthentication();
app.UseMvc();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
});
});
}

View File

@ -1,5 +1,6 @@
using Ocelot.Configuration.Creator;
using Ocelot.Values;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
@ -41,6 +42,8 @@ namespace Ocelot.Configuration.Builder
private List<AddHeader> _addHeadersToUpstream;
private bool _dangerousAcceptAnyServerCertificateValidator;
private SecurityOptions _securityOptions;
private string _downstreamHttpMethod;
private Version _downstreamHttpVersion;
public DownstreamReRouteBuilder()
{
@ -56,6 +59,12 @@ namespace Ocelot.Configuration.Builder
return this;
}
public DownstreamReRouteBuilder WithDownStreamHttpMethod(string method)
{
_downstreamHttpMethod = method;
return this;
}
public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions)
{
_loadBalancerOptions = loadBalancerOptions;
@ -248,6 +257,12 @@ namespace Ocelot.Configuration.Builder
return this;
}
public DownstreamReRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion)
{
_downstreamHttpVersion = downstreamHttpVersion;
return this;
}
public DownstreamReRoute Build()
{
return new DownstreamReRoute(
@ -282,7 +297,9 @@ namespace Ocelot.Configuration.Builder
_addHeadersToDownstream,
_addHeadersToUpstream,
_dangerousAcceptAnyServerCertificateValidator,
_securityOptions);
_securityOptions,
_downstreamHttpMethod,
_downstreamHttpVersion);
}
}
}

View File

@ -0,0 +1,14 @@
namespace Ocelot.Configuration.ChangeTracking
{
using Microsoft.Extensions.Primitives;
/// <summary>
/// <see cref="IChangeToken" /> source which is activated when Ocelot's configuration is changed.
/// </summary>
public interface IOcelotConfigurationChangeTokenSource
{
IChangeToken ChangeToken { get; }
void Activate();
}
}

View File

@ -0,0 +1,74 @@
namespace Ocelot.Configuration.ChangeTracking
{
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
public class OcelotConfigurationChangeToken : IChangeToken
{
public const double PollingIntervalSeconds = 1;
private readonly ICollection<CallbackWrapper> _callbacks = new List<CallbackWrapper>();
private readonly object _lock = new object();
private DateTime? _timeChanged;
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
lock (_lock)
{
var wrapper = new CallbackWrapper(callback, state, _callbacks, _lock);
_callbacks.Add(wrapper);
return wrapper;
}
}
public void Activate()
{
lock (_lock)
{
_timeChanged = DateTime.UtcNow;
foreach (var wrapper in _callbacks)
{
wrapper.Invoke();
}
}
}
// Token stays active for PollingIntervalSeconds after a change (could be parameterised) - otherwise HasChanged would be true forever.
// Taking suggestions for better ways to reset HasChanged back to false.
public bool HasChanged => _timeChanged.HasValue && (DateTime.UtcNow - _timeChanged.Value).TotalSeconds < PollingIntervalSeconds;
public bool ActiveChangeCallbacks => true;
private class CallbackWrapper : IDisposable
{
private readonly ICollection<CallbackWrapper> _callbacks;
private readonly object _lock;
public CallbackWrapper(Action<object> callback, object state, ICollection<CallbackWrapper> callbacks, object @lock)
{
_callbacks = callbacks;
_lock = @lock;
Callback = callback;
State = state;
}
public void Invoke()
{
Callback.Invoke(State);
}
public void Dispose()
{
lock (_lock)
{
_callbacks.Remove(this);
}
}
public Action<object> Callback { get; }
public object State { get; }
}
}
}

View File

@ -0,0 +1,16 @@
namespace Ocelot.Configuration.ChangeTracking
{
using Microsoft.Extensions.Primitives;
public class OcelotConfigurationChangeTokenSource : IOcelotConfigurationChangeTokenSource
{
private readonly OcelotConfigurationChangeToken _changeToken = new OcelotConfigurationChangeToken();
public IChangeToken ChangeToken => _changeToken;
public void Activate()
{
_changeToken.Activate();
}
}
}

View File

@ -0,0 +1,30 @@
namespace Ocelot.Configuration.ChangeTracking
{
using System;
using Microsoft.Extensions.Options;
using Ocelot.Configuration.Repository;
public class OcelotConfigurationMonitor : IOptionsMonitor<IInternalConfiguration>
{
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
private readonly IInternalConfigurationRepository _repo;
public OcelotConfigurationMonitor(IInternalConfigurationRepository repo, IOcelotConfigurationChangeTokenSource changeTokenSource)
{
_changeTokenSource = changeTokenSource;
_repo = repo;
}
public IInternalConfiguration Get(string name)
{
return _repo.Get().Data;
}
public IDisposable OnChange(Action<IInternalConfiguration, string> listener)
{
return _changeTokenSource.ChangeToken.RegisterChangeCallback(_ => listener(CurrentValue, ""), null);
}
public IInternalConfiguration CurrentValue => _repo.Get().Data;
}
}

View File

@ -13,13 +13,15 @@ namespace Ocelot.Configuration.Creator
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IAdministrationPath _adminPath;
private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator;
private readonly IVersionCreator _versionCreator;
public ConfigurationCreator(
IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IServiceProvider serviceProvider,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IVersionCreator versionCreator
)
{
_adminPath = serviceProvider.GetService<IAdministrationPath>();
@ -27,6 +29,7 @@ namespace Ocelot.Configuration.Creator
_serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator;
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
_versionCreator = versionCreator;
}
public InternalConfiguration Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes)
@ -41,6 +44,8 @@ namespace Ocelot.Configuration.Creator
var adminPath = _adminPath != null ? _adminPath.Path : null;
var version = _versionCreator.Create(fileConfiguration.GlobalConfiguration.DownstreamHttpVersion);
return new InternalConfiguration(reRoutes,
adminPath,
serviceProviderConfiguration,
@ -48,7 +53,8 @@ namespace Ocelot.Configuration.Creator
lbOptions,
fileConfiguration.GlobalConfiguration.DownstreamScheme,
qosOptions,
httpHandlerOptions
httpHandlerOptions,
version
);
}
}

View File

@ -8,10 +8,12 @@ namespace Ocelot.Configuration.Creator
public class DynamicsCreator : IDynamicsCreator
{
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
private readonly IVersionCreator _versionCreator;
public DynamicsCreator(IRateLimitOptionsCreator rateLimitOptionsCreator)
public DynamicsCreator(IRateLimitOptionsCreator rateLimitOptionsCreator, IVersionCreator versionCreator)
{
_rateLimitOptionsCreator = rateLimitOptionsCreator;
_versionCreator = versionCreator;
}
public List<ReRoute> Create(FileConfiguration fileConfiguration)
@ -26,10 +28,13 @@ namespace Ocelot.Configuration.Creator
var rateLimitOption = _rateLimitOptionsCreator
.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
var version = _versionCreator.Create(fileDynamicReRoute.DownstreamHttpVersion);
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(rateLimitOption.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicReRoute.ServiceName)
.WithDownstreamHttpVersion(version)
.Build();
var reRoute = new ReRouteBuilder()

View File

@ -0,0 +1,17 @@
namespace Ocelot.Configuration.Creator
{
using System;
public class HttpVersionCreator : IVersionCreator
{
public Version Create(string downstreamHttpVersion)
{
if (!Version.TryParse(downstreamHttpVersion, out Version version))
{
version = new Version(1, 1);
}
return version;
}
}
}

View File

@ -0,0 +1,9 @@
namespace Ocelot.Configuration.Creator
{
using System;
public interface IVersionCreator
{
Version Create(string downstreamHttpVersion);
}
}

View File

@ -22,6 +22,7 @@ namespace Ocelot.Configuration.Creator
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
private readonly IReRouteKeyCreator _reRouteKeyCreator;
private readonly ISecurityOptionsCreator _securityOptionsCreator;
private readonly IVersionCreator _versionCreator;
public ReRoutesCreator(
IClaimsToThingCreator claimsToThingCreator,
@ -37,7 +38,8 @@ namespace Ocelot.Configuration.Creator
IDownstreamAddressesCreator downstreamAddressesCreator,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IReRouteKeyCreator reRouteKeyCreator,
ISecurityOptionsCreator securityOptionsCreator
ISecurityOptionsCreator securityOptionsCreator,
IVersionCreator versionCreator
)
{
_reRouteKeyCreator = reRouteKeyCreator;
@ -55,6 +57,7 @@ namespace Ocelot.Configuration.Creator
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_securityOptionsCreator = securityOptionsCreator;
_versionCreator = versionCreator;
}
public List<ReRoute> Create(FileConfiguration fileConfiguration)
@ -104,6 +107,8 @@ namespace Ocelot.Configuration.Creator
var securityOptions = _securityOptionsCreator.Create(fileReRoute.SecurityOptions);
var downstreamHttpVersion = _versionCreator.Create(fileReRoute.DownstreamHttpVersion);
var reRoute = new DownstreamReRouteBuilder()
.WithKey(fileReRoute.Key)
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
@ -138,6 +143,8 @@ namespace Ocelot.Configuration.Creator
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
.WithSecurityOptions(securityOptions)
.WithDownstreamHttpVersion(downstreamHttpVersion)
.WithDownStreamHttpMethod(fileReRoute.DownstreamHttpMethod)
.Build();
return reRoute;

View File

@ -1,6 +1,7 @@
namespace Ocelot.Configuration
{
using Creator;
using System;
using System.Collections.Generic;
using Values;
@ -38,7 +39,9 @@ namespace Ocelot.Configuration
List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream,
bool dangerousAcceptAnyServerCertificateValidator,
SecurityOptions securityOptions)
SecurityOptions securityOptions,
string downstreamHttpMethod,
Version downstreamHttpVersion)
{
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream;
@ -72,6 +75,8 @@ namespace Ocelot.Configuration
LoadBalancerKey = loadBalancerKey;
AddHeadersToUpstream = addHeadersToUpstream;
SecurityOptions = securityOptions;
DownstreamHttpMethod = downstreamHttpMethod;
DownstreamHttpVersion = downstreamHttpVersion;
}
public string Key { get; }
@ -106,5 +111,7 @@ namespace Ocelot.Configuration
public List<AddHeader> AddHeadersToUpstream { get; }
public bool DangerousAcceptAnyServerCertificateValidator { get; }
public SecurityOptions SecurityOptions { get; }
public string DownstreamHttpMethod { get; }
public Version DownstreamHttpVersion { get; }
}
}

View File

@ -4,5 +4,6 @@ namespace Ocelot.Configuration.File
{
public string ServiceName { get; set; }
public FileRateLimitRule RateLimitRule { get; set; }
public string DownstreamHttpVersion { get; set; }
}
}

View File

@ -26,5 +26,7 @@
public string DownstreamScheme { get; set; }
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public string DownstreamHttpVersion { get; set; }
}
}

View File

@ -29,6 +29,7 @@ namespace Ocelot.Configuration.File
public string DownstreamPathTemplate { get; set; }
public string UpstreamPathTemplate { get; set; }
public List<string> UpstreamHttpMethod { get; set; }
public string DownstreamHttpMethod { get; set; }
public Dictionary<string, string> AddHeadersToRequest { get; set; }
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
@ -55,5 +56,6 @@ namespace Ocelot.Configuration.File
public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
public FileSecurityOptions SecurityOptions { get; set; }
public string DownstreamHttpVersion { get; set; }
}
}

View File

@ -2,6 +2,8 @@ using System.Collections.Generic;
namespace Ocelot.Configuration
{
using System;
public interface IInternalConfiguration
{
List<ReRoute> ReRoutes { get; }
@ -19,5 +21,7 @@ namespace Ocelot.Configuration
QoSOptions QoSOptions { get; }
HttpHandlerOptions HttpHandlerOptions { get; }
Version DownstreamHttpVersion { get; }
}
}

View File

@ -2,6 +2,8 @@ using System.Collections.Generic;
namespace Ocelot.Configuration
{
using System;
public class InternalConfiguration : IInternalConfiguration
{
public InternalConfiguration(
@ -12,7 +14,8 @@ namespace Ocelot.Configuration
LoadBalancerOptions loadBalancerOptions,
string downstreamScheme,
QoSOptions qoSOptions,
HttpHandlerOptions httpHandlerOptions)
HttpHandlerOptions httpHandlerOptions,
Version downstreamHttpVersion)
{
ReRoutes = reRoutes;
AdministrationPath = administrationPath;
@ -22,6 +25,7 @@ namespace Ocelot.Configuration
DownstreamScheme = downstreamScheme;
QoSOptions = qoSOptions;
HttpHandlerOptions = httpHandlerOptions;
DownstreamHttpVersion = downstreamHttpVersion;
}
public List<ReRoute> ReRoutes { get; }
@ -32,5 +36,7 @@ namespace Ocelot.Configuration
public string DownstreamScheme { get; }
public QoSOptions QoSOptions { get; }
public HttpHandlerOptions HttpHandlerOptions { get; }
public Version DownstreamHttpVersion { get; }
}
}

View File

@ -4,18 +4,21 @@ using Ocelot.Configuration.File;
using Ocelot.Responses;
using System;
using System.Threading.Tasks;
using Ocelot.Configuration.ChangeTracking;
namespace Ocelot.Configuration.Repository
{
public class DiskFileConfigurationRepository : IFileConfigurationRepository
{
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
private readonly string _environmentFilePath;
private readonly string _ocelotFilePath;
private static readonly object _lock = new object();
private const string ConfigurationFileName = "ocelot";
public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment)
public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment, IOcelotConfigurationChangeTokenSource changeTokenSource)
{
_changeTokenSource = changeTokenSource;
_environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
_ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
@ -56,6 +59,7 @@ namespace Ocelot.Configuration.Repository
System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
}
_changeTokenSource.Activate();
return Task.FromResult<Response>(new OkResponse());
}
}

View File

@ -1,4 +1,5 @@
using Ocelot.Responses;
using Ocelot.Configuration.ChangeTracking;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
@ -10,6 +11,12 @@ namespace Ocelot.Configuration.Repository
private static readonly object LockObject = new object();
private IInternalConfiguration _internalConfiguration;
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
public InMemoryInternalConfigurationRepository(IOcelotConfigurationChangeTokenSource changeTokenSource)
{
_changeTokenSource = changeTokenSource;
}
public Response<IInternalConfiguration> Get()
{
@ -23,6 +30,7 @@ namespace Ocelot.Configuration.Repository
_internalConfiguration = internalConfiguration;
}
_changeTokenSource.Activate();
return new OkResponse();
}
}

View File

@ -83,6 +83,11 @@
RuleForEach(reRoute => reRoute.DownstreamHostAndPorts)
.SetValidator(hostAndPortValidator);
});
When(reRoute => !string.IsNullOrEmpty(reRoute.DownstreamHttpVersion), () =>
{
RuleFor(r => r.DownstreamHttpVersion).Matches("^[0-9]([.,][0-9]{1,1})?$");
});
}
private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken)

View File

@ -1,9 +1,12 @@
using Ocelot.Configuration.ChangeTracking;
namespace Ocelot.DependencyInjection
{
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Ocelot.Authorisation;
using Ocelot.Cache;
using Ocelot.Claims;
@ -112,6 +115,8 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
Services.TryAddSingleton<ICacheKeyGenerator, CacheKeyGenerator>();
Services.TryAddSingleton<IOcelotConfigurationChangeTokenSource, OcelotConfigurationChangeTokenSource>();
Services.TryAddSingleton<IOptionsMonitor<IInternalConfiguration>, OcelotConfigurationMonitor>();
// 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
@ -131,6 +136,7 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
Services.TryAddSingleton<IQoSFactory, QoSFactory>();
Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>();
Services.TryAddSingleton<IVersionCreator, HttpVersionCreator>();
//add security
this.AddSecurity();
@ -215,7 +221,7 @@ namespace Ocelot.DependencyInjection
{
// see: https://greatrexpectations.com/2018/10/25/decorators-in-net-core-with-dependency-injection
var wrappedDescriptor = Services.First(x => x.ServiceType == typeof(IPlaceholders));
var objectFactory = ActivatorUtilities.CreateFactory(
typeof(ConfigAwarePlaceholders),
new[] { typeof(IPlaceholders) });
@ -229,7 +235,7 @@ namespace Ocelot.DependencyInjection
return this;
}
private static object CreateInstance(IServiceProvider services, ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance != null)

View File

@ -1,5 +1,6 @@
namespace Ocelot.DownstreamRouteFinder.Finder
{
using System;
using Configuration;
using Configuration.Builder;
using Configuration.Creator;
@ -54,6 +55,7 @@
.WithQosOptions(qosOptions)
.WithDownstreamScheme(configuration.DownstreamScheme)
.WithLoadBalancerOptions(configuration.LoadBalancerOptions)
.WithDownstreamHttpVersion(configuration.DownstreamHttpVersion)
.WithUpstreamPathTemplate(upstreamPathTemplate);
var rateLimitOptions = configuration.ReRoutes != null

View File

@ -15,4 +15,4 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]

View File

@ -1,12 +1,13 @@
namespace Ocelot.Request.Mapper
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Responses;
using System.Net.Http;
using System.Threading.Tasks;
public interface IRequestMapper
{
Task<Response<HttpRequestMessage>> Map(HttpRequest request);
Task<Response<HttpRequestMessage>> Map(HttpRequest request, DownstreamReRoute downstreamReRoute);
}
}

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Primitives;
using Ocelot.Configuration;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
@ -15,15 +16,16 @@
{
private readonly string[] _unsupportedHeaders = { "host" };
public async Task<Response<HttpRequestMessage>> Map(HttpRequest request)
public async Task<Response<HttpRequestMessage>> Map(HttpRequest request, DownstreamReRoute downstreamReRoute)
{
try
{
var requestMessage = new HttpRequestMessage()
{
Content = await MapContent(request),
Method = MapMethod(request),
RequestUri = MapUri(request)
Method = MapMethod(request, downstreamReRoute),
RequestUri = MapUri(request),
Version = downstreamReRoute.DownstreamHttpVersion,
};
MapHeaders(request, requestMessage);
@ -71,8 +73,13 @@
}
}
private HttpMethod MapMethod(HttpRequest request)
private HttpMethod MapMethod(HttpRequest request, DownstreamReRoute downstreamReRoute)
{
if (!string.IsNullOrEmpty(downstreamReRoute?.DownstreamHttpMethod))
{
return new HttpMethod(downstreamReRoute.DownstreamHttpMethod);
}
return new HttpMethod(request.Method);
}

View File

@ -52,6 +52,7 @@ namespace Ocelot.Request.Middleware
};
_request.RequestUri = uriBuilder.Uri;
_request.Method = new HttpMethod(Method);
return _request;
}

View File

@ -24,7 +24,7 @@ namespace Ocelot.Request.Middleware
public async Task Invoke(DownstreamContext context)
{
var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request);
var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request, context.DownstreamReRoute);
if (downstreamRequest.IsError)
{

View File

@ -1,4 +1,3 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline;
namespace Ocelot.Request.Middleware