Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,19 +305,25 @@ Below is an example configuration that will transforms claims to query string pa
This shows a transform where Ocelot looks at the users LocationId claim and add its as
a query string parameter to be forwarded onto the downstream service.

## Logging
## Quality of Service

Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
for the standard asp.net core logging stuff at the moment.
Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
.NET library called Polly check them out [here](https://github.com/App-vNext/Polly).

There are a bunch of debugging logs in the ocelot middlewares however I think the
system probably needs more logging in the code it calls into. Other than the debugging
there is a global error handler that should catch any errors thrown and log them as errors.
Add the following section to a ReRoute configuration.

The reason for not just using bog standard framework logging is that I could not
work out how to override the request id that get's logged when setting IncludeScopes
to true for logging settings. Nicely onto the next feature.
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking":3,
"DurationOfBreak":5,
"TimeoutValue":5000
}

You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be
implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped.
TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out.

If you do not add a QoS section QoS will not be used.

## RequestId / CorrelationId

Expand Down Expand Up @@ -404,6 +410,20 @@ http request before it is passed to Ocelots request creator.
Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added
after as Ocelot does not call the next middleware.

## Logging

Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
for the standard asp.net core logging stuff at the moment.

There are a bunch of debugging logs in the ocelot middlewares however I think the
system probably needs more logging in the code it calls into. Other than the debugging
there is a global error handler that should catch any errors thrown and log them as errors.

The reason for not just using bog standard framework logging is that I could not
work out how to override the request id that get's logged when setting IncludeScopes
to true for logging settings. Nicely onto the next feature.

## Not supported

Ocelot does not support...
Expand Down
34 changes: 34 additions & 0 deletions src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Ocelot.Configuration.Builder
{
public class QoSOptionsBuilder
{
private int _exceptionsAllowedBeforeBreaking;

private int _durationOfBreak;

private int _timeoutValue;

public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking)
{
_exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
return this;
}

public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak)
{
_durationOfBreak = durationOfBreak;
return this;
}

public QoSOptionsBuilder WithTimeoutValue(int timeoutValue)
{
_timeoutValue = timeoutValue;
return this;
}

public QoSOptions Build()
{
return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue);
}
}
}
7 changes: 0 additions & 7 deletions src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public class ReRouteBuilder
private string _requestIdHeaderKey;
private bool _isCached;
private CacheOptions _fileCacheOptions;
private string _serviceName;
private string _downstreamScheme;
private string _downstreamHost;
private int _downstreamPort;
Expand All @@ -49,12 +48,6 @@ public ReRouteBuilder WithDownstreamHost(string downstreamHost)
return this;
}

public ReRouteBuilder WithServiceName(string serviceName)
{
_serviceName = serviceName;
return this;
}

public ReRouteBuilder WithDownstreamPathTemplate(string input)
{
_downstreamPathTemplate = input;
Expand Down
60 changes: 48 additions & 12 deletions src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand All @@ -10,9 +9,9 @@
using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Ocelot.Utilities;
using Ocelot.Values;

namespace Ocelot.Configuration.Creator
{
Expand All @@ -26,22 +25,29 @@ public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator
private const string RegExMatchEverything = ".*";
private const string RegExMatchEndString = "$";
private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashOnly = "^/$";

private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
private readonly ILoadBalancerFactory _loadBalanceFactory;
private readonly ILoadBalancerHouse _loadBalancerHouse;
private readonly IQoSProviderFactory _qoSProviderFactory;
private readonly IQosProviderHouse _qosProviderHouse;

public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options,
IConfigurationValidator configurationValidator,
IClaimToThingConfigurationParser claimToThingConfigurationParser,
ILogger<FileOcelotConfigurationCreator> logger,
ILoadBalancerFactory loadBalancerFactory,
ILoadBalancerHouse loadBalancerHouse)
ILoadBalancerHouse loadBalancerHouse,
IQoSProviderFactory qoSProviderFactory,
IQosProviderHouse qosProviderHouse)
{
_loadBalanceFactory = loadBalancerFactory;
_loadBalancerHouse = loadBalancerHouse;
_qoSProviderFactory = qoSProviderFactory;
_qosProviderHouse = qosProviderHouse;
_options = options;
_configurationValidator = configurationValidator;
_claimToThingConfigurationParser = claimToThingConfigurationParser;
Expand Down Expand Up @@ -86,28 +92,30 @@ private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConf
{
var isAuthenticated = IsAuthenticated(fileReRoute);

var isAuthorised = IsAuthenticated(fileReRoute);
var isAuthorised = IsAuthorised(fileReRoute);

var isCached = IsCached(fileReRoute);

var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration);

var loadBalancerKey = BuildLoadBalancerKey(fileReRoute);
var reRouteKey = BuildReRouteKey(fileReRoute);

var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute);
var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0;
var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute);

var isQos = IsQoS(fileReRoute);

var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration);

var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute);

var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest);


var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest);

var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest);

var qosOptions = BuildQoSOptions(fileReRoute);

