Merge pull request #13 from TomPallister/develop

Update Ocelot configuration without downtime
This commit is contained in:
geffzhang 2017-02-26 10:56:36 +08:00 committed by GitHub
commit 26c7d1e37b
94 changed files with 3866 additions and 305 deletions

View File

@ -1,48 +0,0 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Ocelot</id>
<version>1.0.0</version>
<authors>Tom Pallister</authors>
<owners>Tom Pallister</owners>
<licenseUrl>https://github.com/TomPallister/Ocelot/blob/develop/LICENSE.md</licenseUrl>
<projectUrl>https://github.com/TomPallister/Ocelot</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Ocelot Api Gateway</description>
<releaseNotes>Latest Ocelot</releaseNotes>
<copyright></copyright>
<tags></tags>
<dependencies>
<dependency id="Microsoft.AspNetCore.Server.IISIntegration" version="1.1.0" />
<dependency id="Microsoft.Extensions.Configuration.EnvironmentVariables" version= "1.1.0" />
<dependency id="Microsoft.Extensions.Configuration.FileExtensions" version= "1.1.0" />
<dependency id="Microsoft.Extensions.Configuration.Json" version="1.1.0" />
<dependency id="Microsoft.Extensions.Logging" version="1.1.0" />
<dependency id="Microsoft.Extensions.Logging.Console" version="1.1.0" />
<dependency id="Microsoft.Extensions.Logging.Debug" version="1.1.0" />
<dependency id="Microsoft.Extensions.Options.ConfigurationExtensions" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Http" version="1.1.0" />
<dependency id="System.Text.RegularExpressions" version="4.3.0" />
<dependency id="Microsoft.AspNetCore.Authentication.OAuth" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.JwtBearer" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.Cookies" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.Google" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.Facebook" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.Twitter" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication.MicrosoftAccount" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Authentication" version="1.1.0" />
<dependency id="IdentityServer4.AccessTokenValidation" version="1.0.2" />
<dependency id="Microsoft.AspNetCore.Mvc" version="1.1.0" />
<dependency id="Microsoft.AspNetCore.Server.Kestrel" version="1.1.0" />
<dependency id="Microsoft.NETCore.App" version="1.1.0" />
<dependency id="CacheManager.Core" version="0.9.2" />
<dependency id="CacheManager.Microsoft.Extensions.Configuration" version="0.9.2" />
<dependency id="CacheManager.Microsoft.Extensions.Logging" version="0.9.2" />
</dependencies>
</metadata>
<files>
<file src="src\Ocelot\bin\Release\netcoreapp1.4\Ocelot.dll" target="lib\netstandard1.4" />
<file src="src\Ocelot\bin\Release\netcoreapp1.4\Ocelot.pdb" target="lib\netstandard1.4" />
</files>
</package>

View File

@ -8,7 +8,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore .gitignore = .gitignore
appveyor.yml = appveyor.yml
build-and-release-unstable.ps1 = build-and-release-unstable.ps1 build-and-release-unstable.ps1 = build-and-release-unstable.ps1
build-and-run-tests.ps1 = build-and-run-tests.ps1 build-and-run-tests.ps1 = build-and-run-tests.ps1
build.cake = build.cake build.cake = build.cake
@ -19,7 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
GitVersion.yml = GitVersion.yml GitVersion.yml = GitVersion.yml
global.json = global.json global.json = global.json
LICENSE.md = LICENSE.md LICENSE.md = LICENSE.md
Ocelot.nuspec = Ocelot.nuspec ocelot.postman_collection.json = ocelot.postman_collection.json
README.md = README.md README.md = README.md
release.ps1 = release.ps1 release.ps1 = release.ps1
ReleaseNotes.md = ReleaseNotes.md ReleaseNotes.md = ReleaseNotes.md
@ -41,6 +40,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.ManualTest", "test\O
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.xproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.xproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.xproj", "{D4575572-99CA-4530-8737-C296EDA326F8}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -67,6 +68,10 @@ Global
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -77,5 +82,6 @@ Global
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B} {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B}
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} {02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B}
{106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B}
{D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -99,20 +99,48 @@ public class Startup
.WithDictionaryHandle(); .WithDictionaryHandle();
}; };
services.AddOcelotOutputCaching(settings); services.AddOcelotOutputCaching(settings);
services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(Configuration);
services.AddOcelot(); }
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddConsole(Configuration.GetSection("Logging"));
app.UseOcelot(); 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! This is pretty much all you need to get going.......more to come!
## Routing ## Routing
@ -167,6 +195,46 @@ This means that when Ocelot tries to match the incoming upstream url with an ups
evaluation will be case sensitive. This setting defaults to false so only set it if you want 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! 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 ## Service Discovery
@ -470,6 +538,8 @@ forwarded to the downstream service. Obviously this would break everything :(
and doesnt check the response is OK. I think the fact you can even call stuff and doesnt check the response is OK. I think the fact you can even call stuff
that isnt available is annoying. Let alone it be null. 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)
## Coming up ## Coming up
You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)

564
README.md.orig Normal file
View File

@ -0,0 +1,564 @@
# Ocelot
[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb)
[![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
Attempt at a .NET Api Gateway
This project is aimed at people using .NET running
a micro services / service orientated architecture
that need a unified point of entry into their system.
In particular I want easy integration with
IdentityServer reference and bearer tokens.
We have been unable to find this in my current workplace
without having to write our own Javascript middlewares
to handle the IdentityServer reference tokens. We would
rather use the IdentityServer code that already exists
to do this.
Ocelot is a bunch of middlewares in a specific order.
Ocelot manipulates the HttpRequest object into a state specified by its configuration until
it reaches a request builder middleware where it creates a HttpRequestMessage object which 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 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
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.
## 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.
## How to install
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.
Install Ocelot and it's dependecies using nuget. At the moment
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`
All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
## Configuration
An example configuration can be found [here](https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/configuration.json)
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.
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
{
"ReRoutes": [],
"GlobalConfiguration": {}
}
```
More information on how to use these options is below..
## Startup
An example startup using a json file for configuration can be seen below.
Currently this is the only way to get configuration into Ocelot.
<<<<<<< HEAD
public class Startup
=======
```csharp
public class Startup
{
public Startup(IHostingEnvironment env)
>>>>>>> develop
{
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 =>
{
<<<<<<< HEAD
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.
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.
=======
log.AddConsole(LogLevel.Debug);
})
.WithDictionaryHandle();
};
services.AddOcelotOutputCaching(settings);
services.AddOcelotFileConfiguration(Configuration);
services.AddOcelot();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
app.UseOcelot();
}
}
```
>>>>>>> develop
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.
"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
+ The ReRoute configuration object is too large.
+ The base OcelotMiddleware lets you access things that are going to be null
and doesnt check the response is OK. I think the fact you can even call stuff
that isnt available is annoying. Let alone it be null.
## Coming up
You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)

View File

@ -1,8 +0,0 @@
version: 1.0.{build}
configuration:
- Release
platform: Any CPU
build_script:
- build.ps1
cache:
- '%USERPROFILE%\.nuget\packages'

View File

@ -18,7 +18,9 @@ var unitTestAssemblies = @"./test/Ocelot.UnitTests";
// acceptance testing // acceptance testing
var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests");
var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests";
// integration testing
var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests");
// benchmark testing // benchmark testing
var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests");
@ -44,9 +46,12 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
var releaseTag = ""; var releaseTag = "";
string committedVersion = "0.0.0-dev"; string committedVersion = "0.0.0-dev";
var buildVersion = committedVersion; var buildVersion = committedVersion;
GitVersion versioning = null;
var nugetFeedUnstableBranchFilter = "^(develop)$|^(PullRequest/)";
var target = Argument("target", "Default"); var target = Argument("target", "Default");
Information("target is " +target); Information("target is " +target);
Information("Build configuration is " + compileConfig); Information("Build configuration is " + compileConfig);
@ -74,7 +79,9 @@ Task("Clean")
Task("Version") Task("Version")
.Does(() => .Does(() =>
{ {
var nugetVersion = GetNuGetVersionForCommit(); versioning = GetNuGetVersionForCommit();
var nugetVersion = versioning.NuGetVersion;
Information("SemVer version number: " + nugetVersion); Information("SemVer version number: " + nugetVersion);
if (AppVeyor.IsRunningOnAppVeyor) if (AppVeyor.IsRunningOnAppVeyor)
@ -129,6 +136,24 @@ Task("RunAcceptanceTests")
}); });
Task("RunIntegrationTests")
.IsDependentOn("Restore")
.Does(() =>
{
var buildSettings = new DotNetCoreTestSettings
{
Configuration = "Debug", //int test config is hard-coded for debug
};
EnsureDirectoryExists(artifactsForIntegrationTestsDir);
DoInDirectory("test/Ocelot.IntegrationTests", () =>
{
DotNetCoreTest(".", buildSettings);
});
});
Task("RunBenchmarkTests") Task("RunBenchmarkTests")
.IsDependentOn("Restore") .IsDependentOn("Restore")
.Does(() => .Does(() =>
@ -149,6 +174,7 @@ Task("RunBenchmarkTests")
Task("RunTests") Task("RunTests")
.IsDependentOn("RunUnitTests") .IsDependentOn("RunUnitTests")
.IsDependentOn("RunAcceptanceTests") .IsDependentOn("RunAcceptanceTests")
.IsDependentOn("RunIntegrationTests")
.Does(() => .Does(() =>
{ {
}); });
@ -189,7 +215,10 @@ Task("ReleasePackagesToUnstableFeed")
.IsDependentOn("CreatePackages") .IsDependentOn("CreatePackages")
.Does(() => .Does(() =>
{ {
PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); if (ShouldPublishToUnstableFeed(nugetFeedUnstableBranchFilter, versioning.BranchName))
{
PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl);
}
}); });
Task("EnsureStableReleaseRequirements") Task("EnsureStableReleaseRequirements")
@ -250,15 +279,14 @@ Task("Release")
RunTarget(target); RunTarget(target);
/// Gets nuique nuget version for this commit /// Gets nuique nuget version for this commit
private string GetNuGetVersionForCommit() private GitVersion GetNuGetVersionForCommit()
{ {
GitVersion(new GitVersionSettings{ GitVersion(new GitVersionSettings{
UpdateAssemblyInfo = false, UpdateAssemblyInfo = false,
OutputType = GitVersionOutput.BuildServer OutputType = GitVersionOutput.BuildServer
}); });
var versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json });
return versionInfo.NuGetVersion;
} }
/// Updates project version in all of our projects /// Updates project version in all of our projects
@ -343,3 +371,18 @@ private string GetResource(string url)
return assetsReader.ReadToEnd(); return assetsReader.ReadToEnd();
} }
} }
private bool ShouldPublishToUnstableFeed(string filter, string branchName)
{
var regex = new System.Text.RegularExpressions.Regex(filter);
var publish = regex.IsMatch(branchName);
if (publish)
{
Information("Branch " + branchName + " will be published to the unstable feed");
}
else
{
Information("Branch " + branchName + " will not be published to the unstable feed");
}
return publish;
}

