Merge branch 'develop' into feature/Optimization-HttpClient-instance

This commit is contained in:
geffzhang 2017-03-07 08:37:16 +08:00 committed by GitHub
commit 2e1708ef1e
55 changed files with 2023 additions and 1319 deletions

519
README.md
View File

@ -26,531 +26,34 @@ it reaches a request builder middleware where it creates a HttpRequestMessage ob
used to make a request to a downstream service. The middleware that makes the request is used to make a request to a downstream service. The middleware that makes the request is
the last thing in the Ocelot pipeline. It does not call the next middleware. the last thing in the Ocelot pipeline. It does not call the next middleware.
The response from the downstream service is stored in a per request scoped repository The response from the downstream service is stored in a per request scoped repository
and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware
that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client.
That is basically it with a bunch of other features. That is basically it with a bunch of other features.
## Contributing
Pull requests, issues and commentary welcome! No special process just create a request and get in
touch either via gitter or create an issue.
## Building Ocelot
You should be able to just build Ocelot using the ./build.ps1 or ./build.sh scripts if you are on
windows or mac. Alternatively you can build the project in VS2015 with the latest .NET Core SDK.
The tests should all just run and work apart from the integration tests which need the following
environmental variables setting. This is a manual step at the moment.
OCELOT_USERNAME=admin
OCELOT_HASH=kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=
OCELOT_SALT=zzWITpnDximUNKYLiUam/w==
On windows you can use..
SETX OCELOT_USERNAME admin
On mac..
export OCELOT_USERNAME=admin
I need to work out a nicer way of doing this in the future.
## How to install ## How to install
Ocelot is designed to work with ASP.NET core only and is currently Ocelot is designed to work with ASP.NET core only and is currently
built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
Install Ocelot and it's dependecies using nuget. At the moment Install Ocelot and it's dependencies using NuGet.
all we have is the pre version. Once we have something working in
a half decent way we will drop a version.
`Install-Package Ocelot` `Install-Package Ocelot`
All versions can be found [here](https://www.nuget.org/packages/Ocelot/) All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
## Configuration ## Documentation
An example configuration can be found [here](https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/configuration.json) Please click [here](https://github.com/TomPallister/Ocelot/wiki) for the Ocleot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers.
and an explained configuration can be found [here](https://github.com/TomPallister/Ocelot/blob/develop/configuration-explanation.txt). More detailed instructions to come on how to configure this.
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. ## Coming up
The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
if you don't want to manage lots of ReRoute specific settings.
```json You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)
{
"ReRoutes": [],
"GlobalConfiguration": {}
}
```
More information on how to use these options is below..
## Startup ## Contributing
An example startup using a json file for configuration can be seen below. Pull requests, issues and commentary welcome! No special process just create a request and get in
Currently this is the only way to get configuration into Ocelot. touch either via gitter or create an issue.
```csharp
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
Action<ConfigurationBuilderCachePart> settings = (x) =>
{
x.WithMicrosoftLogging(log =>
{
log.AddConsole(LogLevel.Debug);
})
.WithDictionaryHandle();
};
services.AddOcelotOutputCaching(settings);
services.AddOcelot(Configuration);
}
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
await app.UseOcelot();
}
}
}
```
Then in your Program.cs you will want to have the following. This can be changed if you
don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration.
```csharp
public class Program
{
public static void Main(string[] args)
{
IWebHostBuilder builder = new WebHostBuilder();
builder.ConfigureServices(s => {
s.AddSingleton(builder);
});
builder.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>();
var host = builder.Build();
host.Run();
}
}
```
Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot
find a better way of doing this at the moment without setting this in a static or some kind of config.
This is pretty much all you need to get going.......more to come!
## Routing
Ocelot's primary functionality is to take incomeing http requests and forward them on
to a downstream service. At the moment in the form of another http request (in the future
this could be any transport mechanism.).
Ocelot always adds a trailing slash to an UpstreamPathTemplate.
Ocelot's describes the routing of one request to another as a ReRoute. In order to get
anything working in Ocelot you need to set up a ReRoute in the configuration.
```json
{
"ReRoutes": [
]
}
```
In order to set up a ReRoute you need to add one to the json array called ReRoutes like
the following.
```json
{
"DownstreamPathTemplate": "/api/posts/{postId}",
"DownstreamScheme": "https",
"DownstreamPort": 80,
"DownstreamHost" "localhost"
"UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Put"
}
```
The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to.
The UpstreamPathTemplate is the URL that Ocelot will use to identity which
DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so
Ocelot can distinguish between requests to the same URL and is obviously needed to work :)
In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is
Ocelot will attempt to replace the placeholder with the correct variable value from the
Upstream URL when the request comes in.
At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive.
In order to change this you can specify on a per ReRoute basis the following setting.
```json
"ReRouteIsCaseSensitive": true
```
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
evaluation will be case sensitive. This setting defaults to false so only set it if you want
the ReRoute to be case sensitive is my advice!
## Administration
Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated
using bearer tokens that you request from iteself. This is provided by the amazing [IdentityServer](https://github.com/IdentityServer/IdentityServer4)
project that I have been using for a few years now. Check them out.
In order to enable the administration section you need to do a few things. First of all add this to your
initial configuration.json. The value can be anything you want and it is obviously reccomended don't use
a url you would like to route through with Ocelot as this will not work. The administration uses the
MapWhen functionality of asp.net core and all requests to root/administration will be sent there not
to the Ocelot middleware.
```json
"GlobalConfiguration": {
"AdministrationPath": "/administration"
}
```
This will get the admin area set up but not the authentication. Please note that this is a very basic approach to
this problem and if needed we can obviously improve on this!
You need to set 3 environmental variables.
OCELOT_USERNAME
OCELOT_HASH
OCELOT_SALT
These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to
use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to
supply username and password.
In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt()
this technique is based on [this](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing)
using SHA256 rather than SHA1.
Now if you went with the configuration options above and want to access the API you can use the postman scripts
called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these
will need to be changed if you are running Ocelot on a different url to http://localhost:5000.
The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST
a configuration.
## Service Discovery
Ocelot allows you to specify a service discovery provider and will use this to find the host and port
for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the
GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes
you specify a ServiceName for at ReRoute level.
In the future we can add a feature that allows ReRoute specfic configuration.
At the moment the only supported service discovery provider is Consul. The following is required in the
GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default
will be used.
```json
"ServiceDiscoveryProvider": {
"Provider":"Consul",
"Host":"localhost",
"Port":8500
}
```
In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the
ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin
and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests.
```json
{
"DownstreamPathTemplate": "/api/posts/{postId}",
"DownstreamScheme": "https",
"UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Put",
"ServiceName": "product",
"LoadBalancer": "LeastConnection"
}
```
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balancer
requests across any available services.
## Authentication
Ocelot currently supports the use of bearer tokens with Identity Server (more providers to
come if required). In order to identity a ReRoute as authenticated it needs the following
configuration added.
```json
"AuthenticationOptions": {
"Provider": "IdentityServer",
"ProviderRootUrl": "http://localhost:52888",
"ScopeName": "api",
"AdditionalScopes": [
"openid",
"offline_access"
],
"ScopeSecret": "secret"
}
```
In this example the Provider is specified as IdentityServer. This string is important
because it is used to identity the authentication provider (as previously mentioned in
the future there might be more providers). Identity server requires that the client
talk to it so we need to provide the root url of the IdentityServer as ProviderRootUrl.
IdentityServer requires at least one scope and you can also provider additional scopes.
Finally if you are using IdentityServer reference tokens you need to provide the scope
secret.
Ocelot will use this configuration to build an authentication handler and if
authentication is succefull the next middleware will be called else the response
is 401 unauthorised.
## Authorisation
Ocelot supports claims based authorisation which is run post authentication. This means if
you have a route you want to authorise you can add the following to you ReRoute configuration.
```json
"RouteClaimsRequirement": {
"UserType": "registered"
},
```
In this example when the authorisation middleware is called Ocelot will check to see
if the user has the claim type UserType and if the value of that claim is registered.
If it isn't then the user will not be authorised and the response will be 403 forbidden.
## Claims Tranformation
Ocelot allows the user to access claims and transform them into headers, query string
parameters and other claims. This is only available once a user has been authenticated.
After the user is authenticated we run the claims to claims transformation middleware.
This allows the user to transform claims before the authorisation middleware is called.
After the user is authorised first we call the claims to headers middleware and Finally
the claims to query strig parameters middleware.
The syntax for performing the transforms is the same for each proces. In the ReRoute
configuration a json dictionary is added with a specific name either AddClaimsToRequest,
AddHeadersToRequest, AddQueriesToRequest.
Note I'm not a hotshot programmer so have no idea if this syntax is good..
Within this dictionary the entries specify how Ocelot should transform things!
The key to the dictionary is going to become the key of either a claim, header
or query parameter.
The value of the entry is parsed to logic that will perform the transform. First of
all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want
to access the claims and get the CustomerId claim type. Next is a greater than (>)
symbol which is just used to split the string. The next entry is either value or value with
and indexer. If value is specifed Ocelot will just take the value and add it to the
transform. If the value has an indexer Ocelot will look for a delimiter which is provided
after another greater than symbol. Ocelot will then split the value on the delimiter
and add whatever was at the index requested to the transform.
#### Claims to Claims Tranformation
Below is an example configuration that will transforms claims to claims
```json
"AddClaimsToRequest": {
"UserType": "Claims[sub] > value[0] > |",
"UserId": "Claims[sub] > value[1] > |"
},
```
This shows a transforms where Ocelot looks at the users sub claim and transforms it into
UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue".
#### Claims to Headers Tranformation
Below is an example configuration that will transforms claims to headers
```json
"AddHeadersToRequest": {
"CustomerId": "Claims[sub] > value[1] > |"
},
```
This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a
CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue".
#### Claims to Query String Parameters Tranformation
Below is an example configuration that will transforms claims to query string parameters
```json
"AddQueriesToRequest": {
"LocationId": "Claims[LocationId] > value",
},
```
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.
## Quality of Service
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).
Add the following section to a ReRoute configuration.
```json
"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
Ocelot supports a client sending a request id in the form of a header. If set Ocelot will
use the requestid for logging as soon as it becomes available in the middleware pipeline.
Ocelot will also forward the request id with the specified header to the downstream service.
I'm not sure if have this spot on yet in terms of the pipeline order becasue there are a few logs
that don't get the users request id at the moment and ocelot just logs not set for request id
which sucks. You can still get the framework request id in the logs if you set
IncludeScopes true in your logging config. This can then be used to match up later logs that do
have an OcelotRequestId.
In order to use the requestid feature in your ReRoute configuration add this setting
```json
"RequestIdKey": "OcRequestId"
```
In this example OcRequestId is the request header that contains the clients request id.
There is also a setting in the GlobalConfiguration section which will override whatever has been
set at ReRoute level for the request id. The setting is as fllows.
```json
"RequestIdKey": "OcRequestId",
```
It behaves in exactly the same way as the ReRoute level RequestIdKey settings.
## Caching
Ocelot supports some very rudimentary caching at the moment provider by
the [CacheManager](http://cachemanager.net/) project. This is an amazing project
that is solving a lot of caching problems. I would reccomend using this package to
cache with Ocelot. If you look at the example [here](https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Startup.cs)
you can see how the cache manager is setup and then passed into the Ocelot
AddOcelotOutputCaching configuration method. You can use any settings supported by
the CacheManager package and just pass them in.
Anyway Ocelot currently supports caching on the URL of the downstream service
and setting a TTL in seconds to expire the cache. More to come!
In orde to use caching on a route in your ReRoute configuration add this setting.
```json
"FileCacheOptions": { "TtlSeconds": 15 }
```
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
## Ocelot Middleware injection and overrides
Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware
pipeline and you are using any of the following. Remove them and try again!
When setting up Ocelot in your Startup.cs you can provide some additonal middleware
and override middleware. This is done as follos.
```csharp
var configuration = new OcelotMiddlewareConfiguration
{
PreErrorResponderMiddleware = async (ctx, next) =>
{
await next.Invoke();
}
};
app.UseOcelot(configuration);
```
In the example above the provided function will run before the first piece of Ocelot middleware.
This allows a user to supply any behaviours they want before and after the Ocelot pipeline has run.
This means you can break everything so use at your own pleasure!
The user can set functions against the following.
+ PreErrorResponderMiddleware - Already explained above.
+ PreAuthenticationMiddleware - This allows the user to run pre authentication logic and then call
Ocelot's authentication middleware.
+ AuthenticationMiddleware - This overrides Ocelots authentication middleware.
+ PreAuthorisationMiddleware - This allows the user to run pre authorisation logic and then call
Ocelot's authorisation middleware.
+ AuthorisationMiddleware - This overrides Ocelots authorisation middleware.
+ PreQueryStringBuilderMiddleware - This alows the user to manipulate the query string on the
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...
+ Chunked Encoding - Ocelot will always get the body size and return Content-Length
header. Sorry if this doesn't work for your use case!
+ Fowarding a host header - The host header that you send to Ocelot will not be
forwarded to the downstream service. Obviously this would break everything :(
## Things that are currently annoying me ## Things that are currently annoying me
@ -562,9 +65,5 @@ that isnt available is annoying. Let alone it be null.
[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) [![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
## Coming up
You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)

View File

@ -1 +1 @@
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} {"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}}

View File

@ -0,0 +1,71 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.Builder
{
public class RateLimitOptionsBuilder
{
private bool _enableRateLimiting;
private string _clientIdHeader;
private List<string> _clientWhitelist;
private bool _disableRateLimitHeaders;
private string _quotaExceededMessage;
private string _rateLimitCounterPrefix;
private RateLimitRule _rateLimitRule;
private int _httpStatusCode;
public RateLimitOptionsBuilder WithEnableRateLimiting(bool enableRateLimiting)
{
_enableRateLimiting = enableRateLimiting;
return this;
}
public RateLimitOptionsBuilder WithClientIdHeader(string clientIdheader)
{
_clientIdHeader = clientIdheader;
return this;
}
public RateLimitOptionsBuilder WithClientWhiteList(List<string> clientWhitelist)
{
_clientWhitelist = clientWhitelist;
return this;
}
public RateLimitOptionsBuilder WithDisableRateLimitHeaders(bool disableRateLimitHeaders)
{
_disableRateLimitHeaders = disableRateLimitHeaders;
return this;
}
public RateLimitOptionsBuilder WithQuotaExceededMessage(string quotaExceededMessage)
{
_quotaExceededMessage = quotaExceededMessage;
return this;
}
public RateLimitOptionsBuilder WithRateLimitCounterPrefix(string rateLimitCounterPrefix)
{
_rateLimitCounterPrefix = rateLimitCounterPrefix;
return this;
}
public RateLimitOptionsBuilder WithRateLimitRule(RateLimitRule rateLimitRule)
{
_rateLimitRule = rateLimitRule;
return this;
}
public RateLimitOptionsBuilder WithHttpStatusCode(int httpStatusCode)
{
_httpStatusCode = httpStatusCode;
return this;
}
public RateLimitOptions Build()
{
return new RateLimitOptions(_enableRateLimiting, _clientIdHeader, _clientWhitelist,
_disableRateLimitHeaders, _quotaExceededMessage, _rateLimitCounterPrefix,
_rateLimitRule, _httpStatusCode);
}
}
}

View File

@ -25,7 +25,7 @@ namespace Ocelot.Configuration.Builder
private string _downstreamHost; private string _downstreamHost;
private int _downstreamPort; private int _downstreamPort;
private string _loadBalancer; private string _loadBalancer;
private ServiceProviderConfiguraion _serviceProviderConfiguraion; private ServiceProviderConfiguration _serviceProviderConfiguraion;
private bool _useQos; private bool _useQos;
private QoSOptions _qosOptions; private QoSOptions _qosOptions;
public bool _enableRateLimiting; public bool _enableRateLimiting;
@ -150,7 +150,7 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion) public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguration serviceProviderConfiguraion)
{ {
_serviceProviderConfiguraion = serviceProviderConfiguraion; _serviceProviderConfiguraion = serviceProviderConfiguraion;
return this; return this;

View File

@ -0,0 +1,46 @@
namespace Ocelot.Configuration.Builder
{
public class ReRouteOptionsBuilder
{
private bool _isAuthenticated;
private bool _isAuthorised;
private bool _isCached;
private bool _isQoS;
private bool _enableRateLimiting;
public ReRouteOptionsBuilder WithIsCached(bool isCached)
{
_isCached = isCached;
return this;
}
public ReRouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated)
{
_isAuthenticated = isAuthenticated;
return this;
}
public ReRouteOptionsBuilder WithIsAuthorised(bool isAuthorised)
{
_isAuthorised = isAuthorised;
return this;
}
public ReRouteOptionsBuilder WithIsQos(bool isQoS)
{
_isQoS = isQoS;
return this;
}
public ReRouteOptionsBuilder WithRateLimiting(bool enableRateLimiting)
{
_enableRateLimiting = enableRateLimiting;
return this;
}
public ReRouteOptions Build()
{
return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _isQoS, _enableRateLimiting);
}
}
}

View File

@ -1,6 +1,6 @@
namespace Ocelot.Configuration.Builder namespace Ocelot.Configuration.Builder
{ {
public class ServiceProviderConfiguraionBuilder public class ServiceProviderConfigurationBuilder
{ {
private string _serviceName; private string _serviceName;
private string _downstreamHost; private string _downstreamHost;
@ -10,52 +10,52 @@ namespace Ocelot.Configuration.Builder
private string _serviceDiscoveryProviderHost; private string _serviceDiscoveryProviderHost;
private int _serviceDiscoveryProviderPort; private int _serviceDiscoveryProviderPort;
public ServiceProviderConfiguraionBuilder WithServiceName(string serviceName) public ServiceProviderConfigurationBuilder WithServiceName(string serviceName)
{ {
_serviceName = serviceName; _serviceName = serviceName;
return this; return this;
} }
public ServiceProviderConfiguraionBuilder WithDownstreamHost(string downstreamHost) public ServiceProviderConfigurationBuilder WithDownstreamHost(string downstreamHost)
{ {
_downstreamHost = downstreamHost; _downstreamHost = downstreamHost;
return this; return this;
} }
public ServiceProviderConfiguraionBuilder WithDownstreamPort(int downstreamPort) public ServiceProviderConfigurationBuilder WithDownstreamPort(int downstreamPort)
{ {
_downstreamPort = downstreamPort; _downstreamPort = downstreamPort;
return this; return this;
} }
public ServiceProviderConfiguraionBuilder WithUseServiceDiscovery(bool userServiceDiscovery) public ServiceProviderConfigurationBuilder WithUseServiceDiscovery(bool userServiceDiscovery)
{ {
_userServiceDiscovery = userServiceDiscovery; _userServiceDiscovery = userServiceDiscovery;
return this; return this;
} }
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) public ServiceProviderConfigurationBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider)
{ {
_serviceDiscoveryProvider = serviceDiscoveryProvider; _serviceDiscoveryProvider = serviceDiscoveryProvider;
return this; return this;
} }
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost)
{ {
_serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost;
return this; return this;
} }
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort)
{ {
_serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort;
return this; return this;
} }
public ServiceProviderConfiguraion Build() public ServiceProviderConfiguration Build()
{ {
return new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery, return new ServiceProviderConfiguration(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery,
_serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); _serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort);
} }
} }

View File

@ -0,0 +1,20 @@
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator
{
public AuthenticationOptions Create(FileReRoute fileReRoute)
{
return new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
}
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using Ocelot.Configuration.Parser;
using Ocelot.Logging;
namespace Ocelot.Configuration.Creator
{
public class ClaimsToThingCreator : IClaimsToThingCreator
{
private readonly IClaimToThingConfigurationParser _claimToThingConfigParser;
private readonly IOcelotLogger _logger;
public ClaimsToThingCreator(IClaimToThingConfigurationParser claimToThingConfigurationParser,
IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ClaimsToThingCreator>();
_claimToThingConfigParser = claimToThingConfigurationParser;
}
public List<ClaimToThing> Create(Dictionary<string,string> inputToBeParsed)
{
var claimsToThings = new List<ClaimToThing>();
foreach (var input in inputToBeParsed)
{
var claimToThing = _claimToThingConfigParser.Extract(input.Key, input.Value);
if (claimToThing.IsError)
{
_logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest",
$"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect");
}
else
{
claimsToThings.Add(claimToThing.Data);
}
}
return claimsToThings;
}
}
}

View File

@ -22,36 +22,53 @@ namespace Ocelot.Configuration.Creator
{ {
private readonly IOptions<FileConfiguration> _options; private readonly IOptions<FileConfiguration> _options;
private readonly IConfigurationValidator _configurationValidator; private readonly IConfigurationValidator _configurationValidator;
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 ILogger<FileOcelotConfigurationCreator> _logger;
private readonly ILoadBalancerFactory _loadBalanceFactory; private readonly ILoadBalancerFactory _loadBalanceFactory;
private readonly ILoadBalancerHouse _loadBalancerHouse; private readonly ILoadBalancerHouse _loadBalancerHouse;
private readonly IQoSProviderFactory _qoSProviderFactory; private readonly IQoSProviderFactory _qoSProviderFactory;
private readonly IQosProviderHouse _qosProviderHouse; private readonly IQosProviderHouse _qosProviderHouse;
private readonly IClaimsToThingCreator _claimsToThingCreator;
private readonly IAuthenticationOptionsCreator _authOptionsCreator;
private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
private IRequestIdKeyCreator _requestIdKeyCreator;
private IServiceProviderConfigurationCreator _serviceProviderConfigCreator;
private IQoSOptionsCreator _qosOptionsCreator;
private IReRouteOptionsCreator _fileReRouteOptionsCreator;
private IRateLimitOptionsCreator _rateLimitOptionsCreator;
public FileOcelotConfigurationCreator( public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options, IOptions<FileConfiguration> options,
IConfigurationValidator configurationValidator, IConfigurationValidator configurationValidator,
IClaimToThingConfigurationParser claimToThingConfigurationParser,
ILogger<FileOcelotConfigurationCreator> logger, ILogger<FileOcelotConfigurationCreator> logger,
ILoadBalancerFactory loadBalancerFactory, ILoadBalancerFactory loadBalancerFactory,
ILoadBalancerHouse loadBalancerHouse, ILoadBalancerHouse loadBalancerHouse,
IQoSProviderFactory qoSProviderFactory, IQoSProviderFactory qoSProviderFactory,
IQosProviderHouse qosProviderHouse) IQosProviderHouse qosProviderHouse,
IClaimsToThingCreator claimsToThingCreator,
IAuthenticationOptionsCreator authOptionsCreator,
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator,
IRequestIdKeyCreator requestIdKeyCreator,
IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator,
IReRouteOptionsCreator fileReRouteOptionsCreator,
IRateLimitOptionsCreator rateLimitOptionsCreator
)
{ {
_rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
_authOptionsCreator = authOptionsCreator;
_loadBalanceFactory = loadBalancerFactory; _loadBalanceFactory = loadBalancerFactory;
_loadBalancerHouse = loadBalancerHouse; _loadBalancerHouse = loadBalancerHouse;
_qoSProviderFactory = qoSProviderFactory; _qoSProviderFactory = qoSProviderFactory;
_qosProviderHouse = qosProviderHouse; _qosProviderHouse = qosProviderHouse;
_options = options; _options = options;
_configurationValidator = configurationValidator; _configurationValidator = configurationValidator;
_claimToThingConfigurationParser = claimToThingConfigurationParser;
_logger = logger; _logger = logger;
_claimsToThingCreator = claimsToThingCreator;
_serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator;
_fileReRouteOptionsCreator = fileReRouteOptionsCreator;
} }
public async Task<Response<IOcelotConfiguration>> Create() public async Task<Response<IOcelotConfiguration>> Create()
@ -97,50 +114,42 @@ namespace Ocelot.Configuration.Creator
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{ {
var isAuthenticated = IsAuthenticated(fileReRoute); var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute);
var isAuthorised = IsAuthorised(fileReRoute); var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration);
var isCached = IsCached(fileReRoute); var reRouteKey = CreateReRouteKey(fileReRoute);
var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var reRouteKey = BuildReRouteKey(fileReRoute); var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileReRoute, globalConfiguration);
var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
var isQos = IsQoS(fileReRoute); var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); var qosOptions = _qosOptionsCreator.Create(fileReRoute);
var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest);
var qosOptions = BuildQoSOptions(fileReRoute);
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, enableRateLimiting);
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithIsAuthenticated(isAuthenticated) .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
.WithAuthenticationOptions(authOptionsForRoute) .WithAuthenticationOptions(authOptionsForRoute)
.WithClaimsToHeaders(claimsToHeaders) .WithClaimsToHeaders(claimsToHeaders)
.WithClaimsToClaims(claimsToClaims) .WithClaimsToClaims(claimsToClaims)
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
.WithIsAuthorised(isAuthorised) .WithIsAuthorised(fileReRouteOptions.IsAuthorised)
.WithClaimsToQueries(claimsToQueries) .WithClaimsToQueries(claimsToQueries)
.WithRequestIdKey(requestIdKey) .WithRequestIdKey(requestIdKey)
.WithIsCached(isCached) .WithIsCached(fileReRouteOptions.IsCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds))
.WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithDownstreamScheme(fileReRoute.DownstreamScheme)
.WithLoadBalancer(fileReRoute.LoadBalancer) .WithLoadBalancer(fileReRoute.LoadBalancer)
@ -148,95 +157,24 @@ namespace Ocelot.Configuration.Creator
.WithDownstreamPort(fileReRoute.DownstreamPort) .WithDownstreamPort(fileReRoute.DownstreamPort)
.WithLoadBalancerKey(reRouteKey) .WithLoadBalancerKey(reRouteKey)
.WithServiceProviderConfiguraion(serviceProviderConfiguration) .WithServiceProviderConfiguraion(serviceProviderConfiguration)
.WithIsQos(isQos) .WithIsQos(fileReRouteOptions.IsQos)
.WithQosOptions(qosOptions) .WithQosOptions(qosOptions)
.WithEnableRateLimiting(enableRateLimiting) .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption) .WithRateLimitOptions(rateLimitOption)
.Build(); .Build();
await SetupLoadBalancer(reRoute); await SetupLoadBalancer(reRoute);
SetupQosProvider(reRoute); SetupQosProvider(reRoute);
return reRoute; return reRoute;
} }
private static RateLimitOptions BuildRateLimitOptions(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) private string CreateReRouteKey(FileReRoute fileReRoute)
{
RateLimitOptions rateLimitOption = null;
if (enableRateLimiting)
{
rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader,
fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders,
globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix,
new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit)
, globalConfiguration.RateLimitOptions.HttpStatusCode);
}
return rateLimitOption;
}
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
{
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
}
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);
}
private bool IsAuthorised(FileReRoute fileReRoute)
{
return fileReRoute.RouteClaimsRequirement?.Count > 0;
}
private bool IsCached(FileReRoute fileReRoute)
{
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
}
private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
var requestIdKey = globalRequestIdConfiguration
? globalConfiguration.RequestIdKey
: fileReRoute.RequestIdKey;
return requestIdKey;
}
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 //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}"; var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}";
return loadBalancerKey; return loadBalancerKey;
} }
private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute)
{
return new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
}
private async Task SetupLoadBalancer(ReRoute reRoute) private async Task SetupLoadBalancer(ReRoute reRoute)
{ {
var loadBalancer = await _loadBalanceFactory.Get(reRoute); var loadBalancer = await _loadBalanceFactory.Get(reRoute);
@ -248,85 +186,5 @@ namespace Ocelot.Configuration.Creator
var loadBalancer = _qoSProviderFactory.Get(reRoute); var loadBalancer = _qoSProviderFactory.Get(reRoute);
_qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer);
} }
private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
return new ServiceProviderConfiguraionBuilder()
.WithServiceName(fileReRoute.ServiceName)
.WithDownstreamHost(fileReRoute.DownstreamHost)
.WithDownstreamPort(fileReRoute.DownstreamPort)
.WithUseServiceDiscovery(useServiceDiscovery)
.WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider)
.WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
.WithServiceDiscoveryProviderPort(serviceProviderPort)
.Build();
}
private string BuildUpstreamTemplatePattern(FileReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate;
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
var placeholders = new List<string>();
for (var i = 0; i < upstreamTemplate.Length; i++)
{
if (IsPlaceHolder(upstreamTemplate, i))
{
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
var variableName = upstreamTemplate.Substring(i, difference);
placeholders.Add(variableName);
}
}
foreach (var placeholder in placeholders)
{
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
}
if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}
var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
return route;
}
private List<ClaimToThing> BuildAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
{
var claimsToTHings = new List<ClaimToThing>();
foreach (var add in thingBeingAdded)
{
var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value);
if (claimToHeader.IsError)
{
_logger.LogCritical(new EventId(1, "Application Failed to start"),
$"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect");
throw new Exception(claimToHeader.Errors[0].Message);
}
claimsToTHings.Add(claimToHeader.Data);
}
return claimsToTHings;
}
private bool IsPlaceHolder(string upstreamTemplate, int i)
{
return upstreamTemplate[i] == '{';
}
} }
} }

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IAuthenticationOptionsCreator
{
AuthenticationOptions Create(FileReRoute fileReRoute);
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.Creator
{
public interface IClaimsToThingCreator
{
List<ClaimToThing> Create(Dictionary<string,string> thingsBeingAdded);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IQoSOptionsCreator
{
QoSOptions Create(FileReRoute fileReRoute);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IRateLimitOptionsCreator
{
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IReRouteOptionsCreator
{
ReRouteOptions Create(FileReRoute fileReRoute);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IRequestIdKeyCreator
{
string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IServiceProviderConfigurationCreator
{
ServiceProviderConfiguration Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration);
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IUpstreamTemplatePatternCreator
{
string Create(FileReRoute reRoute);
}
}

View File

@ -0,0 +1,17 @@
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class QoSOptionsCreator : IQoSOptionsCreator
{
public QoSOptions Create(FileReRoute fileReRoute)
{
return new QoSOptionsBuilder()
.WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking)
.WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak)
.WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue)
.Build();
}
}
}

View File

@ -0,0 +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,
TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan),
fileReRoute.RateLimitOptions.Limit))
.Build();
}
return rateLimitOption;
}
}
}

View File

@ -0,0 +1,52 @@
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 isQos = IsQoS(fileReRoute);
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
var options = new ReRouteOptionsBuilder()
.WithIsAuthenticated(isAuthenticated)
.WithIsAuthorised(isAuthorised)
.WithIsCached(isCached)
.WithIsQos(isQos)
.WithRateLimiting(enableRateLimiting)
.Build();
return options;
}
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
{
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
}
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);
}
private bool IsAuthorised(FileReRoute fileReRoute)
{
return fileReRoute.RouteClaimsRequirement?.Count > 0;
}
private bool IsCached(FileReRoute fileReRoute)
{
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
}
}
}

View File

@ -0,0 +1,18 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class RequestIdKeyCreator : IRequestIdKeyCreator
{
public string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
var requestIdKey = globalRequestIdConfiguration
? globalConfiguration.RequestIdKey
: fileReRoute.RequestIdKey;
return requestIdKey;
}
}
}

View File

@ -0,0 +1,26 @@
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class ServiceProviderConfigurationCreator : IServiceProviderConfigurationCreator
{
public ServiceProviderConfiguration Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
return new ServiceProviderConfigurationBuilder()
.WithServiceName(fileReRoute.ServiceName)
.WithDownstreamHost(fileReRoute.DownstreamHost)
.WithDownstreamPort(fileReRoute.DownstreamPort)
.WithUseServiceDiscovery(useServiceDiscovery)
.WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider)
.WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
.WithServiceDiscoveryProviderPort(serviceProviderPort)
.Build();
}
}
}

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
using Ocelot.Utilities;
namespace Ocelot.Configuration.Creator
{
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
{
private const string RegExMatchEverything = ".*";
private const string RegExMatchEndString = "$";
private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashOnly = "^/$";
public string Create(FileReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate;
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
var placeholders = new List<string>();
for (var i = 0; i < upstreamTemplate.Length; i++)
{
if (IsPlaceHolder(upstreamTemplate, i))
{
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
var variableName = upstreamTemplate.Substring(i, difference);
placeholders.Add(variableName);
}
}
foreach (var placeholder in placeholders)
{
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
}
if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}
var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
return route;
}
private bool IsPlaceHolder(string upstreamTemplate, int i)
{
return upstreamTemplate[i] == '{';
}
}
}

View File

@ -25,7 +25,7 @@ namespace Ocelot.Configuration
string downstreamHost, string downstreamHost,
int downstreamPort, int downstreamPort,
string reRouteKey, string reRouteKey,
ServiceProviderConfiguraion serviceProviderConfiguraion, ServiceProviderConfiguration serviceProviderConfiguraion,
bool isQos, bool isQos,
QoSOptions qos, QoSOptions qos,
bool enableRateLimit, bool enableRateLimit,
@ -81,7 +81,7 @@ namespace Ocelot.Configuration
public string LoadBalancer {get;private set;} public string LoadBalancer {get;private set;}
public string DownstreamHost { get; private set; } public string DownstreamHost { get; private set; }
public int DownstreamPort { get; private set; } public int DownstreamPort { get; private set; }
public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; } public ServiceProviderConfiguration ServiceProviderConfiguraion { get; private set; }
public bool EnableEndpointRateLimiting { get; private set; } public bool EnableEndpointRateLimiting { get; private set; }
public RateLimitOptions RateLimitOptions { get; private set; } public RateLimitOptions RateLimitOptions { get; private set; }
} }

View File

@ -0,0 +1,20 @@
namespace Ocelot.Configuration
{
public class ReRouteOptions
{
public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isQos, bool isEnableRateLimiting)
{
IsAuthenticated = isAuthenticated;
IsAuthorised = isAuthorised;
IsCached = isCached;
IsQos = isQos;
EnableRateLimiting = isEnableRateLimiting;
}
public bool IsAuthenticated { get; private set; }
public bool IsAuthorised { get; private set; }
public bool IsCached { get; private set; }
public bool IsQos { get; private set; }
public bool EnableRateLimiting { get; private set; }
}
}

View File

@ -1,8 +1,8 @@
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
public class ServiceProviderConfiguraion public class ServiceProviderConfiguration
{ {
public ServiceProviderConfiguraion(string serviceName, string downstreamHost, public ServiceProviderConfiguration(string serviceName, string downstreamHost,
int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort) int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort)
{ {
ServiceName = serviceName; ServiceName = serviceName;

View File

@ -60,6 +60,14 @@ namespace Ocelot.DependencyInjection
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>(); services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>(); services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
services.AddSingleton<IBaseUrlFinder, BaseUrlFinder>(); services.AddSingleton<IBaseUrlFinder, BaseUrlFinder>();
services.AddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
services.AddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
services.AddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
services.AddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
services.AddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
services.AddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
services.AddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
services.AddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();

View File

@ -14,7 +14,6 @@ namespace Ocelot.Request.Builder
string downstreamUrl, string downstreamUrl,
Stream content, Stream content,
IHeaderDictionary headers, IHeaderDictionary headers,
IRequestCookieCollection cookies,
QueryString queryString, QueryString queryString,
string contentType, string contentType,
RequestId.RequestId requestId, RequestId.RequestId requestId,
@ -29,7 +28,6 @@ namespace Ocelot.Request.Builder
.WithContentType(contentType) .WithContentType(contentType)
.WithHeaders(headers) .WithHeaders(headers)
.WithRequestId(requestId) .WithRequestId(requestId)
.WithCookies(cookies)
.WithIsQos(isQos) .WithIsQos(isQos)
.WithQos(qosProvider) .WithQos(qosProvider)
.Build(); .Build();

View File

@ -12,7 +12,6 @@ namespace Ocelot.Request.Builder
string downstreamUrl, string downstreamUrl,
Stream content, Stream content,
IHeaderDictionary headers, IHeaderDictionary headers,
IRequestCookieCollection cookies,
QueryString queryString, QueryString queryString,
string contentType, string contentType,
RequestId.RequestId requestId, RequestId.RequestId requestId,

View File

@ -21,7 +21,6 @@ namespace Ocelot.Request.Builder
private string _contentType; private string _contentType;
private IHeaderDictionary _headers; private IHeaderDictionary _headers;
private RequestId.RequestId _requestId; private RequestId.RequestId _requestId;
private IRequestCookieCollection _cookies;
private readonly string[] _unsupportedHeaders = {"host"}; private readonly string[] _unsupportedHeaders = {"host"};
private bool _isQos; private bool _isQos;
private IQoSProvider _qoSProvider; private IQoSProvider _qoSProvider;
@ -68,12 +67,6 @@ namespace Ocelot.Request.Builder
return this; return this;
} }
public RequestBuilder WithCookies(IRequestCookieCollection cookies)
{
_cookies = cookies;
return this;
}
public RequestBuilder WithIsQos(bool isqos) public RequestBuilder WithIsQos(bool isqos)
{ {
_isQos = isqos; _isQos = isqos;
@ -103,9 +96,7 @@ namespace Ocelot.Request.Builder
AddRequestIdHeader(_requestId, httpRequestMessage); AddRequestIdHeader(_requestId, httpRequestMessage);
} }
var cookieContainer = CreateCookieContainer(uri); return new Request(httpRequestMessage,_isQos, _qoSProvider);
return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider);
} }
private Uri CreateUri() private Uri CreateUri()
@ -153,21 +144,6 @@ namespace Ocelot.Request.Builder
return !_unsupportedHeaders.Contains(header.Key.ToLower()); return !_unsupportedHeaders.Contains(header.Key.ToLower());
} }
private CookieContainer CreateCookieContainer(Uri uri)
{
var cookieContainer = new CookieContainer();
if (_cookies != null)
{
foreach (var cookie in _cookies)
{
cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value));
}
}
return cookieContainer;
}
private void AddRequestIdHeader(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage) private void AddRequestIdHeader(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage)
{ {
httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);

View File

@ -48,7 +48,6 @@ namespace Ocelot.Request.Middleware
DownstreamUrl, DownstreamUrl,
context.Request.Body, context.Request.Body,
context.Request.Headers, context.Request.Headers,
context.Request.Cookies,
context.Request.QueryString, context.Request.QueryString,
context.Request.ContentType, context.Request.ContentType,
new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier),

View File

@ -1,7 +1,4 @@
using Ocelot.Configuration; using System.Net.Http;
using Ocelot.Values;
using System.Net;
using System.Net.Http;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
namespace Ocelot.Request namespace Ocelot.Request
@ -10,18 +7,15 @@ namespace Ocelot.Request
{ {
public Request( public Request(
HttpRequestMessage httpRequestMessage, HttpRequestMessage httpRequestMessage,
CookieContainer cookieContainer,
bool isQos, bool isQos,
IQoSProvider qosProvider) IQoSProvider qosProvider)
{ {
HttpRequestMessage = httpRequestMessage; HttpRequestMessage = httpRequestMessage;
CookieContainer = cookieContainer;
IsQos = isQos; IsQos = isQos;
QosProvider = qosProvider; QosProvider = qosProvider;
} }
public HttpRequestMessage HttpRequestMessage { get; private set; } public HttpRequestMessage HttpRequestMessage { get; private set; }
public CookieContainer CookieContainer { get; private set; }
public bool IsQos { get; private set; } public bool IsQos { get; private set; }
public IQoSProvider QosProvider { get; private set; } public IQoSProvider QosProvider { get; private set; }
} }

View File

@ -14,14 +14,14 @@ namespace Ocelot.Requester
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>(); private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
private Dictionary<string, string> _defaultHeaders; private Dictionary<string, string> _defaultHeaders;
public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger)
{ {
_handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger));
return this; return this;
} }
public IHttpClient Create() public IHttpClient Create()
{ {
var httpclientHandler = new HttpClientHandler(); var httpclientHandler = new HttpClientHandler();

View File

@ -55,7 +55,7 @@ namespace Ocelot.Requester
} }
private string GetCacheKey(Request.Request request, HttpClientBuilder builder) private string GetCacheKey(Request.Request request, IHttpClientBuilder builder)
{ {
string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}";

View File

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

View File

@ -6,7 +6,7 @@ namespace Ocelot.ServiceDiscovery
{ {
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
{ {
public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig)
{ {
if (serviceConfig.UseServiceDiscovery) if (serviceConfig.UseServiceDiscovery)
{ {

View File

@ -13,6 +13,7 @@ using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Ocelot.IntegrationTests namespace Ocelot.IntegrationTests
{ {
public class AdministrationTests : IDisposable public class AdministrationTests : IDisposable

View File

@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.ManualTest;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;
namespace Ocelot.IntegrationTests
{
public class ThreadSafeHeadersTests : IDisposable
{
private readonly HttpClient _httpClient;
private HttpResponseMessage _response;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private BearerToken _token;
private IWebHost _downstreamBuilder;
private readonly Random _random;
private readonly ConcurrentBag<ThreadSafeHeadersTestResult> _results;
public ThreadSafeHeadersTests()
{
_results = new ConcurrentBag<ThreadSafeHeadersTestResult>();
_random = new Random();
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5001";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
}
[Fact]
public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get",
}
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenThereIsAServiceRunningOn("http://localhost:51879"))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300))
.Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService())
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string url)
{
_downstreamBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
var header = context.Request.Headers["ThreadSafeHeadersTest"];
context.Response.StatusCode = 200;
await context.Response.WriteAsync(header[0]);
});
})
.Build();
_downstreamBuilder.Start();
}
private void GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x =>
{
x.AddSingleton(_webHostBuilder);
})
.UseStartup<Startup>();
_builder = _webHostBuilder.Build();
_builder.Start();
}
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{
var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
var text = File.ReadAllText(configurationPath);
configurationPath = $"{AppContext.BaseDirectory}/configuration.json";
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
text = File.ReadAllText(configurationPath);
}
public void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times)
{
var tasks = new Task[times];
for (int i = 0; i < times; i++)
{
var urlCopy = url;
var random = _random.Next(0, 50);
tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random);
}
Task.WaitAll(tasks);
}
private async Task GetForThreadSafeHeadersTest(string url, int random)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("ThreadSafeHeadersTest", new List<string> { random.ToString() });
var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
int result = int.Parse(content);
var tshtr = new ThreadSafeHeadersTestResult(result, random);
_results.Add(tshtr);
}
private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()
{
foreach(var result in _results)
{
result.Result.ShouldBe(result.Random);
}
}
public void Dispose()
{
_builder?.Dispose();
_httpClient?.Dispose();
_downstreamBuilder?.Dispose();
}
class ThreadSafeHeadersTestResult
{
public ThreadSafeHeadersTestResult(int result, int random)
{
Result = result;
Random = random;
}
public int Result { get; private set; }
public int Random { get; private set; }
}
}
}

View File

@ -1 +1 @@
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration","RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} {"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}}

View File

@ -1,303 +1,311 @@
{ {
"ReRoutes": [ "ReRoutes": [
{ {
"DownstreamPathTemplate": "/", "DownstreamPathTemplate": "/",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "localhost", "DownstreamHost": "localhost",
"DownstreamPort": 52876, "DownstreamPort": 52876,
"UpstreamPathTemplate": "/identityserverexample", "UpstreamPathTemplate": "/identityserverexample",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"AuthenticationOptions": { "AuthenticationOptions": {
"Provider": "IdentityServer", "Provider": "IdentityServer",
"ProviderRootUrl": "http://localhost:52888", "ProviderRootUrl": "http://localhost:52888",
"ScopeName": "api", "ScopeName": "api",
"AdditionalScopes": [ "AdditionalScopes": [
"openid", "openid",
"offline_access" "offline_access"
], ],
"ScopeSecret": "secret" "ScopeSecret": "secret"
}, },
"AddHeadersToRequest": { "AddHeadersToRequest": {
"CustomerId": "Claims[CustomerId] > value", "CustomerId": "Claims[CustomerId] > value",
"LocationId": "Claims[LocationId] > value", "LocationId": "Claims[LocationId] > value",
"UserType": "Claims[sub] > value[0] > |", "UserType": "Claims[sub] > value[0] > |",
"UserId": "Claims[sub] > value[1] > |" "UserId": "Claims[sub] > value[1] > |"
}, },
"AddClaimsToRequest": { "AddClaimsToRequest": {
"CustomerId": "Claims[CustomerId] > value", "CustomerId": "Claims[CustomerId] > value",
"LocationId": "Claims[LocationId] > value", "LocationId": "Claims[LocationId] > value",
"UserType": "Claims[sub] > value[0] > |", "UserType": "Claims[sub] > value[0] > |",
"UserId": "Claims[sub] > value[1] > |" "UserId": "Claims[sub] > value[1] > |"
}, },
"AddQueriesToRequest": { "AddQueriesToRequest": {
"CustomerId": "Claims[CustomerId] > value", "CustomerId": "Claims[CustomerId] > value",
"LocationId": "Claims[LocationId] > value", "LocationId": "Claims[LocationId] > value",
"UserType": "Claims[sub] > value[0] > |", "UserType": "Claims[sub] > value[0] > |",
"UserId": "Claims[sub] > value[1] > |" "UserId": "Claims[sub] > value[1] > |"
}, },
"RouteClaimsRequirement": { "RouteClaimsRequirement": {
"UserType": "registered" "UserType": "registered"
}, },
"RequestIdKey": "OcRequestId" "RequestIdKey": "OcRequestId"
}, },
{ {
"DownstreamPathTemplate": "/posts", "DownstreamPathTemplate": "/posts",
"DownstreamScheme": "https", "DownstreamScheme": "https",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 443, "DownstreamPort": 443,
"UpstreamPathTemplate": "/posts", "UpstreamPathTemplate": "/posts",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/posts/{postId}", "DownstreamPathTemplate": "/posts/{postId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/posts/{postId}/comments", "DownstreamPathTemplate": "/posts/{postId}/comments",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts/{postId}/comments", "UpstreamPathTemplate": "/posts/{postId}/comments",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/comments", "DownstreamPathTemplate": "/comments",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/comments", "UpstreamPathTemplate": "/comments",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/posts", "DownstreamPathTemplate": "/posts",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts", "UpstreamPathTemplate": "/posts",
"UpstreamHttpMethod": "Post", "UpstreamHttpMethod": "Post",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/posts/{postId}", "DownstreamPathTemplate": "/posts/{postId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Put", "UpstreamHttpMethod": "Put",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/posts/{postId}", "DownstreamPathTemplate": "/posts/{postId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Patch", "UpstreamHttpMethod": "Patch",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/posts/{postId}", "DownstreamPathTemplate": "/posts/{postId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": "Delete", "UpstreamHttpMethod": "Delete",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/api/products", "DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/products", "UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/products/{productId}", "DownstreamPathTemplate": "/api/products/{productId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/products/{productId}", "UpstreamPathTemplate": "/products/{productId}",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/products", "DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamHost": "products20161126090340.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/products", "UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": "Post", "UpstreamHttpMethod": "Post",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} }
}, },
{ {
"DownstreamPathTemplate": "/api/products/{productId}", "DownstreamPathTemplate": "/api/products/{productId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamHost": "products20161126090340.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/products/{productId}", "UpstreamPathTemplate": "/products/{productId}",
"UpstreamHttpMethod": "Put", "UpstreamHttpMethod": "Put",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/products/{productId}", "DownstreamPathTemplate": "/api/products/{productId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamHost": "products20161126090340.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/products/{productId}", "UpstreamPathTemplate": "/products/{productId}",
"UpstreamHttpMethod": "Delete", "UpstreamHttpMethod": "Delete",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/customers", "DownstreamPathTemplate": "/api/customers",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamHost": "customers20161126090811.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/customers", "UpstreamPathTemplate": "/customers",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/customers/{customerId}", "DownstreamPathTemplate": "/api/customers/{customerId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamHost": "customers20161126090811.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/customers/{customerId}", "UpstreamPathTemplate": "/customers/{customerId}",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/customers", "DownstreamPathTemplate": "/api/customers",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamHost": "customers20161126090811.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/customers", "UpstreamPathTemplate": "/customers",
"UpstreamHttpMethod": "Post", "UpstreamHttpMethod": "Post",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/customers/{customerId}", "DownstreamPathTemplate": "/api/customers/{customerId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamHost": "customers20161126090811.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/customers/{customerId}", "UpstreamPathTemplate": "/customers/{customerId}",
"UpstreamHttpMethod": "Put", "UpstreamHttpMethod": "Put",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/api/customers/{customerId}", "DownstreamPathTemplate": "/api/customers/{customerId}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamHost": "customers20161126090811.azurewebsites.net",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/customers/{customerId}", "UpstreamPathTemplate": "/customers/{customerId}",
"UpstreamHttpMethod": "Delete", "UpstreamHttpMethod": "Delete",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
}, },
{ {
"DownstreamPathTemplate": "/posts", "DownstreamPathTemplate": "/posts",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamHost": "jsonplaceholder.typicode.com",
"DownstreamPort": 80, "DownstreamPort": 80,
"UpstreamPathTemplate": "/posts/", "UpstreamPathTemplate": "/posts/",
"UpstreamHttpMethod": "Get", "UpstreamHttpMethod": "Get",
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
}, },
"FileCacheOptions": { "TtlSeconds": 15 } "FileCacheOptions": { "TtlSeconds": 15 }
} },
], {
"DownstreamPathTemplate": "/",
"DownstreamScheme": "http",
"DownstreamHost": "www.bbc.co.uk",
"DownstreamPort": 80,
"UpstreamPathTemplate": "/bbc/",
"UpstreamHttpMethod": "Get"
}
],
"GlobalConfiguration": { "GlobalConfiguration": {
"RequestIdKey": "OcRequestId", "RequestIdKey": "OcRequestId",

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class AuthenticationOptionsCreatorTests
{
private AuthenticationOptionsCreator _authOptionsCreator;
private FileReRoute _fileReRoute;
private AuthenticationOptions _result;
public AuthenticationOptionsCreatorTests()
{
_authOptionsCreator = new AuthenticationOptionsCreator();
}
[Fact]
public void should_return_auth_options()
{
var fileReRoute = new FileReRoute()
{
AuthenticationOptions = new FileAuthenticationOptions
{
Provider = "Geoff",
ProviderRootUrl = "http://www.bbc.co.uk/",
ScopeName = "Laura",
RequireHttps = true,
AdditionalScopes = new List<string> {"cheese"},
ScopeSecret = "secret"
}
};
var expected = new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
this.Given(x => x.GivenTheFollowing(fileReRoute))
.When(x => x.WhenICreateTheAuthenticationOptions())
.Then(x => x.ThenTheFollowingIsReturned(expected))
.BDDfy();
}
private void GivenTheFollowing(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void WhenICreateTheAuthenticationOptions()
{
_result = _authOptionsCreator.Create(_fileReRoute);
}
private void ThenTheFollowingIsReturned(AuthenticationOptions expected)
{
_result.AdditionalScopes.ShouldBe(expected.AdditionalScopes);
_result.Provider.ShouldBe(expected.Provider);
_result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl);
_result.RequireHttps.ShouldBe(expected.RequireHttps);
_result.ScopeName.ShouldBe(expected.ScopeName);
_result.ScopeSecret.ShouldBe(expected.ScopeSecret);
}
}
}

View File

@ -0,0 +1,110 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.Parser;
using Ocelot.Errors;
using Ocelot.Logging;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class ClaimsToThingCreatorTests
{
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
private Dictionary<string,string> _claimsToThings;
private ClaimsToThingCreator _claimsToThingsCreator;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private List<ClaimToThing> _result;
private Mock<IOcelotLogger> _logger;
public ClaimsToThingCreatorTests()
{
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_loggerFactory
.Setup(x => x.CreateLogger<ClaimsToThingCreator>())
.Returns(_logger.Object);
_configParser = new Mock<IClaimToThingConfigurationParser>();
_claimsToThingsCreator = new ClaimsToThingCreator(_configParser.Object, _loggerFactory.Object);
}
[Fact]
public void should_return_claims_to_things()
{
var userInput = new Dictionary<string,string>()
{
{"CustomerId", "Claims[CustomerId] > value"}
};
var claimsToThing = new OkResponse<ClaimToThing>(new ClaimToThing("CustomerId", "CustomerId", "", 0));
this.Given(x => x.GivenTheFollowingDictionary(userInput))
.And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing))
.When(x => x.WhenIGetTheThings())
.Then(x => x.ThenTheConfigParserIsCalledCorrectly())
.And(x => x.ThenClaimsToThingsAreReturned())
.BDDfy();
}
[Fact]
public void should_log_error_if_cannot_parse_claim_to_thing()
{
var userInput = new Dictionary<string,string>()
{
{"CustomerId", "Claims[CustomerId] > value"}
};
var claimsToThing = new ErrorResponse<ClaimToThing>(It.IsAny<Error>());
this.Given(x => x.GivenTheFollowingDictionary(userInput))
.And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing))
.When(x => x.WhenIGetTheThings())
.Then(x => x.ThenTheConfigParserIsCalledCorrectly())
.And(x => x.ThenNoClaimsToThingsAreReturned())
.BDDfy();
}
private void ThenTheLoggerIsCalledCorrectly()
{
_logger
.Verify(x => x.LogDebug(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
private void ThenClaimsToThingsAreReturned()
{
_result.Count.ShouldBeGreaterThan(0);
}
private void GivenTheFollowingDictionary(Dictionary<string,string> claimsToThings)
{
_claimsToThings = claimsToThings;
}
private void GivenTheConfigHeaderExtractorReturns(Response<ClaimToThing> expected)
{
_configParser
.Setup(x => x.Extract(It.IsAny<string>(), It.IsAny<string>()))
.Returns(expected);
}
private void ThenNoClaimsToThingsAreReturned()
{
_result.Count.ShouldBe(0);
}
private void WhenIGetTheThings()
{
_result = _claimsToThingsCreator.Create(_claimsToThings);
}
private void ThenTheConfigParserIsCalledCorrectly()
{
_configParser
.Verify(x => x.Extract(_claimsToThings.First().Key, _claimsToThings.First().Value), Times.Once);
}
}
}

View File

@ -6,7 +6,6 @@ using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
@ -23,7 +22,6 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IConfigurationValidator> _validator; private readonly Mock<IConfigurationValidator> _validator;
private Response<IOcelotConfiguration> _config; private Response<IOcelotConfiguration> _config;
private FileConfiguration _fileConfiguration; private FileConfiguration _fileConfiguration;
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger; private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory; private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
@ -32,6 +30,14 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IQoSProviderFactory> _qosProviderFactory; private readonly Mock<IQoSProviderFactory> _qosProviderFactory;
private readonly Mock<IQosProviderHouse> _qosProviderHouse; private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly Mock<IQoSProvider> _qosProvider; private readonly Mock<IQoSProvider> _qosProvider;
private Mock<IClaimsToThingCreator> _claimsToThingCreator;
private Mock<IAuthenticationOptionsCreator> _authOptionsCreator;
private Mock<IUpstreamTemplatePatternCreator> _upstreamTemplatePatternCreator;
private Mock<IRequestIdKeyCreator> _requestIdKeyCreator;
private Mock<IServiceProviderConfigurationCreator> _serviceProviderConfigCreator;
private Mock<IQoSOptionsCreator> _qosOptionsCreator;
private Mock<IReRouteOptionsCreator> _fileReRouteOptionsCreator;
private Mock<IRateLimitOptionsCreator> _rateLimitOptions;
public FileConfigurationCreatorTests() public FileConfigurationCreatorTests()
{ {
@ -39,46 +45,69 @@ namespace Ocelot.UnitTests.Configuration
_qosProviderHouse = new Mock<IQosProviderHouse>(); _qosProviderHouse = new Mock<IQosProviderHouse>();
_qosProvider = new Mock<IQoSProvider>(); _qosProvider = new Mock<IQoSProvider>();
_logger = new Mock<ILogger<FileOcelotConfigurationCreator>>(); _logger = new Mock<ILogger<FileOcelotConfigurationCreator>>();
_configParser = new Mock<IClaimToThingConfigurationParser>();
_validator = new Mock<IConfigurationValidator>(); _validator = new Mock<IConfigurationValidator>();
_fileConfig = new Mock<IOptions<FileConfiguration>>(); _fileConfig = new Mock<IOptions<FileConfiguration>>();
_loadBalancerFactory = new Mock<ILoadBalancerFactory>(); _loadBalancerFactory = new Mock<ILoadBalancerFactory>();
_loadBalancerHouse = new Mock<ILoadBalancerHouse>(); _loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_loadBalancer = new Mock<ILoadBalancer>(); _loadBalancer = new Mock<ILoadBalancer>();
_claimsToThingCreator = new Mock<IClaimsToThingCreator>();
_authOptionsCreator = new Mock<IAuthenticationOptionsCreator>();
_upstreamTemplatePatternCreator = new Mock<IUpstreamTemplatePatternCreator>();
_requestIdKeyCreator = new Mock<IRequestIdKeyCreator>();
_serviceProviderConfigCreator = new Mock<IServiceProviderConfigurationCreator>();
_qosOptionsCreator = new Mock<IQoSOptionsCreator>();
_fileReRouteOptionsCreator = new Mock<IReRouteOptionsCreator>();
_rateLimitOptions = new Mock<IRateLimitOptionsCreator>();
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, _fileConfig.Object, _validator.Object, _logger.Object,
_loadBalancerFactory.Object, _loadBalancerHouse.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object,
_qosProviderFactory.Object, _qosProviderHouse.Object); _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object,
_authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object,
_serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object,
_rateLimitOptions.Object);
} }
[Fact] [Fact]
public void should_create_load_balancer() public void should_call_rate_limit_options_creator()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{ {
ReRoutes = new List<FileReRoute> new FileReRoute
{ {
new FileReRoute DownstreamHost = "127.0.0.1",
{ UpstreamPathTemplate = "/api/products/{productId}",
DownstreamHost = "127.0.0.1", DownstreamPathTemplate = "/products/{productId}",
UpstreamPathTemplate = "/api/products/{productId}", UpstreamHttpMethod = "Get",
DownstreamPathTemplate = "/products/{productId}", }
UpstreamHttpMethod = "Get", },
} }))
}, .And(x => x.GivenTheConfigIsValid())
})) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheConfigIsValid()) .When(x => x.WhenICreateTheConfig())
.And(x => x.GivenTheLoadBalancerFactoryReturns()) .Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly())
.When(x => x.WhenICreateTheConfig()) .BDDfy();
.Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly())
.And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly())
.BDDfy();
} }
[Fact] [Fact]
public void should_create_qos_provider() public void should_call_qos_options_creator()
{ {
this.Given(x => x.GivenTheConfigIs(new FileConfiguration var expected = new QoSOptionsBuilder()
.WithDurationOfBreak(1)
.WithExceptionsAllowedBeforeBreaking(1)
.WithTimeoutValue(1)
.Build();
var serviceOptions = new ReRouteOptionsBuilder()
.WithIsQos(true)
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
@ -98,17 +127,23 @@ namespace Ocelot.UnitTests.Configuration
}, },
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions))
.And(x => x.GivenTheQosProviderFactoryReturns()) .And(x => x.GivenTheQosProviderFactoryReturns())
.And(x => x.GivenTheQosOptionsCreatorReturns(expected))
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.TheQosProviderFactoryIsCalledCorrectly()) .Then(x => x.ThenTheQosOptionsAre(expected))
.And(x => x.TheQosProviderFactoryIsCalledCorrectly())
.And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_use_downstream_host() public void should_create_load_balancer()
{ {
this.Given(x => x.GivenTheConfigIs(new FileConfiguration var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
@ -122,23 +157,54 @@ namespace Ocelot.UnitTests.Configuration
}, },
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly())
{ .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly())
new ReRouteBuilder()
.WithDownstreamHost("127.0.0.1")
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_use_downstream_host()
{
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "127.0.0.1",
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamHost("127.0.0.1")
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.Build()
}))
.BDDfy();
}
[Fact] [Fact]
public void should_use_downstream_scheme() public void should_use_downstream_scheme()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -153,6 +219,7 @@ namespace Ocelot.UnitTests.Configuration
}, },
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
@ -161,7 +228,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build() .Build()
})) }))
.BDDfy(); .BDDfy();
@ -170,6 +236,9 @@ namespace Ocelot.UnitTests.Configuration
[Fact] [Fact]
public void should_use_service_discovery_for_downstream_service_host() public void should_use_service_discovery_for_downstream_service_host()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -193,6 +262,7 @@ namespace Ocelot.UnitTests.Configuration
} }
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
@ -200,8 +270,7 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder()
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithUseServiceDiscovery(true) .WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("consul") .WithServiceDiscoveryProvider("consul")
.WithServiceDiscoveryProviderHost("127.0.0.1") .WithServiceDiscoveryProviderHost("127.0.0.1")
@ -215,6 +284,9 @@ namespace Ocelot.UnitTests.Configuration
[Fact] [Fact]
public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -229,6 +301,7 @@ namespace Ocelot.UnitTests.Configuration
} }
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
@ -236,8 +309,7 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder()
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithUseServiceDiscovery(false) .WithUseServiceDiscovery(false)
.Build()) .Build())
.Build() .Build()
@ -246,8 +318,11 @@ namespace Ocelot.UnitTests.Configuration
} }
[Fact] [Fact]
public void should_use_reroute_case_sensitivity_value() public void should_call_template_pattern_creator_correctly()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -262,6 +337,8 @@ namespace Ocelot.UnitTests.Configuration
} }
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$"))
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
@ -276,67 +353,11 @@ namespace Ocelot.UnitTests.Configuration
} }
[Fact] [Fact]
public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() public void should_call_request_id_creator()
{ {
this.Given(x => x.GivenTheConfigIs(new FileConfiguration var reRouteOptions = new ReRouteOptionsBuilder()
{ .Build();
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get"
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_set_upstream_template_pattern_to_respect_case_sensitivity()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_set_global_request_id_key()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -355,6 +376,8 @@ namespace Ocelot.UnitTests.Configuration
} }
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheRequestIdCreatorReturns("blahhhh"))
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
@ -362,46 +385,20 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.WithRequestIdKey("blahhhh") .WithRequestIdKey("blahhhh")
.Build() .Build()
})) }))
.BDDfy(); .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly())
}
[Fact]
public void should_create_template_pattern_that_matches_anything_to_end_of_string()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.Build()
}))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_create_with_headers_to_extract() public void should_create_with_headers_to_extract()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.WithIsAuthenticated(true)
.Build();
var authenticationOptions = new AuthenticationOptionsBuilder() var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider("IdentityServer") .WithProvider("IdentityServer")
.WithProviderRootUrl("http://localhost:51888") .WithProviderRootUrl("http://localhost:51888")
@ -417,7 +414,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.WithAuthenticationOptions(authenticationOptions) .WithAuthenticationOptions(authenticationOptions)
.WithClaimsToHeaders(new List<ClaimToThing> .WithClaimsToHeaders(new List<ClaimToThing>
{ {
@ -453,24 +449,24 @@ namespace Ocelot.UnitTests.Configuration
} }
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0))) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheClaimsToThingCreatorReturns(new List<ClaimToThing>{new ClaimToThing("CustomerId", "CustomerId", "", 0)}))
.And(x => x.GivenTheLoadBalancerFactoryReturns()) .And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected)) .Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected))
.And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
private void GivenTheConfigHeaderExtractorReturns(ClaimToThing expected)
{
_configParser
.Setup(x => x.Extract(It.IsAny<string>(), It.IsAny<string>()))
.Returns(new OkResponse<ClaimToThing>(expected));
}
[Fact] [Fact]
public void should_create_with_authentication_properties() public void should_create_with_authentication_properties()
{ {
var reRouteOptions = new ReRouteOptionsBuilder()
.WithIsAuthenticated(true)
.Build();
var authenticationOptions = new AuthenticationOptionsBuilder() var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider("IdentityServer") .WithProvider("IdentityServer")
.WithProviderRootUrl("http://localhost:51888") .WithProviderRootUrl("http://localhost:51888")
@ -486,7 +482,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}") .WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.WithAuthenticationOptions(authenticationOptions) .WithAuthenticationOptions(authenticationOptions)
.Build() .Build()
}; };
@ -514,101 +509,27 @@ namespace Ocelot.UnitTests.Configuration
} }
})) }))
.And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
.And(x => x.GivenTheLoadBalancerFactoryReturns()) .And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected)) .Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected))
.And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
[Fact] private void GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptions)
public void should_create_template_pattern_that_matches_more_than_one_placeholder()
{ {
this.Given(x => x.GivenTheConfigIs(new FileConfiguration _fileReRouteOptionsCreator
{ .Setup(x => x.Create(It.IsAny<FileReRoute>()))
ReRoutes = new List<FileReRoute> .Returns(fileReRouteOptions);
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$")
.Build()
}))
.BDDfy();
} }
[Fact] private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly()
public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash()
{ {
this.Given(x => x.GivenTheConfigIs(new FileConfiguration _rateLimitOptions
{ .Verify(x => x.Create(It.IsAny<FileReRoute>(), It.IsAny<FileGlobalConfiguration>(), It.IsAny<bool>()), Times.Once);
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}/")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_to_end_of_string()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/api/products/",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/api/products/")
.WithUpstreamPathTemplate("/")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("^/$")
.Build()
}))
.BDDfy();
} }
private void GivenTheConfigIsValid() private void GivenTheConfigIsValid()
@ -642,6 +563,25 @@ namespace Ocelot.UnitTests.Configuration
result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod);
result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value);
result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern);
result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count);
result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count);
result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count);
result.RequestIdKey.ShouldBe(expected.RequestIdKey);
}
}
private void ThenTheServiceConfigurationIs(ServiceProviderConfiguration expected)
{
for (int i = 0; i < _config.Data.ReRoutes.Count; i++)
{
var result = _config.Data.ReRoutes[i];
result.ServiceProviderConfiguraion.DownstreamHost.ShouldBe(expected.DownstreamHost);
result.ServiceProviderConfiguraion.DownstreamPort.ShouldBe(expected.DownstreamPort);
result.ServiceProviderConfiguraion.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider);
result.ServiceProviderConfiguraion.ServiceName.ShouldBe(expected.ServiceName);
result.ServiceProviderConfiguraion.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost);
result.ServiceProviderConfiguraion.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort);
} }
} }
@ -699,5 +639,60 @@ namespace Ocelot.UnitTests.Configuration
_qosProviderHouse _qosProviderHouse
.Verify(x => x.Add(It.IsAny<string>(), _qosProvider.Object), Times.Once); .Verify(x => x.Add(It.IsAny<string>(), _qosProvider.Object), Times.Once);
} }
private void GivenTheClaimsToThingCreatorReturns(List<ClaimToThing> claimsToThing)
{
_claimsToThingCreator
.Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest))
.Returns(claimsToThing);
}
private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions)
{
_authOptionsCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>()))
.Returns(authOptions);
}
private void ThenTheAuthOptionsCreatorIsCalledCorrectly()
{
_authOptionsCreator
.Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once);
}
private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern)
{
_upstreamTemplatePatternCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>()))
.Returns(pattern);
}
private void ThenTheRequestIdKeyCreatorIsCalledCorrectly()
{
_requestIdKeyCreator
.Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.GlobalConfiguration), Times.Once);
}
private void GivenTheRequestIdCreatorReturns(string requestId)
{
_requestIdKeyCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>(), It.IsAny<FileGlobalConfiguration>()))
.Returns(requestId);
}
private void GivenTheQosOptionsCreatorReturns(QoSOptions qosOptions)
{
_qosOptionsCreator
.Setup(x => x.Create(_fileConfiguration.ReRoutes[0]))
.Returns(qosOptions);
}
private void ThenTheQosOptionsAre(QoSOptions qosOptions)
{
_config.Data.ReRoutes[0].QosOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak);
_config.Data.ReRoutes[0].QosOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking);
_config.Data.ReRoutes[0].QosOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue);
}
} }
} }

View File

@ -0,0 +1,63 @@
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class QoSOptionsCreatorTests
{
private QoSOptionsCreator _creator;
private FileReRoute _fileReRoute;
private QoSOptions _result;
public QoSOptionsCreatorTests()
{
_creator = new QoSOptionsCreator();
}
[Fact]
public void should_create_qos_options()
{
var reRoute = new FileReRoute
{
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 1,
DurationOfBreak = 1,
TimeoutValue = 1
}
};
var expected = new QoSOptionsBuilder()
.WithDurationOfBreak(1)
.WithExceptionsAllowedBeforeBreaking(1)
.WithTimeoutValue(1)
.Build();
this.Given(x => x.GivenTheFollowingReRoute(reRoute))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheFollowingIsReturned(expected))
.BDDfy();
}
private void GivenTheFollowingReRoute(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void WhenICreate()
{
_result = _creator.Create(_fileReRoute);
}
private void ThenTheFollowingIsReturned(QoSOptions expected)
{
_result.DurationOfBreak.ShouldBe(expected.DurationOfBreak);
_result.ExceptionsAllowedBeforeBreaking.ShouldBe(expected.ExceptionsAllowedBeforeBreaking);
_result.TimeoutValue.ShouldBe(expected.TimeoutValue);
}
}
}

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class RateLimitOptionsCreatorTests
{
private FileReRoute _fileReRoute;
private FileGlobalConfiguration _fileGlobalConfig;
private bool _enabled;
private RateLimitOptionsCreator _creator;
private RateLimitOptions _result;
public RateLimitOptionsCreatorTests()
{
_creator = new RateLimitOptionsCreator();
}
[Fact]
public void should_create_rate_limit_options()
{
var fileReRoute = new FileReRoute
{
RateLimitOptions = new FileRateLimitRule
{
ClientWhitelist = new List<string>(),
Period = "Period",
Limit = 1,
PeriodTimespan = 1,
EnableRateLimiting = true
}
};
var fileGlobalConfig = new FileGlobalConfiguration
{
RateLimitOptions = new FileRateLimitOptions
{
ClientIdHeader = "ClientIdHeader",
DisableRateLimitHeaders = true,
QuotaExceededMessage = "QuotaExceededMessage",
RateLimitCounterPrefix = "RateLimitCounterPrefix",
HttpStatusCode = 200
}
};
var expected = new RateLimitOptionsBuilder()
.WithClientIdHeader("ClientIdHeader")
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist)
.WithDisableRateLimitHeaders(true)
.WithEnableRateLimiting(true)
.WithHttpStatusCode(200)
.WithQuotaExceededMessage("QuotaExceededMessage")
.WithRateLimitCounterPrefix("RateLimitCounterPrefix")
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan),
fileReRoute.RateLimitOptions.Limit))
.Build();
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig))
.And(x => x.GivenRateLimitingIsEnabled())
.When(x => x.WhenICreate())
.Then(x => x.ThenTheFollowingIsReturned(expected))
.BDDfy();
}
private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig)
{
_fileGlobalConfig = fileGlobalConfig;
}
private void GivenRateLimitingIsEnabled()
{
_enabled = true;
}
private void WhenICreate()
{
_result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled);
}
private void ThenTheFollowingIsReturned(RateLimitOptions expected)
{
_result.ClientIdHeader.ShouldBe(expected.ClientIdHeader);
_result.ClientWhitelist.ShouldBe(expected.ClientWhitelist);
_result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders);
_result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting);
_result.HttpStatusCode.ShouldBe(expected.HttpStatusCode);
_result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage);
_result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix);
_result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit);
_result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period);
_result.RateLimitRule.PeriodTimespan.Ticks.ShouldBe(expected.RateLimitRule.PeriodTimespan.Ticks);
}
}
}

View File

@ -0,0 +1,84 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class ReRouteOptionsCreatorTests
{
private ReRouteOptionsCreator _creator;
private FileReRoute _reRoute;
private ReRouteOptions _result;
public ReRouteOptionsCreatorTests()
{
_creator = new ReRouteOptionsCreator();
}
[Fact]
public void should_create_re_route_options()
{
var reRoute = new FileReRoute
{
RateLimitOptions = new FileRateLimitRule
{
EnableRateLimiting = true
},
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 1
},
AuthenticationOptions = new FileAuthenticationOptions
{
Provider = "IdentityServer"
},
RouteClaimsRequirement = new Dictionary<string, string>()
{
{"",""}
},
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 1
}
};
var expected = new ReRouteOptionsBuilder()
.WithIsAuthenticated(true)
.WithIsAuthorised(true)
.WithIsCached(true)
.WithIsQos(true)
.WithRateLimiting(true)
.Build();
this.Given(x => x.GivenTheFollowing(reRoute))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheFollowingIsReturned(expected))
.BDDfy();
}
private void GivenTheFollowing(FileReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenICreate()
{
_result = _creator.Create(_reRoute);
}
private void ThenTheFollowingIsReturned(ReRouteOptions expected)
{
_result.IsAuthenticated.ShouldBe(expected.IsAuthenticated);
_result.IsAuthorised.ShouldBe(expected.IsAuthorised);
_result.IsQos.ShouldBe(expected.IsQos);
_result.IsCached.ShouldBe(expected.IsCached);
_result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting);
}
}
}

View File

@ -0,0 +1,91 @@
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class RequestIdKeyCreatorTests
{
private FileReRoute _fileReRoute;
private FileGlobalConfiguration _fileGlobalConfig;
private string _result;
private RequestIdKeyCreator _creator;
public RequestIdKeyCreatorTests()
{
_creator = new RequestIdKeyCreator();
}
[Fact]
public void should_use_global_configuration()
{
var reRoute = new FileReRoute();
var globalConfig = new FileGlobalConfiguration
{
RequestIdKey = "cheese"
};
this.Given(x => x.GivenTheFollowingReRoute(reRoute))
.And(x => x.GivenTheFollowingGlobalConfig(globalConfig))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheFollowingIsReturned("cheese"))
.BDDfy();
}
[Fact]
public void should_use_re_route_specific()
{
var reRoute = new FileReRoute
{
RequestIdKey = "cheese"
};
var globalConfig = new FileGlobalConfiguration();
this.Given(x => x.GivenTheFollowingReRoute(reRoute))
.And(x => x.GivenTheFollowingGlobalConfig(globalConfig))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheFollowingIsReturned("cheese"))
.BDDfy();
}
[Fact]
public void should_use_global_cofiguration_over_re_route_specific()
{
var reRoute = new FileReRoute
{
RequestIdKey = "cheese"
}; var globalConfig = new FileGlobalConfiguration
{
RequestIdKey = "cheese"
};
this.Given(x => x.GivenTheFollowingReRoute(reRoute))
.And(x => x.GivenTheFollowingGlobalConfig(globalConfig))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheFollowingIsReturned("cheese"))
.BDDfy();
}
private void GivenTheFollowingReRoute(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration globalConfig)
{
_fileGlobalConfig = globalConfig;
}
private void WhenICreate()
{
_result = _creator.Create(_fileReRoute, _fileGlobalConfig);
}
private void ThenTheFollowingIsReturned(string expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -0,0 +1,76 @@
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class ServiceProviderCreatorTests
{
private ServiceProviderConfigurationCreator _creator;
private FileReRoute _reRoute;
private FileGlobalConfiguration _globalConfig;
private ServiceProviderConfiguration _result;
public ServiceProviderCreatorTests()
{
_creator = new ServiceProviderConfigurationCreator();
}
[Fact]
public void should_create_service_provider_config()
{
var reRoute = new FileReRoute();
var globalConfig = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Provider = "consul",
Host = "127.0.0.1",
Port = 1234
}
};
var expected = new ServiceProviderConfigurationBuilder()
.WithServiceDiscoveryProvider("consul")
.WithServiceDiscoveryProviderHost("127.0.0.1")
.WithServiceDiscoveryProviderPort(1234)
.Build();
this.Given(x => x.GivenTheFollowingReRoute(reRoute))
.And(x => x.GivenTheFollowingGlobalConfig(globalConfig))
.When(x => x.WhenICreate())
.Then(x => x.ThenTheConfigIs(expected))
.BDDfy();
}
private void GivenTheFollowingReRoute(FileReRoute fileReRoute)
{
_reRoute = fileReRoute;
}
private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig)
{
_globalConfig = fileGlobalConfig;
}
private void WhenICreate()
{
_result = _creator.Create(_reRoute, _globalConfig);
}
private void ThenTheConfigIs(ServiceProviderConfiguration expected)
{
_result.DownstreamHost.ShouldBe(expected.DownstreamHost);
_result.DownstreamPort.ShouldBe(expected.DownstreamPort);
_result.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider);
_result.ServiceName.ShouldBe(expected.ServiceName);
_result.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost);
_result.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort);
}
}
}

View File

@ -0,0 +1,122 @@
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class UpstreamTemplatePatternCreatorTests
{
private FileReRoute _fileReRoute;
private UpstreamTemplatePatternCreator _creator;
private string _result;
public UpstreamTemplatePatternCreatorTests()
{
_creator = new UpstreamTemplatePatternCreator();
}
[Fact]
public void should_set_upstream_template_pattern_to_ignore_case_sensitivity()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/PRODUCTS/{productId}",
ReRouteIsCaseSensitive = false
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("(?i)/PRODUCTS/.*/$"))
.BDDfy();
}
[Fact]
public void should_set_upstream_template_pattern_to_respect_case_sensitivity()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/PRODUCTS/{productId}",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/PRODUCTS/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_anything_to_end_of_string()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_to_end_of_string()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/"
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/$"))
.BDDfy();
}
private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void WhenICreateTheTemplatePattern()
{
_result = _creator.Create(_fileReRoute);
}
private void ThenTheFollowingIsReturned(string expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -1,5 +1,3 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Moq; using Moq;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;

View File

@ -28,7 +28,7 @@ namespace Ocelot.UnitTests.LoadBalancer
public void should_return_no_load_balancer() public void should_return_no_load_balancer()
{ {
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build())
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build(); .Build();
@ -45,7 +45,7 @@ namespace Ocelot.UnitTests.LoadBalancer
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithLoadBalancer("RoundRobin") .WithLoadBalancer("RoundRobin")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build())
.Build(); .Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.LoadBalancer
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithLoadBalancer("LeastConnection") .WithLoadBalancer("LeastConnection")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build())
.Build(); .Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
@ -77,7 +77,7 @@ namespace Ocelot.UnitTests.LoadBalancer
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithLoadBalancer("RoundRobin") .WithLoadBalancer("RoundRobin")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build())
.Build(); .Build();
this.Given(x => x.GivenAReRoute(reRoute)) this.Given(x => x.GivenAReRoute(reRoute))
@ -90,14 +90,14 @@ namespace Ocelot.UnitTests.LoadBalancer
private void GivenTheServiceProviderFactoryReturns() private void GivenTheServiceProviderFactoryReturns()
{ {
_serviceProviderFactory _serviceProviderFactory
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguraion>())) .Setup(x => x.Get(It.IsAny<ServiceProviderConfiguration>()))
.Returns(_serviceProvider.Object); .Returns(_serviceProvider.Object);
} }
private void ThenTheServiceProviderIsCalledCorrectly() private void ThenTheServiceProviderIsCalledCorrectly()
{ {
_serviceProviderFactory _serviceProviderFactory
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()), Times.Once); .Verify(x => x.Get(It.IsAny<ServiceProviderConfiguration>()), Times.Once);
} }
private void GivenAReRoute(ReRoute reRoute) private void GivenAReRoute(ReRoute reRoute)

View File

@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.Request
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider()))) .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider())))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider()))) .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider())))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy(); .BDDfy();
@ -105,7 +105,7 @@ namespace Ocelot.UnitTests.Request
_request = new OkResponse<Ocelot.Request.Request>(request); _request = new OkResponse<Ocelot.Request.Request>(request);
_requestBuilder _requestBuilder
.Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(), .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(),
It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>())) It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>()))
.ReturnsAsync(_request); .ReturnsAsync(_request);
} }

View File

@ -195,23 +195,6 @@ namespace Ocelot.UnitTests.Request
_qoSProvider = qoSProvider; _qoSProvider = qoSProvider;
} }
[Fact]
public void should_use_cookies()
{
this.Given(x => x.GivenIHaveHttpMethod("GET"))
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheCookiesAre(new RequestCookieCollection(new Dictionary<string, string>
{
{ "TheCookie","Monster" }
})))
.When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectCookiesAreUsed(new RequestCookieCollection(new Dictionary<string, string>
{
{ "TheCookie","Monster" }
})))
.BDDfy();
}
[Fact] [Fact]
public void should_user_query_string() public void should_user_query_string()
{ {
@ -240,14 +223,14 @@ namespace Ocelot.UnitTests.Request
private void ThenTheCorrectCookiesAreUsed(IRequestCookieCollection expected) private void ThenTheCorrectCookiesAreUsed(IRequestCookieCollection expected)
{ {
var resultCookies = _result.Data.CookieContainer.GetCookies(new Uri(_downstreamUrl + _query)); /* var resultCookies = _result.Data.CookieContainer.GetCookies(new Uri(_downstreamUrl + _query));
var resultDictionary = resultCookies.Cast<Cookie>().ToDictionary(cook => cook.Name, cook => cook.Value); var resultDictionary = resultCookies.Cast<Cookie>().ToDictionary(cook => cook.Name, cook => cook.Value);
foreach (var expectedCookie in expected) foreach (var expectedCookie in expected)
{ {
var resultCookie = resultDictionary[expectedCookie.Key]; var resultCookie = resultDictionary[expectedCookie.Key];
resultCookie.ShouldBe(expectedCookie.Value); resultCookie.ShouldBe(expectedCookie.Value);
} }*/
} }
private void GivenTheCookiesAre(IRequestCookieCollection cookies) private void GivenTheCookiesAre(IRequestCookieCollection cookies)
@ -305,7 +288,7 @@ namespace Ocelot.UnitTests.Request
private void WhenICreateARequest() private void WhenICreateARequest()
{ {
_result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers,
_cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result; _query, _contentType, _requestId,_isQos,_qoSProvider).Result;
} }

View File

@ -62,7 +62,7 @@ namespace Ocelot.UnitTests.Requester
[Fact] [Fact]
public void should_call_scoped_data_repository_correctly() public void should_call_scoped_data_repository_correctly()
{ {
this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider()))) this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider())))
.And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage()))
.And(x => x.GivenTheScopedRepoReturns()) .And(x => x.GivenTheScopedRepoReturns())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())

View File

@ -9,7 +9,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery
{ {
public class ServiceProviderFactoryTests public class ServiceProviderFactoryTests
{ {
private ServiceProviderConfiguraion _serviceConfig; private ServiceProviderConfiguration _serviceConfig;
private IServiceDiscoveryProvider _result; private IServiceDiscoveryProvider _result;
private readonly ServiceDiscoveryProviderFactory _factory; private readonly ServiceDiscoveryProviderFactory _factory;
@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery
[Fact] [Fact]
public void should_return_no_service_provider() public void should_return_no_service_provider()
{ {
var serviceConfig = new ServiceProviderConfiguraionBuilder() var serviceConfig = new ServiceProviderConfigurationBuilder()
.WithDownstreamHost("127.0.0.1") .WithDownstreamHost("127.0.0.1")
.WithDownstreamPort(80) .WithDownstreamPort(80)
.WithUseServiceDiscovery(false) .WithUseServiceDiscovery(false)
@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery
[Fact] [Fact]
public void should_return_consul_service_provider() public void should_return_consul_service_provider()
{ {
var serviceConfig = new ServiceProviderConfiguraionBuilder() var serviceConfig = new ServiceProviderConfigurationBuilder()
.WithServiceName("product") .WithServiceName("product")
.WithUseServiceDiscovery(true) .WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("Consul") .WithServiceDiscoveryProvider("Consul")
@ -48,7 +48,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery
.BDDfy(); .BDDfy();
} }
private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig) private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig)
{ {
_serviceConfig = serviceConfig; _serviceConfig = serviceConfig;
} }