var reRoute = new ReRouteBuilder()
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
Expand All @@ -127,14 +135,31 @@ private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConf
.WithLoadBalancer(fileReRoute.LoadBalancer)
.WithDownstreamHost(fileReRoute.DownstreamHost)
.WithDownstreamPort(fileReRoute.DownstreamPort)
.WithLoadBalancerKey(loadBalancerKey)
.WithLoadBalancerKey(reRouteKey)
.WithServiceProviderConfiguraion(serviceProviderConfiguration)
.WithIsQos(isQos)
.WithQosOptions(qosOptions)
.Build();

await SetupLoadBalancer(reRoute);
SetupQosProvider(reRoute);
return reRoute;
}

private QoSOptions BuildQoSOptions(FileReRoute fileReRoute)
{
return new QoSOptionsBuilder()
.WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking)
.WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak)
.WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue)
.Build();
}

private bool IsQoS(FileReRoute fileReRoute)
{
return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0;
}

private bool IsAuthenticated(FileReRoute fileReRoute)
{
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
Expand All @@ -161,7 +186,7 @@ private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration g
return requestIdKey;
}

private string BuildLoadBalancerKey(FileReRoute fileReRoute)
private string BuildReRouteKey(FileReRoute fileReRoute)
{
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}";
Expand All @@ -183,7 +208,13 @@ private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute
private async Task SetupLoadBalancer(ReRoute reRoute)
{
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
_loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer);
_loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer);
}

private void SetupQosProvider(ReRoute reRoute)
{
var loadBalancer = _qoSProviderFactory.Get(reRoute);
_qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer);
}

private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
Expand All @@ -204,7 +235,7 @@ private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRout
.Build();
}

private string BuildUpstreamTemplate(FileReRoute reRoute)
private string BuildUpstreamTemplatePattern(FileReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate;

Expand All @@ -228,6 +259,11 @@ private string BuildUpstreamTemplate(FileReRoute reRoute)
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
}

if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}

var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
Expand Down
13 changes: 7 additions & 6 deletions src/Ocelot/Configuration/QoSOptions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Polly.Timeout;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Polly.Timeout;

namespace Ocelot.Configuration
{
public class QoSOptions
{
public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
public QoSOptions(
int exceptionsAllowedBeforeBreaking,
int durationofBreak,
int timeoutValue,
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
{
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak);
Expand Down
6 changes: 3 additions & 3 deletions src/Ocelot/Configuration/ReRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public ReRoute(PathTemplate downstreamPathTemplate,
string loadBalancer,
string downstreamHost,
int downstreamPort,
string loadBalancerKey,
string reRouteKey,
ServiceProviderConfiguraion serviceProviderConfiguraion,
bool isQos,
QoSOptions qos)
{
LoadBalancerKey = loadBalancerKey;
ReRouteKey = reRouteKey;
ServiceProviderConfiguraion = serviceProviderConfiguraion;
LoadBalancer = loadBalancer;
DownstreamHost = downstreamHost;
Expand All @@ -57,7 +57,7 @@ public ReRoute(PathTemplate downstreamPathTemplate,
QosOptions = qos;
}

public string LoadBalancerKey {get;private set;}
public string ReRouteKey {get;private set;}
public PathTemplate DownstreamPathTemplate { get; private set; }
public PathTemplate UpstreamPathTemplate { get; private set; }
public string UpstreamTemplatePattern { get; private set; }
Expand Down
3 changes: 3 additions & 0 deletions src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using Ocelot.QueryStrings;
using Ocelot.Request.Builder;
using Ocelot.Requester;
using Ocelot.Requester.QoS;
using Ocelot.Responder;
using Ocelot.ServiceDiscovery;

Expand Down Expand Up @@ -61,6 +62,8 @@ public static IServiceCollection AddOcelot(this IServiceCollection services)
{
services.AddMvcCore().AddJsonFormatters();
services.AddLogging();
services.AddSingleton<IQosProviderHouse, QosProviderHouse>();
services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>();
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public async Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstream

foreach (var reRoute in applicableReRoutes)
{
if (upstreamUrlPath == reRoute.UpstreamTemplatePattern)
{
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);

return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
}

var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);

if (urlMatch.Data.Match)
Expand Down
4 changes: 3 additions & 1 deletion src/Ocelot/Errors/OcelotErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public enum OcelotErrorCode
ServicesAreNullError,
ServicesAreEmptyError,
UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError
UnableToFindLoadBalancerError,
RequestTimedOutError,
UnableToFindQoSProviderError
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.QueryStrings.Middleware;
using Ocelot.ServiceDiscovery;

namespace Ocelot.LoadBalancer.Middleware
{
Expand All @@ -31,7 +30,7 @@ public async Task Invoke(HttpContext context)
{
_logger.LogDebug("started calling load balancing middleware");

var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey);
var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.ReRouteKey);
if(loadBalancer.IsError)
{
SetPipelineError(loadBalancer.Errors);
Expand Down
Loading