1
configuration.json Executable file
View File

@ -0,0 +1 @@
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,6 @@
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;

View File

@ -1,7 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;

View File

@ -0,0 +1,22 @@
using System;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace Ocelot.Configuration.Authentication
{
public class HashMatcher : IHashMatcher
{
public bool Match(string password, string salt, string hash)
{
byte[] s = Convert.FromBase64String(salt);
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: s,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 10000,
numBytesRequested: 256 / 8));
return hashed == hash;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Ocelot.Configuration.Authentication
{
public interface IHashMatcher
{
bool Match(string password, string salt, string hash);
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Validation;
using Ocelot.Configuration.Provider;
namespace Ocelot.Configuration.Authentication
{
public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly IHashMatcher _matcher;
private readonly IIdentityServerConfiguration _identityServerConfiguration;
public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration)
{
_identityServerConfiguration = identityServerConfiguration;
_matcher = matcher;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName);
if(user == null)
{
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
"invalid custom credential");
}
else if(_matcher.Match(context.Password, user.Salt, user.Hash))
{
context.Result = new GrantValidationResult(
subject: "admin",
authenticationMethod: "custom");
}
else
{
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
"invalid custom credential");
}
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
}
}

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Ocelot.Values; using Ocelot.Values;

View File

@ -56,14 +56,21 @@ namespace Ocelot.Configuration.Creator
public async Task<Response<IOcelotConfiguration>> Create() public async Task<Response<IOcelotConfiguration>> Create()
{ {
var config = await SetUpConfiguration(); var config = await SetUpConfiguration(_options.Value);
return new OkResponse<IOcelotConfiguration>(config); return new OkResponse<IOcelotConfiguration>(config);
} }
private async Task<IOcelotConfiguration> SetUpConfiguration() public async Task<Response<IOcelotConfiguration>> Create(FileConfiguration fileConfiguration)
{ {
var response = _configurationValidator.IsValid(_options.Value); var config = await SetUpConfiguration(fileConfiguration);
return new OkResponse<IOcelotConfiguration>(config);
}
private async Task<IOcelotConfiguration> SetUpConfiguration(FileConfiguration fileConfiguration)
{
var response = _configurationValidator.IsValid(fileConfiguration);
if (response.Data.IsError) if (response.Data.IsError)
{ {
@ -79,13 +86,13 @@ namespace Ocelot.Configuration.Creator
var reRoutes = new List<ReRoute>(); var reRoutes = new List<ReRoute>();
foreach (var reRoute in _options.Value.ReRoutes) foreach (var reRoute in fileConfiguration.ReRoutes)
{ {
var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); var ocelotReRoute = await SetUpReRoute(reRoute, fileConfiguration.GlobalConfiguration);
reRoutes.Add(ocelotReRoute); reRoutes.Add(ocelotReRoute);
} }
return new OcelotConfiguration(reRoutes); return new OcelotConfiguration(reRoutes, fileConfiguration.GlobalConfiguration.AdministrationPath);
} }
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
@ -6,5 +7,6 @@ namespace Ocelot.Configuration.Creator
public interface IOcelotConfigurationCreator public interface IOcelotConfigurationCreator
{ {
Task<Response<IOcelotConfiguration>> Create(); Task<Response<IOcelotConfiguration>> Create();
Task<Response<IOcelotConfiguration>> Create(FileConfiguration fileConfiguration);
} }
} }

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Ocelot.Configuration.Provider;
namespace Ocelot.Configuration.Creator
{
public static class IdentityServerConfigurationCreator
{
public static IdentityServerConfiguration GetIdentityServerConfiguration()
{
var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME");
var hash = Environment.GetEnvironmentVariable("OCELOT_HASH");
var salt = Environment.GetEnvironmentVariable("OCELOT_SALT");
return new IdentityServerConfiguration(
"admin",
false,
SupportedTokens.Both,
"secret",
new List<string> { "admin", "openid", "offline_access" },
"Ocelot Administration",
true,
GrantTypes.ResourceOwnerPassword,
AccessTokenType.Jwt,
false,
new List<User>
{
new User("admin", username, hash, salt)
}
);
}
}
}

View File

@ -1,4 +1,5 @@
namespace Ocelot.Configuration.File 
namespace Ocelot.Configuration.File
{ {
public class FileGlobalConfiguration public class FileGlobalConfiguration
{ {
@ -7,9 +8,11 @@
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
RateLimitOptions = new FileRateLimitOptions(); RateLimitOptions = new FileRateLimitOptions();
} }
public string RequestIdKey { get; set; } public string RequestIdKey { get; set; }
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
public string AdministrationPath {get;set;}
public FileRateLimitOptions RateLimitOptions { get; set; } public FileRateLimitOptions RateLimitOptions { get; set; }
} }

View File

@ -0,0 +1,22 @@

namespace Ocelot.Configuration.File
{
public class FileGlobalConfiguration
{
public FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
RateLimitOptions = new FileRateLimitOptions();
}
public string RequestIdKey { get; set; }
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
<<<<<<< HEAD
public string AdministrationPath {get;set;}
=======
public FileRateLimitOptions RateLimitOptions { get; set; }
>>>>>>> develop
}
}

View File

@ -1,9 +1,4 @@
using System; namespace Ocelot.Configuration.File
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Configuration.File
{ {
public class FileQoSOptions public class FileQoSOptions
{ {

View File

@ -2,6 +2,7 @@ namespace Ocelot.Configuration.File
{ {
public class FileServiceDiscoveryProvider public class FileServiceDiscoveryProvider
{ {
public string Provider {get;set;} public string Provider {get;set;}
public string Host {get;set;} public string Host {get;set;}
public int Port { get; set; } public int Port { get; set; }

View File

@ -5,5 +5,6 @@ namespace Ocelot.Configuration
public interface IOcelotConfiguration public interface IOcelotConfiguration
{ {
List<ReRoute> ReRoutes { get; } List<ReRoute> ReRoutes { get; }
string AdministrationPath {get;}
} }
} }

View File

@ -4,11 +4,13 @@ namespace Ocelot.Configuration
{ {
public class OcelotConfiguration : IOcelotConfiguration public class OcelotConfiguration : IOcelotConfiguration
{ {
public OcelotConfiguration(List<ReRoute> reRoutes) public OcelotConfiguration(List<ReRoute> reRoutes, string administrationPath)
{ {
ReRoutes = reRoutes; ReRoutes = reRoutes;
AdministrationPath = administrationPath;
} }
public List<ReRoute> ReRoutes { get; } public List<ReRoute> ReRoutes { get; }
public string AdministrationPath {get;}
} }
} }

View File

@ -0,0 +1,25 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Responses;
namespace Ocelot.Configuration.Provider
{
public class FileConfigurationProvider : IFileConfigurationProvider
{
private IFileConfigurationRepository _repo;
public FileConfigurationProvider(IFileConfigurationRepository repo)
{
_repo = repo;
}
public Response<FileConfiguration> Get()
{
var fileConfig = _repo.Get();
return new OkResponse<FileConfiguration>(fileConfig.Data);
}
}
}

View File

@ -0,0 +1,10 @@
using Ocelot.Configuration.File;
using Ocelot.Responses;
namespace Ocelot.Configuration.Provider
{
public interface IFileConfigurationProvider
{
Response<FileConfiguration> Get();
}
}

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
namespace Ocelot.Configuration.Provider
{
public interface IIdentityServerConfiguration
{
string ApiName { get; }
bool RequireHttps { get; }
List<string> AllowedScopes { get; }
SupportedTokens SupportedTokens { get; }
string ApiSecret { get; }
string Description {get;}
bool Enabled {get;}
IEnumerable<string> AllowedGrantTypes {get;}
AccessTokenType AccessTokenType {get;}
bool RequireClientSecret {get;}
List<User> Users {get;}
}
}

View File

@ -1,10 +1,9 @@
using System.Threading.Tasks; using Ocelot.Responses;
using Ocelot.Responses;
namespace Ocelot.Configuration.Provider namespace Ocelot.Configuration.Provider
{ {
public interface IOcelotConfigurationProvider public interface IOcelotConfigurationProvider
{ {
Task<Response<IOcelotConfiguration>> Get(); Response<IOcelotConfiguration> Get();
} }
} }

View File

@ -0,0 +1,47 @@
using System.Collections.Generic;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
namespace Ocelot.Configuration.Provider
{
public class IdentityServerConfiguration : IIdentityServerConfiguration
{
public IdentityServerConfiguration(
string apiName,
bool requireHttps,
SupportedTokens supportedTokens,
string apiSecret,
List<string> allowedScopes,
string description,
bool enabled,
IEnumerable<string> grantType,
AccessTokenType accessTokenType,
bool requireClientSecret,
List<User> users)
{
ApiName = apiName;
RequireHttps = requireHttps;
SupportedTokens = supportedTokens;
ApiSecret = apiSecret;
AllowedScopes = allowedScopes;
Description = description;
Enabled = enabled;
AllowedGrantTypes = grantType;
AccessTokenType = accessTokenType;
RequireClientSecret = requireClientSecret;
Users = users;
}
public string ApiName { get; private set; }
public bool RequireHttps { get; private set; }
public List<string> AllowedScopes { get; private set; }
public SupportedTokens SupportedTokens { get; private set; }
public string ApiSecret { get; private set; }
public string Description {get;private set;}
public bool Enabled {get;private set;}
public IEnumerable<string> AllowedGrantTypes {get;private set;}
public AccessTokenType AccessTokenType {get;private set;}
public bool RequireClientSecret {get;private set;}
public List<User> Users {get;private set;}
}
}

View File

@ -1,6 +1,4 @@
using System.Threading.Tasks; using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.Repository;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Configuration.Provider namespace Ocelot.Configuration.Provider
@ -11,16 +9,13 @@ namespace Ocelot.Configuration.Provider
public class OcelotConfigurationProvider : IOcelotConfigurationProvider public class OcelotConfigurationProvider : IOcelotConfigurationProvider
{ {
private readonly IOcelotConfigurationRepository _repo; private readonly IOcelotConfigurationRepository _repo;
private readonly IOcelotConfigurationCreator _creator;
public OcelotConfigurationProvider(IOcelotConfigurationRepository repo, public OcelotConfigurationProvider(IOcelotConfigurationRepository repo)
IOcelotConfigurationCreator creator)
{ {
_repo = repo; _repo = repo;
_creator = creator;
} }
public async Task<Response<IOcelotConfiguration>> Get() public Response<IOcelotConfiguration> Get()
{ {
var repoConfig = _repo.Get(); var repoConfig = _repo.Get();
@ -29,20 +24,6 @@ namespace Ocelot.Configuration.Provider
return new ErrorResponse<IOcelotConfiguration>(repoConfig.Errors); return new ErrorResponse<IOcelotConfiguration>(repoConfig.Errors);
} }
if (repoConfig.Data == null)
{
var creatorConfig = await _creator.Create();
if (creatorConfig.IsError)
{
return new ErrorResponse<IOcelotConfiguration>(creatorConfig.Errors);
}
_repo.AddOrReplace(creatorConfig.Data);
return new OkResponse<IOcelotConfiguration>(creatorConfig.Data);
}
return new OkResponse<IOcelotConfiguration>(repoConfig.Data); return new OkResponse<IOcelotConfiguration>(repoConfig.Data);
} }
} }

View File

@ -0,0 +1,17 @@
namespace Ocelot.Configuration.Provider
{
public class User
{
public User(string subject, string userName, string hash, string salt)
{
Subject = subject;
UserName = userName;
Hash = hash;
Salt = salt;
}
public string Subject { get; private set; }
public string UserName { get; private set; }
public string Hash { get; private set; }
public string Salt { get; private set; }
}
}

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Ocelot.Values; using Ocelot.Values;

View File

@ -0,0 +1,42 @@
using System;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
public class FileConfigurationRepository : IFileConfigurationRepository
{
private static readonly object _lock = new object();
public Response<FileConfiguration> Get()
{
var configFilePath = $"{AppContext.BaseDirectory}/configuration.json";
string json = string.Empty;
lock(_lock)
{
json = System.IO.File.ReadAllText(configFilePath);
}
var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(json);
return new OkResponse<FileConfiguration>(fileConfiguration);
}
public Response Set(FileConfiguration fileConfiguration)
{
var configurationPath = $"{AppContext.BaseDirectory}/configuration.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
lock(_lock)
{
if (System.IO.File.Exists(configurationPath))
{
System.IO.File.Delete(configurationPath);
}
System.IO.File.WriteAllText(configurationPath, jsonConfiguration);
}
return new OkResponse();
}
}
}

View File

@ -0,0 +1,11 @@
using Ocelot.Configuration.File;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
public interface IFileConfigurationRepository
{
Response<FileConfiguration> Get();
Response Set(FileConfiguration fileConfiguration);
}
}

View File

@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Responses;
namespace Ocelot.Configuration.Setter
{
public class FileConfigurationSetter : IFileConfigurationSetter
{
private readonly IOcelotConfigurationRepository _configRepo;
private readonly IOcelotConfigurationCreator _configCreator;
private readonly IFileConfigurationRepository _repo;
public FileConfigurationSetter(IOcelotConfigurationRepository configRepo,
IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo)
{
_configRepo = configRepo;
_configCreator = configCreator;
_repo = repo;
}
public async Task<Response> Set(FileConfiguration fileConfig)
{
var response = _repo.Set(fileConfig);
if(response.IsError)
{
return new ErrorResponse(response.Errors);
}
var config = await _configCreator.Create(fileConfig);
if(!config.IsError)
{
_configRepo.AddOrReplace(config.Data);
}
return new ErrorResponse(config.Errors);
}
}
}

View File

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses;
namespace Ocelot.Configuration.Setter
{
public interface IFileConfigurationSetter
{
Task<Response> Set(FileConfiguration config);
}
}

View File

@ -0,0 +1,49 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Setter;
namespace Ocelot.Controllers
{
[Authorize]
[Route("configuration")]
public class FileConfigurationController : Controller
{
private readonly IFileConfigurationProvider _configGetter;
private readonly IFileConfigurationSetter _configSetter;
public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter)
{
_configGetter = getFileConfig;
_configSetter = configSetter;
}
[HttpGet]
public IActionResult Get()
{
var response = _configGetter.Get();
if(response.IsError)
{
return new BadRequestObjectResult(response.Errors);
}
return new OkObjectResult(response.Data);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
{
var response = await _configSetter.Set(fileConfiguration);
if(response.IsError)
{
return new BadRequestObjectResult(response.Errors);
}
return new OkObjectResult(fileConfiguration);
}
}
}

View File

@ -1,20 +1,24 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using CacheManager.Core; using CacheManager.Core;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Creator;
using Ocelot.Authentication.Handler.Factory; using Ocelot.Authentication.Handler.Factory;
using Ocelot.Authorisation; using Ocelot.Authorisation;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Claims; using Ocelot.Claims;
using Ocelot.Configuration.Authentication;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
@ -25,44 +29,86 @@ using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.QueryStrings; using Ocelot.QueryStrings;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
using Ocelot.RateLimit; using Ocelot.RateLimit;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings) public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
{ {
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings); var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
var ocelotCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache); var ocelotCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache);
services.AddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache); services.AddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
services.AddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotCacheManager); services.AddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotCacheManager);
return services; return services;
} }
public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot)
{ {
services.Configure<FileConfiguration>(configurationRoot); services.Configure<FileConfiguration>(configurationRoot);
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>(); services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>(); services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>(); services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
services.AddSingleton<IBaseUrlFinder, BaseUrlFinder>();
return services; var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();
}
public static IServiceCollection AddOcelot(this IServiceCollection services) if(identityServerConfiguration != null)
{ {
services.AddMvcCore().AddJsonFormatters(); services.AddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
services.AddSingleton<IHashMatcher, HashMatcher>();
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource
{
Name = identityServerConfiguration.ApiName,
Description = identityServerConfiguration.Description,
Enabled = identityServerConfiguration.Enabled,
DisplayName = identityServerConfiguration.ApiName,
Scopes = identityServerConfiguration.AllowedScopes.Select(x => new Scope(x)).ToList(),
ApiSecrets = new List<Secret>
{
new Secret
{
Value = identityServerConfiguration.ApiSecret.Sha256()
}
}
}
})
.AddInMemoryClients(new List<Client>
{
new Client
{
ClientId = identityServerConfiguration.ApiName,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
AllowedScopes = identityServerConfiguration.AllowedScopes,
AccessTokenType = identityServerConfiguration.AccessTokenType,
Enabled = identityServerConfiguration.Enabled,
RequireClientSecret = identityServerConfiguration.RequireClientSecret
}
}).AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
}
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddLogging(); services.AddLogging();
services.AddSingleton<IFileConfigurationRepository, FileConfigurationRepository>();
services.AddSingleton<IFileConfigurationSetter, FileConfigurationSetter>();
services.AddSingleton<IFileConfigurationProvider, FileConfigurationProvider>();
services.AddSingleton<IQosProviderHouse, QosProviderHouse>(); services.AddSingleton<IQosProviderHouse, QosProviderHouse>();
services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>(); services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>();
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();

View File

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using CacheManager.Core;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Authentication.Handler.Creator;
using Ocelot.Authentication.Handler.Factory;
using Ocelot.Authorisation;
using Ocelot.Cache;
using Ocelot.Claims;
using Ocelot.Configuration.Authentication;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter;
using Ocelot.Configuration.Validator;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.DownstreamUrlCreator;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Infrastructure.RequestData;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.QueryStrings;
using Ocelot.Request.Builder;
using Ocelot.Requester;
using Ocelot.Requester.QoS;
using Ocelot.Responder;
using Ocelot.ServiceDiscovery;
<<<<<<< HEAD
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
=======
using Ocelot.RateLimit;
>>>>>>> develop
namespace Ocelot.DependencyInjection
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
{
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
var ocelotCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache);
services.AddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
services.AddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotCacheManager);
return services;
}
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot)
{
services.Configure<FileConfiguration>(configurationRoot);
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
services.AddSingleton<IBaseUrlFinder, BaseUrlFinder>();
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();
if(identityServerConfiguration != null)
{
services.AddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
services.AddSingleton<IHashMatcher, HashMatcher>();
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource
{
Name = identityServerConfiguration.ApiName,
Description = identityServerConfiguration.Description,
Enabled = identityServerConfiguration.Enabled,
DisplayName = identityServerConfiguration.ApiName,
Scopes = identityServerConfiguration.AllowedScopes.Select(x => new Scope(x)).ToList(),
ApiSecrets = new List<Secret>
{
new Secret
{
Value = identityServerConfiguration.ApiSecret.Sha256()
}
}
}
})
.AddInMemoryClients(new List<Client>
{
new Client
{
ClientId = identityServerConfiguration.ApiName,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
AllowedScopes = identityServerConfiguration.AllowedScopes,
AccessTokenType = identityServerConfiguration.AccessTokenType,
Enabled = identityServerConfiguration.Enabled,
RequireClientSecret = identityServerConfiguration.RequireClientSecret
}
}).AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
}
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddLogging();
services.AddSingleton<IFileConfigurationRepository, FileConfigurationRepository>();
services.AddSingleton<IFileConfigurationSetter, FileConfigurationSetter>();
services.AddSingleton<IFileConfigurationProvider, FileConfigurationProvider>();
services.AddSingleton<IQosProviderHouse, QosProviderHouse>();
services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>();
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
services.AddSingleton<IUrlBuilder, UrlBuilder>();
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
services.AddSingleton<IAuthoriser, ClaimsAuthoriser>();
services.AddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
services.AddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
services.AddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
services.AddSingleton<IClaimsParser, ClaimsParser>();
services.AddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
services.AddSingleton<IUrlPathPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
services.AddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
services.AddSingleton<IHttpResponder, HttpContextResponder>();
services.AddSingleton<IRequestCreator, HttpRequestCreator>();
services.AddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
services.AddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
// could maybe use a scoped data repository
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IRequestScopedDataRepository, HttpDataRepository>();
services.AddMemoryCache();
return services;
}
}
}

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Provider;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors; using Ocelot.Errors;
@ -22,9 +21,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
} }
public async Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
{ {
var configuration = await _configProvider.Get(); var configuration = _configProvider.Get();
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));

View File

@ -1,10 +1,9 @@
using System.Threading.Tasks; using Ocelot.Responses;
using Ocelot.Responses;
namespace Ocelot.DownstreamRouteFinder.Finder namespace Ocelot.DownstreamRouteFinder.Finder
{ {
public interface IDownstreamRouteFinder public interface IDownstreamRouteFinder
{ {
Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
} }
} }

View File

@ -1,6 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
@ -34,7 +33,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath);
var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
if (downstreamRoute.IsError) if (downstreamRoute.IsError)
{ {

View File

@ -1,6 +1,4 @@
using Ocelot.Configuration; using Ocelot.Responses;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Responses;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.DownstreamUrlCreator namespace Ocelot.DownstreamUrlCreator

View File

@ -1,12 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Configuration;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Values;
namespace Ocelot.DownstreamUrlCreator.Middleware namespace Ocelot.DownstreamUrlCreator.Middleware
{ {

View File

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values; using Ocelot.Values;

View File

@ -1,8 +1,6 @@
using System; using System;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;

View File

@ -1,7 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;

View File

@ -3,8 +3,6 @@ using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
using System;
public class RemoveOutputHeaders : IRemoveOutputHeaders public class RemoveOutputHeaders : IRemoveOutputHeaders
{ {
/// <summary> /// <summary>

View File

@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values; using Ocelot.Values;

View File

@ -1,7 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.LoadBalancer.LoadBalancers namespace Ocelot.LoadBalancer.LoadBalancers

View File

@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Hosting;
namespace Ocelot.Middleware
{
public class BaseUrlFinder : IBaseUrlFinder
{
private readonly IWebHostBuilder _webHostBuilder;
public BaseUrlFinder(IWebHostBuilder webHostBuilder)
{
_webHostBuilder = webHostBuilder;
}
public string Find()
{
var baseSchemeUrlAndPort = _webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey);
return string.IsNullOrEmpty(baseSchemeUrlAndPort) ? "http://localhost:5000" : baseSchemeUrlAndPort;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Ocelot.Middleware
{
public interface IBaseUrlFinder
{
string Find();
}
}

View File

@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Builder; using System.Collections.Generic;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Builder;
using Ocelot.Authentication.Middleware; using Ocelot.Authentication.Middleware;
using Ocelot.Cache.Middleware; using Ocelot.Cache.Middleware;
using Ocelot.Claims.Middleware; using Ocelot.Claims.Middleware;
@ -18,8 +20,13 @@ namespace Ocelot.Middleware
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Authorisation.Middleware; using Authorisation.Middleware;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Ocelot.Configuration;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Setter;
using Ocelot.LoadBalancer.Middleware; using Ocelot.LoadBalancer.Middleware;
public static class OcelotMiddlewareExtensions public static class OcelotMiddlewareExtensions
@ -29,10 +36,10 @@ namespace Ocelot.Middleware
/// </summary> /// </summary>
/// <param name="builder"></param> /// <param name="builder"></param>
/// <returns></returns> /// <returns></returns>
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
{ {
CreateConfiguration(builder); await builder.UseOcelot(new OcelotMiddlewareConfiguration());
builder.UseOcelot(new OcelotMiddlewareConfiguration());
return builder; return builder;
} }
@ -42,9 +49,9 @@ namespace Ocelot.Middleware
/// <param name="builder"></param> /// <param name="builder"></param>
/// <param name="middlewareConfiguration"></param> /// <param name="middlewareConfiguration"></param>
/// <returns></returns> /// <returns></returns>
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
{ {
CreateConfiguration(builder); await CreateAdministrationArea(builder);
// This is registered to catch any global exceptions that are not handled // This is registered to catch any global exceptions that are not handled
builder.UseExceptionHandlerMiddleware(); builder.UseExceptionHandlerMiddleware();
@ -126,15 +133,61 @@ namespace Ocelot.Middleware
return builder; return builder;
} }
private static void CreateConfiguration(IApplicationBuilder builder) private static async Task<IOcelotConfiguration> CreateConfiguration(IApplicationBuilder builder)
{ {
var fileConfig = (IOptions<FileConfiguration>)builder.ApplicationServices.GetService(typeof(IOptions<FileConfiguration>));
var configSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider));
var config = configProvider.Get(); var config = await configSetter.Set(fileConfig.Value);
if(config == null) if(config == null || config.IsError)
{ {
throw new Exception("Unable to start Ocelot: configuration was null"); throw new Exception("Unable to start Ocelot: configuration was not set up correctly.");
}
var ocelotConfiguration = configProvider.Get();
if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError)
{
throw new Exception("Unable to start Ocelot: ocelot configuration was not returned by provider.");
}
return ocelotConfiguration.Data;
}
private static async Task CreateAdministrationArea(IApplicationBuilder builder)
{
var configuration = await CreateConfiguration(builder);
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null)
{
var urlFinder = (IBaseUrlFinder)builder.ApplicationServices.GetService(typeof(IBaseUrlFinder));
var baseSchemeUrlAndPort = urlFinder.Find();
builder.Map(configuration.AdministrationPath, app =>
{
var identityServerUrl = $"{baseSchemeUrlAndPort}/{configuration.AdministrationPath.Remove(0,1)}";
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = identityServerUrl,
ApiName = identityServerConfiguration.ApiName,
RequireHttpsMetadata = identityServerConfiguration.RequireHttps,
AllowedScopes = identityServerConfiguration.AllowedScopes,
SupportedTokens = SupportedTokens.Both,
ApiSecret = identityServerConfiguration.ApiSecret
});
app.UseIdentityServer();
app.UseMvc();
});
} }
} }

View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following

View File

@ -1,7 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;

View File

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -54,7 +55,10 @@ namespace Ocelot.Responder
using (Stream stream = new MemoryStream(content)) using (Stream stream = new MemoryStream(content))
{ {
await stream.CopyToAsync(context.Response.Body); if (response.StatusCode != HttpStatusCode.NotModified)
{
await stream.CopyToAsync(context.Response.Body);
}
} }
} }

View File

@ -5,6 +5,9 @@ namespace Ocelot.Responses
{ {
public class ErrorResponse : Response public class ErrorResponse : Response
{ {
public ErrorResponse(Error error) : base(new List<Error>{error})
{
}
public ErrorResponse(List<Error> errors) : base(errors) public ErrorResponse(List<Error> errors) : base(errors)
{ {
} }

View File

@ -36,11 +36,14 @@
"CacheManager.Microsoft.Extensions.Configuration": "0.9.2", "CacheManager.Microsoft.Extensions.Configuration": "0.9.2",
"CacheManager.Microsoft.Extensions.Logging": "0.9.2", "CacheManager.Microsoft.Extensions.Logging": "0.9.2",
"Consul": "0.7.2.1", "Consul": "0.7.2.1",
"Polly": "5.0.3" "Polly": "5.0.3",
"IdentityServer4": "1.0.1",
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
}, },
"runtimes": { "runtimes": {
"win10-x64": {}, "win10-x64": {},
"osx.10.11-x64": {}, "osx.10.11-x64": {},
"osx.10.12-x64": {},
"win7-x64": {} "win7-x64": {}
}, },
"frameworks": { "frameworks": {

View File

@ -0,0 +1,88 @@
{
"version": "0.0.0-dev",
<<<<<<< HEAD
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"System.Text.RegularExpressions": "4.3.0",
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0",
"Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
"Microsoft.AspNetCore.Authentication.Google": "1.1.0",
"Microsoft.AspNetCore.Authentication.Facebook": "1.1.0",
"Microsoft.AspNetCore.Authentication.Twitter": "1.1.0",
"Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0",
"Microsoft.AspNetCore.Authentication": "1.1.0",
"IdentityServer4.AccessTokenValidation": "1.0.2",
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"CacheManager.Core": "0.9.2",
"CacheManager.Microsoft.Extensions.Configuration": "0.9.2",
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
"Consul": "0.7.2.1",
"Polly": "5.0.3",
"IdentityServer4": "1.0.1",
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
},
=======
"title": "Ocelot",
"summary": "API Gateway created using .NET core.",
"projectUrl": "https://github.com/TomPallister/Ocelot",
"description": "This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which 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 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 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.",
"tags": [
"API Gateway",
".NET core"
],
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"System.Text.RegularExpressions": "4.3.0",
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0",
"Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
"Microsoft.AspNetCore.Authentication.Google": "1.1.0",
"Microsoft.AspNetCore.Authentication.Facebook": "1.1.0",
"Microsoft.AspNetCore.Authentication.Twitter": "1.1.0",
"Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0",
"Microsoft.AspNetCore.Authentication": "1.1.0",
"IdentityServer4.AccessTokenValidation": "1.0.2",
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"CacheManager.Core": "0.9.2",
"CacheManager.Microsoft.Extensions.Configuration": "0.9.2",
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
"Consul": "0.7.2.1",
"Polly": "5.0.3"
},
>>>>>>> develop
"runtimes": {
"win10-x64": {},
"osx.10.11-x64": {},
"osx.10.12-x64": {},
"win7-x64": {}
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
]
}
}
}

View File

@ -21,7 +21,7 @@ namespace Ocelot.AcceptanceTests
} }
[Fact] [Fact]
public void should_return_response_200_and_foward_claim_as_header() public void should_return_internal_server_error_if_downstream_service_returns_internal_server_error()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
@ -29,9 +29,12 @@ namespace Ocelot.AcceptanceTests
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "http://localhost:53876/", DownstreamPathTemplate = "/",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get" UpstreamHttpMethod = "Get",
DownstreamPort = 53876,
DownstreamScheme = "http",
DownstreamHost = "localhost"
} }
} }
}; };

View File

@ -32,7 +32,8 @@ namespace Ocelot.AcceptanceTests
private BearerToken _token; private BearerToken _token;
public HttpClient OcelotClient => _ocelotClient; public HttpClient OcelotClient => _ocelotClient;
public string RequestIdKey = "OcRequestId"; public string RequestIdKey = "OcRequestId";
private Random _random; private readonly Random _random;
private IWebHostBuilder _webHostBuilder;
public Steps() public Steps()
{ {
@ -70,12 +71,40 @@ namespace Ocelot.AcceptanceTests
/// </summary> /// </summary>
public void GivenOcelotIsRunning() public void GivenOcelotIsRunning()
{ {
_ocelotServer = new TestServer(new WebHostBuilder() _webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
});
_ocelotServer = new TestServer(_webHostBuilder
.UseStartup<Startup>()); .UseStartup<Startup>());
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
internal void ThenTheResponseShouldBe(FileConfiguration expected)
{
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider);
for(var i = 0; i < response.ReRoutes.Count; i++)
{
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort);
response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme);
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate);
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod);
}
}
/// <summary> /// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary> /// </summary>
@ -89,7 +118,14 @@ namespace Ocelot.AcceptanceTests
var configuration = builder.Build(); var configuration = builder.Build();
_ocelotServer = new TestServer(new WebHostBuilder() _webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
});
_ocelotServer = new TestServer(_webHostBuilder
.UseConfiguration(configuration) .UseConfiguration(configuration)
.ConfigureServices(s => .ConfigureServices(s =>
{ {
@ -101,9 +137,9 @@ namespace Ocelot.AcceptanceTests
}) })
.WithDictionaryHandle(); .WithDictionaryHandle();
}; };
s.AddOcelotOutputCaching(settings); s.AddOcelotOutputCaching(settings);
s.AddOcelotFileConfiguration(configuration); s.AddOcelot(configuration);
s.AddOcelot();
}) })
.ConfigureLogging(l => .ConfigureLogging(l =>
{ {
@ -112,7 +148,7 @@ namespace Ocelot.AcceptanceTests
}) })
.Configure(a => .Configure(a =>
{ {
a.UseOcelot(ocelotMiddlewareConfig); a.UseOcelot(ocelotMiddlewareConfig).Wait();
})); }));
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
@ -146,6 +182,26 @@ namespace Ocelot.AcceptanceTests
} }
} }
public void GivenIHaveAnOcelotToken(string adminPath)
{
var tokenUrl = $"{adminPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("username", "admin"),
new KeyValuePair<string, string>("password", "admin"),
new KeyValuePair<string, string>("grant_type", "password")
};
var content = new FormUrlEncodedContent(formData);
var response = _ocelotClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
}
public void VerifyIdentiryServerStarted(string url) public void VerifyIdentiryServerStarted(string url)
{ {
using (var httpClient = new HttpClient()) using (var httpClient = new HttpClient())
@ -155,7 +211,6 @@ namespace Ocelot.AcceptanceTests
} }
} }
public void WhenIGetUrlOnTheApiGateway(string url) public void WhenIGetUrlOnTheApiGateway(string url)
{ {
_response = _ocelotClient.GetAsync(url).Result; _response = _ocelotClient.GetAsync(url).Result;

View File

@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using CacheManager.Core;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.DependencyInjection;
using Ocelot.ManualTest;
using Ocelot.Middleware;
using Shouldly;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.AcceptanceTests
{
public class Steps : IDisposable
{
private TestServer _ocelotServer;
private HttpClient _ocelotClient;
private HttpResponseMessage _response;
private HttpContent _postContent;
private BearerToken _token;
public HttpClient OcelotClient => _ocelotClient;
public string RequestIdKey = "OcRequestId";
private readonly Random _random;
private IWebHostBuilder _webHostBuilder;
public Steps()
{
_random = new Random();
}
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{
var configurationPath = TestConfiguration.ConfigurationPath;
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
}
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath)
{
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
}
/// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary>
public void GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
});
_ocelotServer = new TestServer(_webHostBuilder
.UseStartup<Startup>());
_ocelotClient = _ocelotServer.CreateClient();
}
internal void ThenTheResponseShouldBe(FileConfiguration expected)
{
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider);
for(var i = 0; i < response.ReRoutes.Count; i++)
{
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort);
response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme);
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate);
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod);
}
}
/// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary>
public void GivenOcelotIsRunning(OcelotMiddlewareConfiguration ocelotMiddlewareConfig)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
var configuration = builder.Build();
_webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
});
_ocelotServer = new TestServer(_webHostBuilder
.UseConfiguration(configuration)
.ConfigureServices(s =>
{
Action<ConfigurationBuilderCachePart> settings = (x) =>
{
x.WithMicrosoftLogging(log =>
{
log.AddConsole(LogLevel.Debug);
})
.WithDictionaryHandle();
};
<<<<<<< HEAD
=======
>>>>>>> develop
s.AddOcelotOutputCaching(settings);
s.AddOcelot(configuration);
})
.ConfigureLogging(l =>
{
l.AddConsole(configuration.GetSection("Logging"));
l.AddDebug();
})
.Configure(a =>
{
a.UseOcelot(ocelotMiddlewareConfig).Wait();
}));
_ocelotClient = _ocelotServer.CreateClient();
}
public void GivenIHaveAddedATokenToMyRequest()
{
_ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
}
public void GivenIHaveAToken(string url)
{
var tokenUrl = $"{url}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "client"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "api"),
new KeyValuePair<string, string>("username", "test"),
new KeyValuePair<string, string>("password", "test"),
new KeyValuePair<string, string>("grant_type", "password")
};
var content = new FormUrlEncodedContent(formData);
using (var httpClient = new HttpClient())
{
var response = httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
}
}
public void GivenIHaveAnOcelotToken(string adminPath)
{
var tokenUrl = $"{adminPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("username", "admin"),
new KeyValuePair<string, string>("password", "admin"),
new KeyValuePair<string, string>("grant_type", "password")
};
var content = new FormUrlEncodedContent(formData);
var response = _ocelotClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
}
public void VerifyIdentiryServerStarted(string url)
{
using (var httpClient = new HttpClient())
{
var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result;
response.EnsureSuccessStatusCode();
}
}
public void WhenIGetUrlOnTheApiGateway(string url)
{
_response = _ocelotClient.GetAsync(url).Result;
}
public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times)
{
var tasks = new Task[times];
for (int i = 0; i < times; i++)
{
var urlCopy = url;
tasks[i] = GetForServiceDiscoveryTest(urlCopy);
Thread.Sleep(_random.Next(40,60));
}
Task.WaitAll(tasks);
}
private async Task GetForServiceDiscoveryTest(string url)
{
var response = await _ocelotClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
int count = int.Parse(content);
count.ShouldBeGreaterThan(0);
}
public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times)
{
for (int i = 0; i < times; i++)
{
var clientId = "ocelotclient1";
var request = new HttpRequestMessage(new HttpMethod("GET"), url);
request.Headers.Add("ClientId", clientId);
_response = _ocelotClient.SendAsync(request).Result;
}
}
public void WhenIGetUrlOnTheApiGateway(string url, string requestId)
{
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId);
_response = _ocelotClient.GetAsync(url).Result;
}
public void WhenIPostUrlOnTheApiGateway(string url)
{
_response = _ocelotClient.PostAsync(url, _postContent).Result;
}
public void GivenThePostHasContent(string postcontent)
{
_postContent = new StringContent(postcontent);
}
public void ThenTheResponseBodyShouldBe(string expectedBody)
{
_response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody);
}
public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
{
_response.StatusCode.ShouldBe(expectedHttpStatusCode);
}
public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode)
{
var responseStatusCode = (int)_response.StatusCode;
responseStatusCode.ShouldBe(expectedHttpStatusCode);
}
public void Dispose()
{
_ocelotClient?.Dispose();
_ocelotServer?.Dispose();
}
public void ThenTheRequestIdIsReturned()
{
_response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty();
}
public void ThenTheRequestIdIsReturned(string expected)
{
_response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected);
}
}
}

View File

@ -0,0 +1,17 @@
using System;
<<<<<<< HEAD
=======
using System.IO;
>>>>>>> develop
namespace Ocelot.AcceptanceTests
{
public static class TestConfiguration
{
<<<<<<< HEAD
public static string ConfigurationPath => $"{AppContext.BaseDirectory}/configuration.json";
=======
public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json");
>>>>>>> develop
}
}

View File

@ -1 +1 @@
{"ReRoutes":[{"DownstreamPathTemplate":"41879/","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":41879,"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},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} {"ReRoutes":[{"DownstreamPathTemplate":"41879/","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":41879,"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,59 @@
{
"version": "0.0.0-dev",
"buildOptions": {
"copyToOutput": {
"include": [
"configuration.json"
]
}
},
"testRunner": "xunit",
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
"Ocelot": "0.0.0-dev",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Ocelot.ManualTest": "0.0.0-dev",
"Microsoft.AspNetCore.TestHost": "1.1.0",
"IdentityServer4": "1.0.1",
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel.Https": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"Shouldly": "2.8.2",
"TestStack.BDDfy": "4.3.2",
"Consul": "0.7.2.1",
"Microsoft.Extensions.Caching.Memory": "1.1.0",
"xunit": "2.2.0-rc1-build3507"
},
"runtimes": {
<<<<<<< HEAD
"win10-x64": {},
"osx.10.11-x64": {},
"osx.10.12-x64": {},
"win7-x64": {}
=======
"osx.10.11-x64":{},
"osx.10.12-x64":{},
"win7-x64": {},
"win10-x64": {}
>>>>>>> develop
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
]
}
}
}

View File

@ -0,0 +1,28 @@
{
"version": "0.0.0-dev",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Ocelot": "0.0.0-dev",
"BenchmarkDotNet": "0.10.2"
},
"runtimes": {
"osx.10.11-x64":{},
<<<<<<< HEAD
"osx.10.12-x64": {},
"win7-x64": {}
=======
"osx.10.12-x64":{},
"win7-x64": {},
"win10-x64": {}
>>>>>>> develop
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
]
}
}
}

View File

@ -0,0 +1,308 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
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;
namespace Ocelot.IntegrationTests
{
public class AdministrationTests : IDisposable
{
private readonly HttpClient _httpClient;
private HttpResponseMessage _response;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private BearerToken _token;
public AdministrationTests()
{
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5000";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
}
[Fact]
public void should_return_response_401_with_call_re_routes_controller()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "/administration"
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized))
.BDDfy();
}
[Fact]
public void should_return_response_200_with_call_re_routes_controller()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "/administration"
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_file_configuration()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "/administration",
RequestIdKey = "RequestId",
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "127.0.0.1",
Provider = "test"
}
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = "get",
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = "get",
UpstreamPathTemplate = "/test"
}
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenTheResponseShouldBe(configuration))
.BDDfy();
}
[Fact]
public void should_get_file_configuration_edit_and_post_updated_version()
{
var initialConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "/administration"
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = "get",
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = "get",
UpstreamPathTemplate = "/test"
}
}
};
var updatedConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "/administration"
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "127.0.0.1",
DownstreamPort = 80,
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = "get",
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "123.123.123",
DownstreamPort = 443,
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = "post",
UpstreamPathTemplate = "/test"
}
}
};
this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.BDDfy();
}
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
{
var json = JsonConvert.SerializeObject(updatedConfiguration);
var content = new StringContent(json);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
_response = _httpClient.PostAsync(url, content).Result;
}
private void ThenTheResponseShouldBe(FileConfiguration expected)
{
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider);
for (var i = 0; i < response.ReRoutes.Count; i++)
{
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort);
response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme);
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate);
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod);
}
}
private void GivenIHaveAddedATokenToMyRequest()
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
}
private void GivenIHaveAnOcelotToken(string adminPath)
{
var tokenUrl = $"{adminPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("username", "admin"),
new KeyValuePair<string, string>("password", "secret"),
new KeyValuePair<string, string>("grant_type", "password")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
}
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);
}
private void WhenIGetUrlOnTheApiGateway(string url)
{
_response = _httpClient.GetAsync(url).Result;
}
private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
{
_response.StatusCode.ShouldBe(expectedHttpStatusCode);
}
public void Dispose()
{
_builder?.Dispose();
_httpClient?.Dispose();
}
}
}

View File

@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace Ocelot.IntegrationTests
{
class BearerToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>d4575572-99ca-4530-8737-c296eda326f8</ProjectGuid>
<RootNamespace>Ocelot.IntegrationTests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Ocelot.IntegrationTests")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")]

View File

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@ -0,0 +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}}}

View File

@ -0,0 +1,51 @@
{
"version": "0.0.0-dev",
"buildOptions": {
"copyToOutput": {
"include": [
"configuration.json",
"appsettings.json"
]
}
},
"testRunner": "xunit",
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
"Ocelot": "0.0.0-dev",
"xunit": "2.2.0-beta2-build3300",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Ocelot.ManualTest": "0.0.0-dev",
"Microsoft.AspNetCore.TestHost": "1.1.0",
"IdentityServer4": "1.0.1",
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"Shouldly": "2.8.2",
"TestStack.BDDfy": "4.3.2",
"Consul": "0.7.2.1"
},
"runtimes": {
"win10-x64": {},
"osx.10.11-x64": {},
"osx.10.12-x64": {},
"win7-x64": {}
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
]
}
}
}

View File

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Ocelot.ManualTest namespace Ocelot.ManualTest
{ {
@ -7,11 +8,17 @@ namespace Ocelot.ManualTest
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var host = new WebHostBuilder() IWebHostBuilder builder = new WebHostBuilder();
.UseKestrel()
builder.ConfigureServices(s => {
s.AddSingleton(builder);
});
builder.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>() .UseStartup<Startup>();
.Build();
var host = builder.Build();
host.Run(); host.Run();
} }

View File

@ -38,15 +38,14 @@ namespace Ocelot.ManualTest
.WithDictionaryHandle(); .WithDictionaryHandle();
}; };
services.AddOcelotOutputCaching(settings); services.AddOcelotOutputCaching(settings);
services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(Configuration);
services.AddOcelot();
} }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddConsole(Configuration.GetSection("Logging"));
app.UseOcelot(); await app.UseOcelot();
} }
} }
} }

View File

@ -300,6 +300,7 @@
], ],
"GlobalConfiguration": { "GlobalConfiguration": {
"RequestIdKey": "OcRequestId" "RequestIdKey": "OcRequestId",
"AdministrationPath": "/admin"
} }
} }

View File

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

View File

@ -12,7 +12,10 @@
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Ocelot": "0.0.0-dev", "Ocelot": "0.0.0-dev",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0" "Microsoft.NETCore.App": "1.1.0",
"Consul": "0.7.2.1",
"Polly": "5.0.3",
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
}, },
"tools": { "tools": {

View File

@ -0,0 +1,72 @@
{
"version": "0.0.0-dev",
"dependencies": {
"Microsoft.AspNetCore.Http": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Ocelot": "0.0.0-dev",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"Consul": "0.7.2.1",
"Polly": "5.0.3",
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"runtimes": {
"osx.10.11-x64":{},
<<<<<<< HEAD
"osx.10.12-x64": {},
"win7-x64": {}
=======
"osx.10.12-x64":{},
"win7-x64": {},
"win10-x64": {}
>>>>>>> develop
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true,
"copyToOutput": {
"include": [
"configuration.json"
]
}
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"Areas/**/Views",
"appsettings.json",
"web.config",
"configuration.json"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

View File

@ -1,112 +1,62 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.File;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository;
using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Newtonsoft.Json;
using System.IO;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository;
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
public class FileConfigurationProviderTests public class FileConfigurationProviderTests
{ {
private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; private readonly IFileConfigurationProvider _provider;
private readonly Mock<IOcelotConfigurationRepository> _configurationRepository; private Mock<IFileConfigurationRepository> _repo;
private readonly Mock<IOcelotConfigurationCreator> _creator; private FileConfiguration _result;
private Response<IOcelotConfiguration> _result; private FileConfiguration _fileConfiguration;
public FileConfigurationProviderTests() public FileConfigurationProviderTests()
{ {
_creator = new Mock<IOcelotConfigurationCreator>(); _repo = new Mock<IFileConfigurationRepository>();
_configurationRepository = new Mock<IOcelotConfigurationRepository>(); _provider = new FileConfigurationProvider(_repo.Object);
_ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object, _creator.Object);
} }
[Fact] [Fact]
public void should_get_config() public void should_return_file_configuration()
{ {
this.Given(x => x.GivenTheRepoReturns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(new List<ReRoute>())))) var config = new FileConfiguration();
.When(x => x.WhenIGetTheConfig())
.Then(x => x.TheFollowingIsReturned(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(new List<ReRoute>())))) this.Given(x => x.GivenTheConfigurationIs(config))
.When(x => x.WhenIGetTheReRoutes())
.Then(x => x.ThenTheRepoIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_create_config_if_it_doesnt_exist()
{
this.Given(x => x.GivenTheRepoReturns(new OkResponse<IOcelotConfiguration>(null)))
.And(x => x.GivenTheCreatorReturns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(new List<ReRoute>()))))
.When(x => x.WhenIGetTheConfig())
.Then(x => x.TheFollowingIsReturned(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(new List<ReRoute>()))))
.BDDfy();
}
[Fact]
public void should_return_error()
{
this.Given(x => x.GivenTheRepoReturns(new ErrorResponse<IOcelotConfiguration>(new List<Error>
{
new AnyError()
})))
.When(x => x.WhenIGetTheConfig())
.Then(x => x.TheFollowingIsReturned(
new ErrorResponse<IOcelotConfiguration>(new List<Error>
{
new AnyError()
})))
.BDDfy();
}
[Fact] private void GivenTheConfigurationIs(FileConfiguration fileConfiguration)
public void should_return_error_if_creator_errors()
{ {
this.Given(x => x.GivenTheRepoReturns(new OkResponse<IOcelotConfiguration>(null))) _fileConfiguration = fileConfiguration;
.And(x => x.GivenTheCreatorReturns(new ErrorResponse<IOcelotConfiguration>(new List<Error> _repo
{
new AnyError()
})))
.When(x => x.WhenIGetTheConfig())
.Then(x => x.TheFollowingIsReturned(new ErrorResponse<IOcelotConfiguration>(new List<Error>
{
new AnyError()
})))
.BDDfy();
}
private void GivenTheCreatorReturns(Response<IOcelotConfiguration> config)
{
_creator
.Setup(x => x.Create())
.ReturnsAsync(config);
}
private void GivenTheRepoReturns(Response<IOcelotConfiguration> config)
{
_configurationRepository
.Setup(x => x.Get()) .Setup(x => x.Get())
.Returns(config); .Returns(new OkResponse<FileConfiguration>(fileConfiguration));
} }
private void WhenIGetTheConfig() private void WhenIGetTheReRoutes()
{ {
_result = _ocelotConfigurationProvider.Get().Result; _result = _provider.Get().Data;
} }
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected) private void ThenTheRepoIsCalledCorrectly()
{ {
_result.IsError.ShouldBe(expected.IsError); _repo
} .Verify(x => x.Get(), Times.Once);
class AnyError : Error
{
public AnyError()
: base("blamo", OcelotErrorCode.UnknownError)
{
}
} }
} }
} }

View File

@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.File;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Newtonsoft.Json;
using System.IO;
using Ocelot.Configuration.Repository;
namespace Ocelot.UnitTests.Configuration
{
public class FileConfigurationRepositoryTests
{
private readonly IFileConfigurationRepository _repo;
private FileConfiguration _result;
private FileConfiguration _fileConfiguration;
public FileConfigurationRepositoryTests()
{
_repo = new FileConfigurationRepository();
}
[Fact]
public void should_return_file_configuration()
{
var reRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/test/test/{test}"
}
};
var globalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "testy",
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Provider = "consul",
Port = 198,
Host = "blah"
}
};
var config = new FileConfiguration();
config.GlobalConfiguration = globalConfiguration;
config.ReRoutes.AddRange(reRoutes);
this.Given(x => x.GivenTheConfigurationIs(config))
.When(x => x.WhenIGetTheReRoutes())
.Then(x => x.ThenTheFollowingIsReturned(config))
.BDDfy();
}
[Fact]
public void should_set_file_configuration()
{
var reRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "123.12.12.12",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/asdfs/test/{test}"
}
};
var globalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "asdas",
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Provider = "consul",
Port = 198,
Host = "blah"
}
};
var config = new FileConfiguration();
config.GlobalConfiguration = globalConfiguration;
config.ReRoutes.AddRange(reRoutes);
this.Given(x => GivenIHaveAConfiguration(config))
.When(x => WhenISetTheConfiguration())
.Then(x => ThenTheConfigurationIsStoredAs(config))
.BDDfy();
}
private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration)
{
_fileConfiguration = fileConfiguration;
}
private void WhenISetTheConfiguration()
{
_repo.Set(_fileConfiguration);
_result = _repo.Get().Data;
}
private void ThenTheConfigurationIsStoredAs(FileConfiguration expected)
{
_result.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
_result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
_result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
_result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
_result.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider);
for(var i = 0; i < _result.ReRoutes.Count; i++)
{
_result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
_result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
_result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort);
_result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme);
}
}
private void GivenTheConfigurationIs(FileConfiguration fileConfiguration)
{
var configurationPath = $"{AppContext.BaseDirectory}/configuration.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
}
private void WhenIGetTheReRoutes()
{
_result = _repo.Get().Data;
}
private void ThenTheFollowingIsReturned(FileConfiguration expected)
{
_result.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
_result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
_result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
_result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
_result.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider);
for(var i = 0; i < _result.ReRoutes.Count; i++)
{
_result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
_result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
_result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort);
_result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme);
}
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter;
using Ocelot.Errors;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class FileConfigurationSetterTests
{
private FileConfiguration _fileConfiguration;
private FileConfigurationSetter _configSetter;
private Mock<IOcelotConfigurationRepository> _configRepo;
private Mock<IOcelotConfigurationCreator> _configCreator;
private Response<IOcelotConfiguration> _configuration;
private object _result;
private Mock<IFileConfigurationRepository> _repo;
public FileConfigurationSetterTests()
{
_repo = new Mock<IFileConfigurationRepository>();
_configRepo = new Mock<IOcelotConfigurationRepository>();
_configCreator = new Mock<IOcelotConfigurationCreator>();
_configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object);
}
[Fact]
public void should_set_configuration()
{
var fileConfig = new FileConfiguration();
var config = new OcelotConfiguration(new List<ReRoute>(), string.Empty);
this.Given(x => GivenTheFollowingConfiguration(fileConfig))
.And(x => GivenTheRepoReturns(new OkResponse()))
.And(x => GivenTheCreatorReturns(new OkResponse<IOcelotConfiguration>(config)))
.When(x => WhenISetTheConfiguration())
.Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_error_if_unable_to_set_file_configuration()
{
var fileConfig = new FileConfiguration();
this.Given(x => GivenTheFollowingConfiguration(fileConfig))
.And(x => GivenTheRepoReturns(new ErrorResponse(It.IsAny<Error>())))
.When(x => WhenISetTheConfiguration())
.And(x => ThenAnErrorResponseIsReturned())
.BDDfy();
}
[Fact]
public void should_return_error_if_unable_to_set_ocelot_configuration()
{
var fileConfig = new FileConfiguration();
this.Given(x => GivenTheFollowingConfiguration(fileConfig))
.And(x => GivenTheRepoReturns(new OkResponse()))
.And(x => GivenTheCreatorReturns(new ErrorResponse<IOcelotConfiguration>(It.IsAny<Error>())))
.When(x => WhenISetTheConfiguration())
.And(x => ThenAnErrorResponseIsReturned())
.BDDfy();
}
private void GivenTheRepoReturns(Response response)
{
_repo
.Setup(x => x.Set(It.IsAny<FileConfiguration>()))
.Returns(response);
}
private void ThenAnErrorResponseIsReturned()
{
_result.ShouldBeOfType<ErrorResponse>();
}
private void GivenTheCreatorReturns(Response<IOcelotConfiguration> configuration)
{
_configuration = configuration;
_configCreator
.Setup(x => x.Create(_fileConfiguration))
.ReturnsAsync(_configuration);
}
private void GivenTheFollowingConfiguration(FileConfiguration fileConfiguration)
{
_fileConfiguration = fileConfiguration;
}
private void WhenISetTheConfiguration()
{
_result = _configSetter.Set(_fileConfiguration).Result;
}
private void ThenTheConfigurationRepositoryIsCalledCorrectly()
{
_configRepo
.Verify(x => x.AddOrReplace(_configuration.Data), Times.Once);
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class HashCreationTests
{
[Fact]
public void should_create_hash_and_salt()
{
var password = "secret";
var salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
var storedSalt = Convert.ToBase64String(salt);
var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 10000,
numBytesRequested: 256 / 8));
}
}
}

View File

@ -0,0 +1,76 @@
using Ocelot.Configuration.Authentication;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class HashMatcherTests
{
private string _password;
private string _hash;
private string _salt;
private bool _result;
private HashMatcher _hashMatcher;
public HashMatcherTests()
{
_hashMatcher = new HashMatcher();
}
[Fact]
public void should_match_hash()
{
var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=";
var salt = "zzWITpnDximUNKYLiUam/w==";
var password = "secret";
this.Given(x => GivenThePassword(password))
.And(x => GivenTheHash(hash))
.And(x => GivenTheSalt(salt))
.When(x => WhenIMatch())
.Then(x => ThenTheResultIs(true))
.BDDfy();
}
[Fact]
public void should_not_match_hash()
{
var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=";
var salt = "zzWITpnDximUNKYLiUam/w==";
var password = "secret1";
this.Given(x => GivenThePassword(password))
.And(x => GivenTheHash(hash))
.And(x => GivenTheSalt(salt))
.When(x => WhenIMatch())
.Then(x => ThenTheResultIs(false))
.BDDfy();
}
private void GivenThePassword(string password)
{
_password = password;
}
private void GivenTheHash(string hash)
{
_hash = hash;
}
private void GivenTheSalt(string salt)
{
_salt = salt;
}
private void WhenIMatch()
{
_result = _hashMatcher.Match(_password, _salt, _hash);
}
private void ThenTheResultIs(bool expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -27,7 +27,7 @@ namespace Ocelot.UnitTests.Configuration
[Fact] [Fact]
public void can_add_config() public void can_add_config()
{ {
this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial"))) this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath")))
.When(x => x.WhenIAddOrReplaceTheConfig()) .When(x => x.WhenIAddOrReplaceTheConfig())
.Then(x => x.ThenNoErrorsAreReturned()) .Then(x => x.ThenNoErrorsAreReturned())
.BDDfy(); .BDDfy();
@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.Configuration
private void GivenThereIsASavedConfiguration() private void GivenThereIsASavedConfiguration()
{ {
GivenTheConfigurationIs(new FakeConfig("initial")); GivenTheConfigurationIs(new FakeConfig("initial", "adminath"));
WhenIAddOrReplaceTheConfig(); WhenIAddOrReplaceTheConfig();
} }
@ -77,9 +77,10 @@ namespace Ocelot.UnitTests.Configuration
{ {
private readonly string _downstreamTemplatePath; private readonly string _downstreamTemplatePath;
public FakeConfig(string downstreamTemplatePath) public FakeConfig(string downstreamTemplatePath, string administrationPath)
{ {
_downstreamTemplatePath = downstreamTemplatePath; _downstreamTemplatePath = downstreamTemplatePath;
AdministrationPath = administrationPath;
} }
public List<ReRoute> ReRoutes => new List<ReRoute> public List<ReRoute> ReRoutes => new List<ReRoute>
@ -89,6 +90,8 @@ namespace Ocelot.UnitTests.Configuration
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build() .Build()
}; };
public string AdministrationPath {get;}
} }
} }
} }

View File

@ -0,0 +1,77 @@
using System.Collections.Generic;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository;
using Ocelot.Errors;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class OcelotConfigurationProviderTests
{
private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider;
private readonly Mock<IOcelotConfigurationRepository> _configurationRepository;
private Response<IOcelotConfiguration> _result;
public OcelotConfigurationProviderTests()
{
_configurationRepository = new Mock<IOcelotConfigurationRepository>();
_ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object);
}
[Fact]
public void should_get_config()
{
this.Given(x => x.GivenTheRepoReturns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(new List<ReRoute>(), string.Empty))))
.When(x => x.WhenIGetTheConfig())
.Then(x => x.TheFollowingIsReturned(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(new List<ReRoute>(), string.Empty))))
.BDDfy();
}
[Fact]
public void should_return_error()
{
this.Given(x => x.GivenTheRepoReturns(new ErrorResponse<IOcelotConfiguration>(new List<Error>
{
new AnyError()
})))
.When(x => x.WhenIGetTheConfig())
.Then(x => x.TheFollowingIsReturned(
new ErrorResponse<IOcelotConfiguration>(new List<Error>
{
new AnyError()
})))
.BDDfy();
}
private void GivenTheRepoReturns(Response<IOcelotConfiguration> config)
{
_configurationRepository
.Setup(x => x.Get())
.Returns(config);
}
private void WhenIGetTheConfig()
{
_result = _ocelotConfigurationProvider.Get();
}
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)
{
_result.IsError.ShouldBe(expected.IsError);
}
class AnyError : Error
{
public AnyError()
: base("blamo", OcelotErrorCode.UnknownError)
{
}
}
}
}

View File

@ -0,0 +1,117 @@
using Ocelot.Configuration.Authentication;
using Xunit;
using Shouldly;
using TestStack.BDDfy;
using Moq;
using IdentityServer4.Validation;
using Ocelot.Configuration.Provider;
using System.Collections.Generic;
namespace Ocelot.UnitTests.Configuration
{
public class OcelotResourceOwnerPasswordValidatorTests
{
private OcelotResourceOwnerPasswordValidator _validator;
private Mock<IHashMatcher> _matcher;
private string _userName;
private string _password;
private ResourceOwnerPasswordValidationContext _context;
private Mock<IIdentityServerConfiguration> _config;
private User _user;
public OcelotResourceOwnerPasswordValidatorTests()
{
_matcher = new Mock<IHashMatcher>();
_config = new Mock<IIdentityServerConfiguration>();
_validator = new OcelotResourceOwnerPasswordValidator(_matcher.Object, _config.Object);
}
[Fact]
public void should_return_success()
{
this.Given(x => GivenTheUserName("tom"))
.And(x => GivenThePassword("password"))
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
.And(x => GivenTheMatcherReturns(true))
.When(x => WhenIValidate())
.Then(x => ThenTheUserIsValidated())
.And(x => ThenTheMatcherIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_fail_when_no_user()
{
this.Given(x => GivenTheUserName("bob"))
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
.And(x => GivenTheMatcherReturns(true))
.When(x => WhenIValidate())
.Then(x => ThenTheUserIsNotValidated())
.BDDfy();
}
[Fact]
public void should_return_fail_when_password_doesnt_match()
{
this.Given(x => GivenTheUserName("tom"))
.And(x => GivenThePassword("password"))
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
.And(x => GivenTheMatcherReturns(false))
.When(x => WhenIValidate())
.Then(x => ThenTheUserIsNotValidated())
.And(x => ThenTheMatcherIsCalledCorrectly())
.BDDfy();
}
private void ThenTheMatcherIsCalledCorrectly()
{
_matcher
.Verify(x => x.Match(_password, _user.Salt, _user.Hash), Times.Once);
}
private void GivenThePassword(string password)
{
_password = password;
}
private void GivenTheUserIs(User user)
{
_user = user;
_config
.Setup(x => x.Users)
.Returns(new List<User>{_user});
}
private void GivenTheMatcherReturns(bool expected)
{
_matcher
.Setup(x => x.Match(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(expected);
}
private void GivenTheUserName(string userName)
{
_userName = userName;
}
private void WhenIValidate()
{
_context = new ResourceOwnerPasswordValidationContext
{
UserName = _userName,
Password = _password
};
_validator.ValidateAsync(_context).Wait();
}
private void ThenTheUserIsValidated()
{
_context.Result.IsError.ShouldBe(false);
}
private void ThenTheUserIsNotValidated()
{
_context.Result.IsError.ShouldBe(true);
}
}
}

View File

@ -0,0 +1,133 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Setter;
using Ocelot.Controllers;
using Ocelot.Errors;
using Ocelot.Responses;
using TestStack.BDDfy;
using Xunit;
using Shouldly;
using Ocelot.Configuration.Provider;
namespace Ocelot.UnitTests.Controllers
{
public class FileConfigurationControllerTests
{
private FileConfigurationController _controller;
private Mock<IFileConfigurationProvider> _configGetter;
private Mock<IFileConfigurationSetter> _configSetter;
private IActionResult _result;
private FileConfiguration _fileConfiguration;
public FileConfigurationControllerTests()
{
_configGetter = new Mock<IFileConfigurationProvider>();
_configSetter = new Mock<IFileConfigurationSetter>();
_controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object);
}
[Fact]
public void should_get_file_configuration()
{
var expected = new OkResponse<FileConfiguration>(new FileConfiguration());
this.Given(x => x.GivenTheGetConfigurationReturns(expected))
.When(x => x.WhenIGetTheFileConfiguration())
.Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_error_when_cannot_get_config()
{
var expected = new ErrorResponse<FileConfiguration>(It.IsAny<Error>());
this.Given(x => x.GivenTheGetConfigurationReturns(expected))
.When(x => x.WhenIGetTheFileConfiguration())
.Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly())
.And(x => x.ThenTheResponseIs<BadRequestObjectResult>())
.BDDfy();
}
[Fact]
public void should_post_file_configuration()
{
var expected = new FileConfiguration();
this.Given(x => GivenTheFileConfiguration(expected))
.And(x => GivenTheConfigSetterReturnsAnError(new OkResponse()))
.When(x => WhenIPostTheFileConfiguration())
.Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_error_when_cannot_set_config()
{
var expected = new FileConfiguration();
this.Given(x => GivenTheFileConfiguration(expected))
.And(x => GivenTheConfigSetterReturnsAnError(new ErrorResponse(new FakeError())))
.When(x => WhenIPostTheFileConfiguration())
.Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly())
.And(x => ThenTheResponseIs<BadRequestObjectResult>())
.BDDfy();
}
private void GivenTheConfigSetterReturnsAnError(Response response)
{
_configSetter
.Setup(x => x.Set(It.IsAny<FileConfiguration>()))
.ReturnsAsync(response);
}
private void ThenTheConfigrationSetterIsCalledCorrectly()
{
_configSetter
.Verify(x => x.Set(_fileConfiguration), Times.Once);
}
private void WhenIPostTheFileConfiguration()
{
_result = _controller.Post(_fileConfiguration).Result;
}
private void GivenTheFileConfiguration(FileConfiguration fileConfiguration)
{
_fileConfiguration = fileConfiguration;
}
private void ThenTheResponseIs<T>()
{
_result.ShouldBeOfType<T>();
}
private void GivenTheGetConfigurationReturns(Response<FileConfiguration> fileConfiguration)
{
_configGetter
.Setup(x => x.Get())
.Returns(fileConfiguration);
}
private void WhenIGetTheFileConfiguration()
{
_result = _controller.Get();
}
private void TheTheGetFileConfigurationIsCalledCorrectly()
{
_configGetter
.Verify(x => x.Get(), Times.Once);
}
class FakeError : Error
{
public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError)
{
}
}
}
}

View File

@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_downstreamRouteFinder _downstreamRouteFinder
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>())) .Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(_downstreamRoute); .Returns(_downstreamRoute);
} }
public void Dispose() public void Dispose()

View File

@ -48,7 +48,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("someUpstreamPath") .WithUpstreamTemplatePattern("someUpstreamPath")
.Build() .Build()
})) }, string.Empty
))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder()) .When(x => x.WhenICallTheFinder())
@ -80,7 +81,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("someUpstreamPath") .WithUpstreamTemplatePattern("someUpstreamPath")
.Build() .Build()
} }, string.Empty
)) ))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
@ -118,7 +119,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithUpstreamHttpMethod("Post") .WithUpstreamHttpMethod("Post")
.WithUpstreamTemplatePattern("") .WithUpstreamTemplatePattern("")
.Build() .Build()
} }, string.Empty
)) ))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .And(x => x.GivenTheUpstreamHttpMethodIs("Post"))
@ -145,7 +146,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("somePath") .WithUpstreamTemplatePattern("somePath")
.Build(), .Build(),
} }, string.Empty
)) ))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(false)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(false))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
@ -193,12 +194,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.Returns(_match); .Returns(_match);
} }
private void GivenTheConfigurationIs(List<ReRoute> reRoutesConfig) private void GivenTheConfigurationIs(List<ReRoute> reRoutesConfig, string adminPath)
{ {
_reRoutesConfig = reRoutesConfig; _reRoutesConfig = reRoutesConfig;
_mockConfig _mockConfig
.Setup(x => x.Get()) .Setup(x => x.Get())
.ReturnsAsync(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig))); .Returns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig, adminPath)));
} }
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
@ -208,7 +209,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void WhenICallTheFinder() private void WhenICallTheFinder()
{ {
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result; _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod);
} }
private void ThenTheFollowingIsReturned(DownstreamRoute expected) private void ThenTheFollowingIsReturned(DownstreamRoute expected)

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Moq;
using Ocelot.Middleware;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Middleware
{
public class BaseUrlFinderTests
{
private readonly BaseUrlFinder _baseUrlFinder;
private readonly Mock<IWebHostBuilder> _webHostBuilder;
private string _result;
public BaseUrlFinderTests()
{
_webHostBuilder = new Mock<IWebHostBuilder>();
_baseUrlFinder = new BaseUrlFinder(_webHostBuilder.Object);
}
[Fact]
public void should_find_base_url_based_on_webhostbuilder()
{
this.Given(x => GivenTheWebHostBuilderReturns("http://localhost:7000"))
.When(x => WhenIFindTheUrl())
.Then(x => ThenTheUrlIs("http://localhost:7000"))
.BDDfy();
}
[Fact]
public void should_use_default_base_url()
{
this.Given(x => GivenTheWebHostBuilderReturns(""))
.When(x => WhenIFindTheUrl())
.Then(x => ThenTheUrlIs("http://localhost:5000"))
.BDDfy();
}
private void GivenTheWebHostBuilderReturns(string url)
{
_webHostBuilder
.Setup(x => x.GetSetting(WebHostDefaults.ServerUrlsKey))
.Returns(url);
}
private void WhenIFindTheUrl()
{
_result = _baseUrlFinder.Find();
}
private void ThenTheUrlIs(string expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -0,0 +1 @@
{"ReRoutes":[{"DownstreamPathTemplate":"/test/test/{test}","UpstreamPathTemplate":null,"UpstreamHttpMethod":null,"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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":"consul","Host":"blah","Port":198},"AdministrationPath":"testy"}}

View File

@ -3,29 +3,30 @@
"testRunner": "xunit", "testRunner": "xunit",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.TestHost": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0", "Microsoft.DotNet.InternalAbstractions": "1.0.0",
"Ocelot": "0.0.0-dev", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Moq": "4.6.38-alpha", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.AspNetCore.TestHost": "1.1.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.1.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.NETCore.App": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Shouldly": "2.8.2", "Microsoft.NETCore.App": "1.1.0",
"TestStack.BDDfy": "4.3.2", "Moq": "4.6.38-alpha",
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", "Ocelot": "0.0.0-dev",
"Microsoft.DotNet.InternalAbstractions": "1.0.0", "Shouldly": "2.8.2",
"xunit": "2.2.0-rc1-build3507" "TestStack.BDDfy": "4.3.2",
}, "xunit": "2.2.0-rc1-build3507"
},
"runtimes": { "runtimes": {
"osx.10.11-x64":{}, "osx.10.11-x64":{},
"osx.10.12-x64":{}, "osx.10.12-x64":{},

View File

@ -0,0 +1,50 @@
{
"version": "0.0.0-dev",
"testRunner": "xunit",
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"Ocelot": "0.0.0-dev",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Moq": "4.6.38-alpha",
"Microsoft.AspNetCore.TestHost": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"Shouldly": "2.8.2",
"TestStack.BDDfy": "4.3.2",
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
<<<<<<< HEAD
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
=======
"xunit": "2.2.0-rc1-build3507"
>>>>>>> develop
},
"runtimes": {
"osx.10.11-x64":{},
<<<<<<< HEAD
"osx.10.12-x64": {},
"win7-x64": {}
=======
"osx.10.12-x64":{},
"win7-x64": {},
"win10-x64": {}
>>>>>>> develop
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
]
}
}
}