mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 20:12:51 +08:00
Merge branch 'develop' of https://github.com/geffzhang/Ocelot into develop
This commit is contained in:
commit
4bd14f7537
7
.gitignore
vendored
7
.gitignore
vendored
@ -235,3 +235,10 @@ _Pvt_Extensions
|
|||||||
|
|
||||||
# FAKE - F# Make
|
# FAKE - F# Make
|
||||||
.fake/
|
.fake/
|
||||||
|
tools/
|
||||||
|
|
||||||
|
# MacOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Ocelot acceptance test config
|
||||||
|
test/Ocelot.AcceptanceTests/configuration.json
|
4
GitVersion.yml
Normal file
4
GitVersion.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mode: ContinuousDelivery
|
||||||
|
branches: {}
|
||||||
|
ignore:
|
||||||
|
sha: []
|
@ -1,44 +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.Mvc" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Server.IISIntegration" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Server.Kestrel" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Configuration.EnvironmentVariables" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Configuration.FileExtensions" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Configuration.Json" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Logging" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Logging.Console" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Logging.Debug" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.Extensions.Options.ConfigurationExtensions" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Http" version="1.0.0" />
|
|
||||||
<dependency id="System.Text.RegularExpressions" version="4.1.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.OAuth" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.JwtBearer" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.Cookies" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.Google" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.Facebook" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.Twitter" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication.MicrosoftAccount" version="1.0.0" />
|
|
||||||
<dependency id="Microsoft.AspNetCore.Authentication" version="1.0.0" />
|
|
||||||
<dependency id="IdentityServer4.AccessTokenValidation" version="1.0.1-rc2" />
|
|
||||||
</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>
|
|
28
Ocelot.sln
28
Ocelot.sln
@ -8,17 +8,24 @@ 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-run-tests.bat = build-and-run-tests.bat
|
build-and-run-tests.ps1 = build-and-run-tests.ps1
|
||||||
build.bat = build.bat
|
build.cake = build.cake
|
||||||
|
build.ps1 = build.ps1
|
||||||
|
build.readme.md = build.readme.md
|
||||||
configuration-explanation.txt = configuration-explanation.txt
|
configuration-explanation.txt = configuration-explanation.txt
|
||||||
|
configuration.yaml = configuration.yaml
|
||||||
|
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
|
||||||
push-to-nuget.bat = push-to-nuget.bat
|
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
run-benchmarks.bat = run-benchmarks.bat
|
release.ps1 = release.ps1
|
||||||
run-tests.bat = run-tests.bat
|
ReleaseNotes.md = ReleaseNotes.md
|
||||||
|
run-acceptance-tests.ps1 = run-acceptance-tests.ps1
|
||||||
|
run-benchmarks.ps1 = run-benchmarks.ps1
|
||||||
|
run-unit-tests.ps1 = run-unit-tests.ps1
|
||||||
|
version.ps1 = version.ps1
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
|
||||||
@ -33,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
|
||||||
@ -59,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
|
||||||
@ -69,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
|
||||||
|
266
README.md
266
README.md
@ -1,6 +1,6 @@
|
|||||||
# Ocelot
|
# Ocelot
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/TomPallister/ocelot)
|
[](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb)
|
||||||
|
|
||||||
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
@ -30,9 +30,6 @@ and retrived as the requests goes back up the Ocelot pipeline. There is a piece
|
|||||||
that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client.
|
that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client.
|
||||||
That is basically it with a bunch of other features.
|
That is basically it with a bunch of other features.
|
||||||
|
|
||||||
This is not ready for production yet as uses a lot of rc and beta .net core packages.
|
|
||||||
Hopefully by the start of 2017 it will be in use.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Pull requests, issues and commentary welcome! No special process just create a request and get in
|
Pull requests, issues and commentary welcome! No special process just create a request and get in
|
||||||
@ -41,13 +38,13 @@ touch either via gitter or create an issue.
|
|||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
Ocelot is designed to work with ASP.NET core only and is currently
|
Ocelot is designed to work with ASP.NET core only and is currently
|
||||||
built to netcoreapp1.4 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||||
|
|
||||||
Install Ocelot and it's dependecies using nuget. At the moment
|
Install Ocelot and it's dependecies using nuget. At the moment
|
||||||
all we have is the pre version. Once we have something working in
|
all we have is the pre version. Once we have something working in
|
||||||
a half decent way we will drop a version.
|
a half decent way we will drop a version.
|
||||||
|
|
||||||
`Install-Package Ocelot -Pre`
|
`Install-Package Ocelot`
|
||||||
|
|
||||||
All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
|
All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
|
||||||
|
|
||||||
@ -61,11 +58,12 @@ The ReRoutes are the objects that tell Ocelot how to treat an upstream request.
|
|||||||
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
|
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.
|
if you don't want to manage lots of ReRoute specific settings.
|
||||||
|
|
||||||
{
|
```json
|
||||||
|
{
|
||||||
"ReRoutes": [],
|
"ReRoutes": [],
|
||||||
"GlobalConfiguration": {}
|
"GlobalConfiguration": {}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
More information on how to use these options is below..
|
More information on how to use these options is below..
|
||||||
|
|
||||||
## Startup
|
## Startup
|
||||||
@ -73,8 +71,9 @@ More information on how to use these options is below..
|
|||||||
An example startup using a json file for configuration can be seen below.
|
An example startup using a json file for configuration can be seen below.
|
||||||
Currently this is the only way to get configuration into Ocelot.
|
Currently this is the only way to get configuration into Ocelot.
|
||||||
|
|
||||||
public class Startup
|
```csharp
|
||||||
{
|
public class Startup
|
||||||
|
{
|
||||||
public Startup(IHostingEnvironment env)
|
public Startup(IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
@ -101,18 +100,46 @@ Currently this is the only way to get configuration into Ocelot.
|
|||||||
};
|
};
|
||||||
|
|
||||||
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!
|
||||||
|
|
||||||
@ -122,50 +149,141 @@ Ocelot's primary functionality is to take incomeing http requests and forward th
|
|||||||
to a downstream service. At the moment in the form of another http request (in the future
|
to a downstream service. At the moment in the form of another http request (in the future
|
||||||
this could be any transport mechanism.).
|
this could be any transport mechanism.).
|
||||||
|
|
||||||
Ocelot always adds a trailing slash to an UpstreamTemplate.
|
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
|
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.
|
anything working in Ocelot you need to set up a ReRoute in the configuration.
|
||||||
|
|
||||||
{
|
```json
|
||||||
|
{
|
||||||
"ReRoutes": [
|
"ReRoutes": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
In order to set up a ReRoute you need to add one to the json array called ReRoutes like
|
In order to set up a ReRoute you need to add one to the json array called ReRoutes like
|
||||||
the following.
|
the following.
|
||||||
|
|
||||||
{
|
```json
|
||||||
"DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}",
|
{
|
||||||
"UpstreamTemplate": "/posts/{postId}",
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamPort": 80,
|
||||||
|
"DownstreamHost" "localhost"
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": "Put"
|
"UpstreamHttpMethod": "Put"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The DownstreamTemplate is the URL that this request will be forwarded to.
|
The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to.
|
||||||
The UpstreamTemplate is the URL that Ocelot will use to identity which
|
The UpstreamPathTemplate is the URL that Ocelot will use to identity which
|
||||||
DownstreamTemplate to use for a given request. Finally the UpstreamHttpMethod is used so
|
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 :)
|
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}.
|
In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
|
||||||
The placeholder needs to be in both the DownstreamTemplate and UpstreamTemplate. If it is
|
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
|
Ocelot will attempt to replace the placeholder with the correct variable value from the
|
||||||
Upstream URL when the request comes in.
|
Upstream URL when the request comes in.
|
||||||
|
|
||||||
At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive.
|
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.
|
In order to change this you can specify on a per ReRoute basis the following setting.
|
||||||
|
|
||||||
"ReRouteIsCaseSensitive": true
|
```json
|
||||||
|
"ReRouteIsCaseSensitive": true
|
||||||
|
```
|
||||||
|
|
||||||
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
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
|
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
|
||||||
|
|
||||||
|
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
|
## Authentication
|
||||||
|
|
||||||
Ocelot currently supports the use of bearer tokens with Identity Server (more providers to
|
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
|
come if required). In order to identity a ReRoute as authenticated it needs the following
|
||||||
configuration added.
|
configuration added.
|
||||||
|
|
||||||
"AuthenticationOptions": {
|
```json
|
||||||
|
"AuthenticationOptions": {
|
||||||
"Provider": "IdentityServer",
|
"Provider": "IdentityServer",
|
||||||
"ProviderRootUrl": "http://localhost:52888",
|
"ProviderRootUrl": "http://localhost:52888",
|
||||||
"ScopeName": "api",
|
"ScopeName": "api",
|
||||||
@ -174,7 +292,8 @@ configuration added.
|
|||||||
"offline_access"
|
"offline_access"
|
||||||
],
|
],
|
||||||
"ScopeSecret": "secret"
|
"ScopeSecret": "secret"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
In this example the Provider is specified as IdentityServer. This string is important
|
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
|
because it is used to identity the authentication provider (as previously mentioned in
|
||||||
@ -193,9 +312,11 @@ is 401 unauthorised.
|
|||||||
Ocelot supports claims based authorisation which is run post authentication. This means if
|
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.
|
you have a route you want to authorise you can add the following to you ReRoute configuration.
|
||||||
|
|
||||||
"RouteClaimsRequirement": {
|
```json
|
||||||
|
"RouteClaimsRequirement": {
|
||||||
"UserType": "registered"
|
"UserType": "registered"
|
||||||
},
|
},
|
||||||
|
```
|
||||||
|
|
||||||
In this example when the authorisation middleware is called Ocelot will check to see
|
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 the user has the claim type UserType and if the value of that claim is registered.
|
||||||
@ -234,10 +355,12 @@ and add whatever was at the index requested to the transform.
|
|||||||
|
|
||||||
Below is an example configuration that will transforms claims to claims
|
Below is an example configuration that will transforms claims to claims
|
||||||
|
|
||||||
|
```json
|
||||||
"AddClaimsToRequest": {
|
"AddClaimsToRequest": {
|
||||||
"UserType": "Claims[sub] > value[0] > |",
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
"UserId": "Claims[sub] > value[1] > |"
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
},
|
},
|
||||||
|
```
|
||||||
|
|
||||||
This shows a transforms where Ocelot looks at the users sub claim and transforms it into
|
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".
|
UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue".
|
||||||
@ -246,9 +369,11 @@ UserType and UserId claims. Assuming the sub looks like this "usertypevalue|user
|
|||||||
|
|
||||||
Below is an example configuration that will transforms claims to headers
|
Below is an example configuration that will transforms claims to headers
|
||||||
|
|
||||||
"AddHeadersToRequest": {
|
```json
|
||||||
|
"AddHeadersToRequest": {
|
||||||
"CustomerId": "Claims[sub] > value[1] > |"
|
"CustomerId": "Claims[sub] > value[1] > |"
|
||||||
},
|
},
|
||||||
|
```
|
||||||
|
|
||||||
This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a
|
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".
|
CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue".
|
||||||
@ -257,26 +382,36 @@ CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue".
|
|||||||
|
|
||||||
Below is an example configuration that will transforms claims to query string parameters
|
Below is an example configuration that will transforms claims to query string parameters
|
||||||
|
|
||||||
"AddQueriesToRequest": {
|
```json
|
||||||
|
"AddQueriesToRequest": {
|
||||||
"LocationId": "Claims[LocationId] > value",
|
"LocationId": "Claims[LocationId] > value",
|
||||||
},
|
},
|
||||||
|
```
|
||||||
|
|
||||||
This shows a transform where Ocelot looks at the users LocationId claim and add its as
|
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.
|
a query string parameter to be forwarded onto the downstream service.
|
||||||
|
|
||||||
## Logging
|
## Quality of Service
|
||||||
|
|
||||||
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
|
Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
|
||||||
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
|
want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
|
||||||
for the standard asp.net core logging stuff at the moment.
|
.NET library called Polly check them out [here](https://github.com/App-vNext/Polly).
|
||||||
|
|
||||||
There are a bunch of debugging logs in the ocelot middlewares however I think the
|
Add the following section to a ReRoute configuration.
|
||||||
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
|
```json
|
||||||
work out how to override the request id that get's logged when setting IncludeScopes
|
"QoSOptions": {
|
||||||
to true for logging settings. Nicely onto the next feature.
|
"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
|
## RequestId / CorrelationId
|
||||||
|
|
||||||
@ -291,14 +426,18 @@ have an OcelotRequestId.
|
|||||||
|
|
||||||
In order to use the requestid feature in your ReRoute configuration add this setting
|
In order to use the requestid feature in your ReRoute configuration add this setting
|
||||||
|
|
||||||
"RequestIdKey": "OcRequestId"
|
```json
|
||||||
|
"RequestIdKey": "OcRequestId"
|
||||||
|
```
|
||||||
|
|
||||||
In this example OcRequestId is the request header that contains the clients request id.
|
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
|
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.
|
set at ReRoute level for the request id. The setting is as fllows.
|
||||||
|
|
||||||
"RequestIdKey": "OcRequestId",
|
```json
|
||||||
|
"RequestIdKey": "OcRequestId",
|
||||||
|
```
|
||||||
|
|
||||||
It behaves in exactly the same way as the ReRoute level RequestIdKey settings.
|
It behaves in exactly the same way as the ReRoute level RequestIdKey settings.
|
||||||
|
|
||||||
@ -317,7 +456,9 @@ 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.
|
In orde to use caching on a route in your ReRoute configuration add this setting.
|
||||||
|
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 }
|
```json
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 }
|
||||||
|
```
|
||||||
|
|
||||||
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
||||||
|
|
||||||
@ -329,15 +470,17 @@ 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
|
When setting up Ocelot in your Startup.cs you can provide some additonal middleware
|
||||||
and override middleware. This is done as follos.
|
and override middleware. This is done as follos.
|
||||||
|
|
||||||
var configuration = new OcelotMiddlewareConfiguration
|
```csharp
|
||||||
{
|
var configuration = new OcelotMiddlewareConfiguration
|
||||||
|
{
|
||||||
PreErrorResponderMiddleware = async (ctx, next) =>
|
PreErrorResponderMiddleware = async (ctx, next) =>
|
||||||
{
|
{
|
||||||
await next.Invoke();
|
await next.Invoke();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.UseOcelot(configuration);
|
app.UseOcelot(configuration);
|
||||||
|
```
|
||||||
|
|
||||||
In the example above the provided function will run before the first piece of Ocelot middleware.
|
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 allows a user to supply any behaviours they want before and after the Ocelot pipeline has run.
|
||||||
@ -363,6 +506,20 @@ http request before it is passed to Ocelots request creator.
|
|||||||
Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added
|
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.
|
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
|
## Not supported
|
||||||
|
|
||||||
Ocelot does not support...
|
Ocelot does not support...
|
||||||
@ -381,8 +538,11 @@ 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.
|
||||||
|
|
||||||
|
[ 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
564
README.md.orig
Normal file
@ -0,0 +1,564 @@
|
|||||||
|
# Ocelot
|
||||||
|
|
||||||
|
[](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb)
|
||||||
|
|
||||||
|
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
[ 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
1
ReleaseNotes.md
Normal file
1
ReleaseNotes.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
No issues closed since last release
|
12
appveyor.yml
12
appveyor.yml
@ -1,12 +0,0 @@
|
|||||||
version: 1.0.{build}
|
|
||||||
configuration:
|
|
||||||
- Release
|
|
||||||
platform: Any CPU
|
|
||||||
build_script:
|
|
||||||
- build.bat
|
|
||||||
test_script:
|
|
||||||
- run-tests.bat
|
|
||||||
after_test:
|
|
||||||
- push-to-nuget.bat %appveyor_build_version%-rc1 %nugetApiKey%
|
|
||||||
cache:
|
|
||||||
- '%USERPROFILE%\.nuget\packages'
|
|
1
build-and-release-unstable.ps1
Normal file
1
build-and-release-unstable.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target BuildAndReleaseUnstable
|
@ -1,2 +0,0 @@
|
|||||||
./run-tests.bat
|
|
||||||
./build.bat
|
|
1
build-and-run-tests.ps1
Normal file
1
build-and-run-tests.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target RunTests
|
@ -1,8 +0,0 @@
|
|||||||
echo -------------------------
|
|
||||||
|
|
||||||
echo Building Ocelot
|
|
||||||
dotnet restore src/Ocelot
|
|
||||||
dotnet build src/Ocelot -c Release
|
|
||||||
|
|
||||||
|
|
||||||
|
|
388
build.cake
Normal file
388
build.cake
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
#tool "nuget:?package=GitVersion.CommandLine"
|
||||||
|
#tool "nuget:?package=OpenCover"
|
||||||
|
#tool "nuget:?package=ReportGenerator"
|
||||||
|
#tool "nuget:?package=GitReleaseNotes"
|
||||||
|
#addin "nuget:?package=Cake.DoInDirectory"
|
||||||
|
#addin "nuget:?package=Cake.Json"
|
||||||
|
|
||||||
|
// compile
|
||||||
|
var compileConfig = Argument("configuration", "Release");
|
||||||
|
var projectJson = "./src/Ocelot/project.json";
|
||||||
|
|
||||||
|
// build artifacts
|
||||||
|
var artifactsDir = Directory("artifacts");
|
||||||
|
|
||||||
|
// unit testing
|
||||||
|
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
|
||||||
|
var unitTestAssemblies = @"./test/Ocelot.UnitTests";
|
||||||
|
|
||||||
|
// acceptance testing
|
||||||
|
var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests");
|
||||||
|
|
||||||
|
// integration testing
|
||||||
|
var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests");
|
||||||
|
|
||||||
|
// benchmark testing
|
||||||
|
var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests");
|
||||||
|
var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks";
|
||||||
|
|
||||||
|
// packaging
|
||||||
|
var packagesDir = artifactsDir + Directory("Packages");
|
||||||
|
var releaseNotesFile = packagesDir + File("releasenotes.md");
|
||||||
|
var artifactsFile = packagesDir + File("artifacts.txt");
|
||||||
|
|
||||||
|
// unstable releases
|
||||||
|
var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable");
|
||||||
|
var nugetFeedUnstableUploadUrl = "https://www.nuget.org/api/v2/package";
|
||||||
|
var nugetFeedUnstableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
|
||||||
|
|
||||||
|
// stable releases
|
||||||
|
var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/";
|
||||||
|
var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable");
|
||||||
|
var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package";
|
||||||
|
var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
|
||||||
|
|
||||||
|
// internal build variables - don't change these.
|
||||||
|
var releaseTag = "";
|
||||||
|
string committedVersion = "0.0.0-dev";
|
||||||
|
var buildVersion = committedVersion;
|
||||||
|
GitVersion versioning = null;
|
||||||
|
var nugetFeedUnstableBranchFilter = "^(develop)$|^(PullRequest/)";
|
||||||
|
|
||||||
|
var target = Argument("target", "Default");
|
||||||
|
|
||||||
|
|
||||||
|
Information("target is " +target);
|
||||||
|
Information("Build configuration is " + compileConfig);
|
||||||
|
|
||||||
|
Task("Default")
|
||||||
|
.IsDependentOn("Build");
|
||||||
|
|
||||||
|
Task("Build")
|
||||||
|
.IsDependentOn("RunTests")
|
||||||
|
.IsDependentOn("CreatePackages");
|
||||||
|
|
||||||
|
Task("BuildAndReleaseUnstable")
|
||||||
|
.IsDependentOn("Build")
|
||||||
|
.IsDependentOn("ReleasePackagesToUnstableFeed");
|
||||||
|
|
||||||
|
Task("Clean")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
if (DirectoryExists(artifactsDir))
|
||||||
|
{
|
||||||
|
DeleteDirectory(artifactsDir, recursive:true);
|
||||||
|
}
|
||||||
|
CreateDirectory(artifactsDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("Version")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
versioning = GetNuGetVersionForCommit();
|
||||||
|
var nugetVersion = versioning.NuGetVersion;
|
||||||
|
|
||||||
|
Information("SemVer version number: " + nugetVersion);
|
||||||
|
|
||||||
|
if (AppVeyor.IsRunningOnAppVeyor)
|
||||||
|
{
|
||||||
|
Information("Persisting version number...");
|
||||||
|
PersistVersion(committedVersion, nugetVersion);
|
||||||
|
buildVersion = nugetVersion;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Information("We are not running on build server, so we won't persist the version number.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("Restore")
|
||||||
|
.IsDependentOn("Clean")
|
||||||
|
.IsDependentOn("Version")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
DotNetCoreRestore("./src");
|
||||||
|
DotNetCoreRestore("./test");
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("RunUnitTests")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var buildSettings = new DotNetCoreTestSettings
|
||||||
|
{
|
||||||
|
Configuration = compileConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
EnsureDirectoryExists(artifactsForUnitTestsDir);
|
||||||
|
DotNetCoreTest(unitTestAssemblies, buildSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("RunAcceptanceTests")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var buildSettings = new DotNetCoreTestSettings
|
||||||
|
{
|
||||||
|
Configuration = "Debug", //acceptance test config is hard-coded for debug
|
||||||
|
};
|
||||||
|
|
||||||
|
EnsureDirectoryExists(artifactsForAcceptanceTestsDir);
|
||||||
|
|
||||||
|
DoInDirectory("test/Ocelot.AcceptanceTests", () =>
|
||||||
|
{
|
||||||
|
DotNetCoreTest(".", buildSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
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")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var buildSettings = new DotNetCoreRunSettings
|
||||||
|
{
|
||||||
|
Configuration = compileConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
EnsureDirectoryExists(artifactsForBenchmarkTestsDir);
|
||||||
|
|
||||||
|
DoInDirectory(benchmarkTestAssemblies, () =>
|
||||||
|
{
|
||||||
|
DotNetCoreRun(".", "", buildSettings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("RunTests")
|
||||||
|
.IsDependentOn("RunUnitTests")
|
||||||
|
.IsDependentOn("RunAcceptanceTests")
|
||||||
|
.IsDependentOn("RunIntegrationTests")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("CreatePackages")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
EnsureDirectoryExists(packagesDir);
|
||||||
|
|
||||||
|
GenerateReleaseNotes(releaseNotesFile);
|
||||||
|
|
||||||
|
var settings = new DotNetCorePackSettings
|
||||||
|
{
|
||||||
|
OutputDirectory = packagesDir,
|
||||||
|
NoBuild = true
|
||||||
|
};
|
||||||
|
|
||||||
|
DotNetCorePack(projectJson, settings);
|
||||||
|
|
||||||
|
System.IO.File.WriteAllLines(artifactsFile, new[]{
|
||||||
|
"nuget:Ocelot." + buildVersion + ".nupkg",
|
||||||
|
"nugetSymbols:Ocelot." + buildVersion + ".symbols.nupkg",
|
||||||
|
"releaseNotes:releasenotes.md"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (AppVeyor.IsRunningOnAppVeyor)
|
||||||
|
{
|
||||||
|
var path = packagesDir.ToString() + @"/**/*";
|
||||||
|
|
||||||
|
foreach (var file in GetFiles(path))
|
||||||
|
{
|
||||||
|
AppVeyor.UploadArtifact(file.FullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("ReleasePackagesToUnstableFeed")
|
||||||
|
.IsDependentOn("CreatePackages")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
if (ShouldPublishToUnstableFeed(nugetFeedUnstableBranchFilter, versioning.BranchName))
|
||||||
|
{
|
||||||
|
PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("EnsureStableReleaseRequirements")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
if (!AppVeyor.IsRunningOnAppVeyor)
|
||||||
|
{
|
||||||
|
throw new Exception("Stable release should happen via appveyor");
|
||||||
|
}
|
||||||
|
|
||||||
|
var isTag =
|
||||||
|
AppVeyor.Environment.Repository.Tag.IsTag &&
|
||||||
|
!string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name);
|
||||||
|
|
||||||
|
if (!isTag)
|
||||||
|
{
|
||||||
|
throw new Exception("Stable release should happen from a published GitHub release");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("UpdateVersionInfo")
|
||||||
|
.IsDependentOn("EnsureStableReleaseRequirements")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
releaseTag = AppVeyor.Environment.Repository.Tag.Name;
|
||||||
|
AppVeyor.UpdateBuildVersion(releaseTag);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("DownloadGitHubReleaseArtifacts")
|
||||||
|
.IsDependentOn("UpdateVersionInfo")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
EnsureDirectoryExists(packagesDir);
|
||||||
|
|
||||||
|
var releaseUrl = tagsUrl + releaseTag;
|
||||||
|
var assets_url = ParseJson(GetResource(releaseUrl))
|
||||||
|
.GetValue("assets_url")
|
||||||
|
.Value<string>();
|
||||||
|
|
||||||
|
foreach(var asset in DeserializeJson<JArray>(GetResource(assets_url)))
|
||||||
|
{
|
||||||
|
var file = packagesDir + File(asset.Value<string>("name"));
|
||||||
|
Information("Downloading " + file);
|
||||||
|
DownloadFile(asset.Value<string>("browser_download_url"), file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("ReleasePackagesToStableFeed")
|
||||||
|
.IsDependentOn("DownloadGitHubReleaseArtifacts")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("Release")
|
||||||
|
.IsDependentOn("ReleasePackagesToStableFeed");
|
||||||
|
|
||||||
|
RunTarget(target);
|
||||||
|
|
||||||
|
/// Gets nuique nuget version for this commit
|
||||||
|
private GitVersion GetNuGetVersionForCommit()
|
||||||
|
{
|
||||||
|
GitVersion(new GitVersionSettings{
|
||||||
|
UpdateAssemblyInfo = false,
|
||||||
|
OutputType = GitVersionOutput.BuildServer
|
||||||
|
});
|
||||||
|
|
||||||
|
return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates project version in all of our projects
|
||||||
|
private void PersistVersion(string committedVersion, string newVersion)
|
||||||
|
{
|
||||||
|
Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, newVersion));
|
||||||
|
|
||||||
|
var projectJsonFiles = GetFiles("./**/project.json");
|
||||||
|
|
||||||
|
foreach(var projectJsonFile in projectJsonFiles)
|
||||||
|
{
|
||||||
|
var file = projectJsonFile.ToString();
|
||||||
|
|
||||||
|
Information(string.Format("Updating {0}...", file));
|
||||||
|
|
||||||
|
var updatedProjectJson = System.IO.File.ReadAllText(file)
|
||||||
|
.Replace(committedVersion, newVersion);
|
||||||
|
|
||||||
|
System.IO.File.WriteAllText(file, updatedProjectJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generates release notes based on issues closed in GitHub since the last release
|
||||||
|
private void GenerateReleaseNotes(ConvertableFilePath file)
|
||||||
|
{
|
||||||
|
if (!IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
Warning("We can't generate release notes as we're not running on Windows.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Information("Generating release notes at " + file);
|
||||||
|
|
||||||
|
var releaseNotesExitCode = StartProcess(
|
||||||
|
@"tools/GitReleaseNotes/tools/gitreleasenotes.exe",
|
||||||
|
new ProcessSettings { Arguments = ". /o " + file });
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file)))
|
||||||
|
{
|
||||||
|
System.IO.File.WriteAllText(file, "No issues closed since last release");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseNotesExitCode != 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to generate release notes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file
|
||||||
|
private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl)
|
||||||
|
{
|
||||||
|
var artifacts = System.IO.File
|
||||||
|
.ReadAllLines(artifactsFile)
|
||||||
|
.Select(l => l.Split(':'))
|
||||||
|
.ToDictionary(v => v[0], v => v[1]);
|
||||||
|
|
||||||
|
var codePackage = packagesDir + File(artifacts["nuget"]);
|
||||||
|
|
||||||
|
Information("Pushing package " + codePackage);
|
||||||
|
NuGetPush(
|
||||||
|
codePackage,
|
||||||
|
new NuGetPushSettings {
|
||||||
|
ApiKey = feedApiKey,
|
||||||
|
Source = codeFeedUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets the resource from the specified url
|
||||||
|
private string GetResource(string url)
|
||||||
|
{
|
||||||
|
Information("Getting resource from " + url);
|
||||||
|
|
||||||
|
var assetsRequest = System.Net.WebRequest.CreateHttp(url);
|
||||||
|
assetsRequest.Method = "GET";
|
||||||
|
assetsRequest.Accept = "application/vnd.github.v3+json";
|
||||||
|
assetsRequest.UserAgent = "BuildScript";
|
||||||
|
|
||||||
|
using (var assetsResponse = assetsRequest.GetResponse())
|
||||||
|
{
|
||||||
|
var assetsStream = assetsResponse.GetResponseStream();
|
||||||
|
var assetsReader = new StreamReader(assetsStream);
|
||||||
|
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;
|
||||||
|
}
|
189
build.ps1
Normal file
189
build.ps1
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
##########################################################################
|
||||||
|
# This is the Cake bootstrapper script for PowerShell.
|
||||||
|
# This file was downloaded from https://github.com/cake-build/resources
|
||||||
|
# Feel free to change this file to fit your needs.
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
<#
|
||||||
|
|
||||||
|
.SYNOPSIS
|
||||||
|
This is a Powershell script to bootstrap a Cake build.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
|
||||||
|
and execute your Cake build script with the parameters you provide.
|
||||||
|
|
||||||
|
.PARAMETER Script
|
||||||
|
The build script to execute.
|
||||||
|
.PARAMETER Target
|
||||||
|
The build script target to run.
|
||||||
|
.PARAMETER Configuration
|
||||||
|
The build configuration to use.
|
||||||
|
.PARAMETER Verbosity
|
||||||
|
Specifies the amount of information to be displayed.
|
||||||
|
.PARAMETER Experimental
|
||||||
|
Tells Cake to use the latest Roslyn release.
|
||||||
|
.PARAMETER WhatIf
|
||||||
|
Performs a dry run of the build script.
|
||||||
|
No tasks will be executed.
|
||||||
|
.PARAMETER Mono
|
||||||
|
Tells Cake to use the Mono scripting engine.
|
||||||
|
.PARAMETER SkipToolPackageRestore
|
||||||
|
Skips restoring of packages.
|
||||||
|
.PARAMETER ScriptArgs
|
||||||
|
Remaining arguments are added here.
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
http://cakebuild.net
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[string]$Script = "build.cake",
|
||||||
|
[string]$Target = "Default",
|
||||||
|
[ValidateSet("Release", "Debug")]
|
||||||
|
[string]$Configuration = "Release",
|
||||||
|
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
|
||||||
|
[string]$Verbosity = "Verbose",
|
||||||
|
[switch]$Experimental,
|
||||||
|
[Alias("DryRun","Noop")]
|
||||||
|
[switch]$WhatIf,
|
||||||
|
[switch]$Mono,
|
||||||
|
[switch]$SkipToolPackageRestore,
|
||||||
|
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||||
|
[string[]]$ScriptArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
|
||||||
|
function MD5HashFile([string] $filePath)
|
||||||
|
{
|
||||||
|
if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
|
||||||
|
{
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.IO.Stream] $file = $null;
|
||||||
|
[System.Security.Cryptography.MD5] $md5 = $null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$md5 = [System.Security.Cryptography.MD5]::Create()
|
||||||
|
$file = [System.IO.File]::OpenRead($filePath)
|
||||||
|
return [System.BitConverter]::ToString($md5.ComputeHash($file))
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if ($file -ne $null)
|
||||||
|
{
|
||||||
|
$file.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Preparing to run build script..."
|
||||||
|
|
||||||
|
if(!$PSScriptRoot){
|
||||||
|
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
|
||||||
|
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
|
||||||
|
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
|
||||||
|
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
|
||||||
|
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
|
||||||
|
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
|
||||||
|
|
||||||
|
# Should we use mono?
|
||||||
|
$UseMono = "";
|
||||||
|
if($Mono.IsPresent) {
|
||||||
|
Write-Verbose -Message "Using the Mono based scripting engine."
|
||||||
|
$UseMono = "-mono"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Should we use the new Roslyn?
|
||||||
|
$UseExperimental = "";
|
||||||
|
if($Experimental.IsPresent -and !($Mono.IsPresent)) {
|
||||||
|
Write-Verbose -Message "Using experimental version of Roslyn."
|
||||||
|
$UseExperimental = "-experimental"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is this a dry run?
|
||||||
|
$UseDryRun = "";
|
||||||
|
if($WhatIf.IsPresent) {
|
||||||
|
$UseDryRun = "-dryrun"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure tools folder exists
|
||||||
|
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
|
||||||
|
Write-Verbose -Message "Creating tools directory..."
|
||||||
|
New-Item -Path $TOOLS_DIR -Type directory | out-null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure that packages.config exist.
|
||||||
|
if (!(Test-Path $PACKAGES_CONFIG)) {
|
||||||
|
Write-Verbose -Message "Downloading packages.config..."
|
||||||
|
try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
|
||||||
|
Throw "Could not download packages.config."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try find NuGet.exe in path if not exists
|
||||||
|
if (!(Test-Path $NUGET_EXE)) {
|
||||||
|
Write-Verbose -Message "Trying to find nuget.exe in PATH..."
|
||||||
|
$existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) }
|
||||||
|
$NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
|
||||||
|
if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
|
||||||
|
Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
|
||||||
|
$NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try download NuGet.exe if not exists
|
||||||
|
if (!(Test-Path $NUGET_EXE)) {
|
||||||
|
Write-Verbose -Message "Downloading NuGet.exe..."
|
||||||
|
try {
|
||||||
|
(New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
|
||||||
|
} catch {
|
||||||
|
Throw "Could not download NuGet.exe."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save nuget.exe path to environment to be available to child processed
|
||||||
|
$ENV:NUGET_EXE = $NUGET_EXE
|
||||||
|
|
||||||
|
# Restore tools from NuGet?
|
||||||
|
if(-Not $SkipToolPackageRestore.IsPresent) {
|
||||||
|
Push-Location
|
||||||
|
Set-Location $TOOLS_DIR
|
||||||
|
|
||||||
|
# Check for changes in packages.config and remove installed tools if true.
|
||||||
|
[string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
|
||||||
|
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
|
||||||
|
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
|
||||||
|
Write-Verbose -Message "Missing or changed package.config hash..."
|
||||||
|
Remove-Item * -Recurse -Exclude packages.config,nuget.exe
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose -Message "Restoring tools from NuGet..."
|
||||||
|
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Throw "An error occured while restoring NuGet tools."
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
|
||||||
|
}
|
||||||
|
Write-Verbose -Message ($NuGetOutput | out-string)
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure that Cake has been installed.
|
||||||
|
if (!(Test-Path $CAKE_EXE)) {
|
||||||
|
Throw "Could not find Cake.exe at $CAKE_EXE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start Cake
|
||||||
|
Write-Host "Running build script..."
|
||||||
|
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs"
|
||||||
|
exit $LASTEXITCODE
|
22
build.readme.md
Normal file
22
build.readme.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#1. Overview
|
||||||
|
|
||||||
|
This document summarises the build and release process for the project. The build scripts are written using [Cake](http://cakebuild.net/), and are defined in `./build.cake`. The scripts have been designed to be run by either developers locally or by a build server (currently [AppVeyor](https://www.appveyor.com/)), with minimal logic defined in the build server itself.
|
||||||
|
|
||||||
|
#2. Building
|
||||||
|
* You'll generally want to run the `./build.ps1` script. This will compile, run unit and acceptance tests and build the output packages locally. Output will got to the `./artifacts` directory.
|
||||||
|
* You can view the current commit's [SemVer](http://semver.org/) build information by running `./version.ps1`.
|
||||||
|
* The other `./*.ps1` scripts perform subsets of the build process, if you don't want to run the full build.
|
||||||
|
* The release process works best with GitFlow branching; this allows us to publish every development commit to an unstable feed with a unique SemVer version, and then choose when to release to a stable feed.
|
||||||
|
|
||||||
|
#3. Release process
|
||||||
|
This section defines the release process for the maintainers of the project.
|
||||||
|
* Merge pull requests to the `release` branch.
|
||||||
|
* Every commit pushed to the Origin repo will kick off the [ocelot-build](https://ci.appveyor.com/project/binarymash/ocelot) project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed.
|
||||||
|
* When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory.
|
||||||
|
* When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub.
|
||||||
|
* In Github, navigate to the [release](https://github.com/binarymash/Ocelot/releases). Modify the release name and tag as desired.
|
||||||
|
* When you're ready, publish the release. This will tag the commit with the specified release number.
|
||||||
|
* The [ocelot-release](https://ci.appveyor.com/project/binarymash/ocelot-wtaj9) project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed.
|
||||||
|
* When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing.
|
||||||
|
* Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub.
|
||||||
|
|
101
build.sh
Executable file
101
build.sh
Executable file
@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# This is the Cake bootstrapper script for Linux and OS X.
|
||||||
|
# This file was downloaded from https://github.com/cake-build/resources
|
||||||
|
# Feel free to change this file to fit your needs.
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
# Define directories.
|
||||||
|
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||||
|
TOOLS_DIR=$SCRIPT_DIR/tools
|
||||||
|
NUGET_EXE=$TOOLS_DIR/nuget.exe
|
||||||
|
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
|
||||||
|
PACKAGES_CONFIG=$TOOLS_DIR/packages.config
|
||||||
|
PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
|
||||||
|
|
||||||
|
# Define md5sum or md5 depending on Linux/OSX
|
||||||
|
MD5_EXE=
|
||||||
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
|
MD5_EXE="md5 -r"
|
||||||
|
else
|
||||||
|
MD5_EXE="md5sum"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Define default arguments.
|
||||||
|
SCRIPT="build.cake"
|
||||||
|
TARGET="Default"
|
||||||
|
CONFIGURATION="Release"
|
||||||
|
VERBOSITY="verbose"
|
||||||
|
DRYRUN=
|
||||||
|
SHOW_VERSION=false
|
||||||
|
SCRIPT_ARGUMENTS=()
|
||||||
|
|
||||||
|
# Parse arguments.
|
||||||
|
for i in "$@"; do
|
||||||
|
case $1 in
|
||||||
|
-s|--script) SCRIPT="$2"; shift ;;
|
||||||
|
-t|--target) TARGET="$2"; shift ;;
|
||||||
|
-c|--configuration) CONFIGURATION="$2"; shift ;;
|
||||||
|
-v|--verbosity) VERBOSITY="$2"; shift ;;
|
||||||
|
-d|--dryrun) DRYRUN="-dryrun" ;;
|
||||||
|
--version) SHOW_VERSION=true ;;
|
||||||
|
--) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
|
||||||
|
*) SCRIPT_ARGUMENTS+=("$1") ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Make sure the tools folder exist.
|
||||||
|
if [ ! -d "$TOOLS_DIR" ]; then
|
||||||
|
mkdir "$TOOLS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure that packages.config exist.
|
||||||
|
if [ ! -f "$TOOLS_DIR/packages.config" ]; then
|
||||||
|
echo "Downloading packages.config..."
|
||||||
|
curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "An error occured while downloading packages.config."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download NuGet if it does not exist.
|
||||||
|
if [ ! -f "$NUGET_EXE" ]; then
|
||||||
|
echo "Downloading NuGet..."
|
||||||
|
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "An error occured while downloading nuget.exe."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore tools from NuGet.
|
||||||
|
pushd "$TOOLS_DIR" >/dev/null
|
||||||
|
if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then
|
||||||
|
find . -type d ! -name . | xargs rm -rf
|
||||||
|
fi
|
||||||
|
|
||||||
|
mono "$NUGET_EXE" install -ExcludeVersion
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Could not restore NuGet packages."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5"
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
# Make sure that Cake has been installed.
|
||||||
|
if [ ! -f "$CAKE_EXE" ]; then
|
||||||
|
echo "Could not find Cake.exe at '$CAKE_EXE'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start Cake
|
||||||
|
if $SHOW_VERSION; then
|
||||||
|
exec mono "$CAKE_EXE" -version
|
||||||
|
else
|
||||||
|
exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
|
||||||
|
fi
|
@ -1,12 +1,21 @@
|
|||||||
{
|
{
|
||||||
"ReRoutes": [
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
# The url we are forwarding the request to, ocelot will not add a trailing slash
|
# The downstream path we are forwarding the request to, ocelot will not add a trailing slash.
|
||||||
"DownstreamTemplate": "http://somehost.com/identityserverexample",
|
# Ocelot replaces any placeholders {etc} with matched values from the incoming request.
|
||||||
# The path we are listening on for this re route, Ocelot will add a trailing slash to
|
"DownstreamPathTemplate": "/identityserverexample/{someid}/something",
|
||||||
# this property. Then when a request is made Ocelot makes sure a trailing slash is added
|
# The scheme you want Ocelot to use when making the downstream request
|
||||||
# to that so everything matches
|
"DownstreamScheme": "https",
|
||||||
"UpstreamTemplate": "/identityserverexample",
|
# The port you want Ocelot to use when making the downstream request, will default to
|
||||||
|
# scheme if nothing set
|
||||||
|
"DownstreamPort": 80,
|
||||||
|
# The host address of the downstream service, should not have a trailing slash or scheme
|
||||||
|
# if there is a trailing slash Ocelot will remove it.
|
||||||
|
"DownstreamHost" "localhost"
|
||||||
|
# The path template we are listening on for this re route, Ocelot will add a trailing
|
||||||
|
# slash to this property. Then when a request is made Ocelot makes sure a trailing
|
||||||
|
# slash is added, so everything matches
|
||||||
|
"UpstreamPathTemplate": "/identityserverexample",
|
||||||
# The method we are listening for on this re route
|
# The method we are listening for on this re route
|
||||||
"UpstreamHttpMethod": "Get",
|
"UpstreamHttpMethod": "Get",
|
||||||
# Only support identity server at the moment
|
# Only support identity server at the moment
|
||||||
@ -71,12 +80,24 @@
|
|||||||
# the caching a lot.
|
# the caching a lot.
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 },
|
"FileCacheOptions": { "TtlSeconds": 15 },
|
||||||
# The value of this is used when matching the upstream template to an upstream url.
|
# The value of this is used when matching the upstream template to an upstream url.
|
||||||
"ReRouteIsCaseSensitive": false
|
"ReRouteIsCaseSensitive": false,
|
||||||
|
# Tells Ocelot the name of the service it is looking when making requests to service discovery
|
||||||
|
# for hosts and ports
|
||||||
|
"ServiceName": "product"
|
||||||
|
# Tells Ocelot which load balancer to use when making downstream requests.
|
||||||
|
"LoadBalancer": "RoundRobin"
|
||||||
},
|
},
|
||||||
# This section is meant to be for global configuration settings
|
# This section is meant to be for global configuration settings
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
# If this is set it will override any route specific request id keys, behaves the same
|
# If this is set it will override any route specific request id keys, behaves the same
|
||||||
# otherwise
|
# otherwise
|
||||||
"RequestIdKey": "OcRequestId",
|
"RequestIdKey": "OcRequestId",
|
||||||
|
# If set Ocelot will try and use service discovery to locate downstream hosts and ports
|
||||||
|
"ServiceDiscoveryProvider":
|
||||||
|
{
|
||||||
|
"Provider":"Consul",
|
||||||
|
"Host":"localhost",
|
||||||
|
"Port":8500
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
1
configuration.json
Executable file
1
configuration.json
Executable file
@ -0,0 +1 @@
|
|||||||
|
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}}
|
3
configuration.yaml
Executable file
3
configuration.yaml
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
Routes:
|
||||||
|
- Downstream: http://localhost:51879/
|
||||||
|
Upstream: /heee
|
117
ocelot.postman_collection.json
Normal file
117
ocelot.postman_collection.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,11 +0,0 @@
|
|||||||
echo -------------------------
|
|
||||||
|
|
||||||
echo Packing Ocelot Version %1
|
|
||||||
nuget pack .\Ocelot.nuspec -version %1
|
|
||||||
|
|
||||||
echo Publishing Ocelot
|
|
||||||
nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
1
release.ps1
Normal file
1
release.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target Release
|
1
run-acceptance-tests.ps1
Normal file
1
run-acceptance-tests.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build -target RunAcceptanceTests
|
@ -1,15 +0,0 @@
|
|||||||
echo -------------------------
|
|
||||||
|
|
||||||
echo Running Ocelot.Benchmarks
|
|
||||||
|
|
||||||
cd test/Ocelot.Benchmarks
|
|
||||||
|
|
||||||
dotnet restore
|
|
||||||
|
|
||||||
dotnet run
|
|
||||||
|
|
||||||
cd ../../
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
1
run-benchmarks.ps1
Normal file
1
run-benchmarks.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target RunBenchmarkTests
|
@ -1,17 +0,0 @@
|
|||||||
echo -------------------------
|
|
||||||
|
|
||||||
echo Restoring Ocelot
|
|
||||||
dotnet restore src/Ocelot
|
|
||||||
|
|
||||||
echo Restoring Ocelot.ManualTest
|
|
||||||
dotnet restore test/Ocelot.ManualTest/
|
|
||||||
|
|
||||||
echo Running Ocelot.UnitTests
|
|
||||||
dotnet restore test/Ocelot.UnitTests/
|
|
||||||
dotnet test test/Ocelot.UnitTests/
|
|
||||||
|
|
||||||
echo Running Ocelot.AcceptanceTests
|
|
||||||
cd test/Ocelot.AcceptanceTests/
|
|
||||||
dotnet restore
|
|
||||||
dotnet test
|
|
||||||
cd ../../
|
|
1
run-unit-tests.ps1
Normal file
1
run-unit-tests.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target RunUnitTests
|
@ -12,18 +12,18 @@ namespace Ocelot.Authentication.Handler.Creator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator
|
public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator
|
||||||
{
|
{
|
||||||
public Response<RequestDelegate> CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions)
|
public Response<RequestDelegate> Create(IApplicationBuilder app, AuthenticationOptions authOptions)
|
||||||
{
|
{
|
||||||
var builder = app.New();
|
var builder = app.New();
|
||||||
|
|
||||||
builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
||||||
{
|
{
|
||||||
Authority = authOptions.ProviderRootUrl,
|
Authority = authOptions.ProviderRootUrl,
|
||||||
ScopeName = authOptions.ScopeName,
|
ApiName = authOptions.ScopeName,
|
||||||
RequireHttpsMetadata = authOptions.RequireHttps,
|
RequireHttpsMetadata = authOptions.RequireHttps,
|
||||||
AdditionalScopes = authOptions.AdditionalScopes,
|
AllowedScopes = authOptions.AdditionalScopes,
|
||||||
SupportedTokens = SupportedTokens.Both,
|
SupportedTokens = SupportedTokens.Both,
|
||||||
ScopeSecret = authOptions.ScopeSecret
|
ApiSecret = authOptions.ScopeSecret
|
||||||
});
|
});
|
||||||
|
|
||||||
var authenticationNext = builder.Build();
|
var authenticationNext = builder.Build();
|
||||||
|
@ -8,6 +8,6 @@ namespace Ocelot.Authentication.Handler.Creator
|
|||||||
|
|
||||||
public interface IAuthenticationHandlerCreator
|
public interface IAuthenticationHandlerCreator
|
||||||
{
|
{
|
||||||
Response<RequestDelegate> CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions);
|
Response<RequestDelegate> Create(IApplicationBuilder app, AuthenticationOptions authOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace Ocelot.Authentication.Handler.Factory
|
|||||||
|
|
||||||
public Response<AuthenticationHandler> Get(IApplicationBuilder app, AuthenticationOptions authOptions)
|
public Response<AuthenticationHandler> Get(IApplicationBuilder app, AuthenticationOptions authOptions)
|
||||||
{
|
{
|
||||||
var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions);
|
var handler = _creator.Create(app, authOptions);
|
||||||
|
|
||||||
if (!handler.IsError)
|
if (!handler.IsError)
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Ocelot.Authentication.Handler.Factory;
|
using Ocelot.Authentication.Handler.Factory;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
@ -61,7 +60,7 @@ namespace Ocelot.Authorisation.Middleware
|
|||||||
SetPipelineError(new List<Error>
|
SetPipelineError(new List<Error>
|
||||||
{
|
{
|
||||||
new UnauthorisedError(
|
new UnauthorisedError(
|
||||||
$"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamTemplate}")
|
$"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
22
src/Ocelot/Configuration/Authentication/HashMatcher.cs
Normal file
22
src/Ocelot/Configuration/Authentication/HashMatcher.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/Ocelot/Configuration/Authentication/IHashMatcher.cs
Normal file
7
src/Ocelot/Configuration/Authentication/IHashMatcher.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Configuration.Authentication
|
||||||
|
{
|
||||||
|
public interface IHashMatcher
|
||||||
|
{
|
||||||
|
bool Match(string password, string salt, string hash);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Builder
|
||||||
|
{
|
||||||
|
public class AuthenticationOptionsBuilder
|
||||||
|
{
|
||||||
|
|
||||||
|
private string _provider;
|
||||||
|
private string _providerRootUrl;
|
||||||
|
private string _scopeName;
|
||||||
|
private string _scopeSecret;
|
||||||
|
private bool _requireHttps;
|
||||||
|
private List<string> _additionalScopes;
|
||||||
|
|
||||||
|
public AuthenticationOptionsBuilder WithProvider(string provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationOptionsBuilder WithProviderRootUrl(string providerRootUrl)
|
||||||
|
{
|
||||||
|
_providerRootUrl = providerRootUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationOptionsBuilder WithScopeName(string scopeName)
|
||||||
|
{
|
||||||
|
_scopeName = scopeName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationOptionsBuilder WithScopeSecret(string scopeSecret)
|
||||||
|
{
|
||||||
|
_scopeSecret = scopeSecret;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationOptionsBuilder WithRequireHttps(bool requireHttps)
|
||||||
|
{
|
||||||
|
_requireHttps = requireHttps;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationOptionsBuilder WithAdditionalScopes(List<string> additionalScopes)
|
||||||
|
{
|
||||||
|
_additionalScopes = additionalScopes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationOptions Build()
|
||||||
|
{
|
||||||
|
return new AuthenticationOptions(_provider, _providerRootUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs
Normal file
34
src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
namespace Ocelot.Configuration.Builder
|
||||||
|
{
|
||||||
|
public class QoSOptionsBuilder
|
||||||
|
{
|
||||||
|
private int _exceptionsAllowedBeforeBreaking;
|
||||||
|
|
||||||
|
private int _durationOfBreak;
|
||||||
|
|
||||||
|
private int _timeoutValue;
|
||||||
|
|
||||||
|
public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking)
|
||||||
|
{
|
||||||
|
_exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak)
|
||||||
|
{
|
||||||
|
_durationOfBreak = durationOfBreak;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QoSOptionsBuilder WithTimeoutValue(int timeoutValue)
|
||||||
|
{
|
||||||
|
_timeoutValue = timeoutValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QoSOptions Build()
|
||||||
|
{
|
||||||
|
return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Builder
|
namespace Ocelot.Configuration.Builder
|
||||||
{
|
{
|
||||||
public class ReRouteBuilder
|
public class ReRouteBuilder
|
||||||
{
|
{
|
||||||
private string _downstreamTemplate;
|
private AuthenticationOptions _authenticationOptions;
|
||||||
|
private string _loadBalancerKey;
|
||||||
|
private string _downstreamPathTemplate;
|
||||||
private string _upstreamTemplate;
|
private string _upstreamTemplate;
|
||||||
private string _upstreamTemplatePattern;
|
private string _upstreamTemplatePattern;
|
||||||
private string _upstreamHttpMethod;
|
private string _upstreamHttpMethod;
|
||||||
private bool _isAuthenticated;
|
private bool _isAuthenticated;
|
||||||
private string _authenticationProvider;
|
|
||||||
private string _authenticationProviderUrl;
|
|
||||||
private string _scopeName;
|
|
||||||
private List<string> _additionalScopes;
|
|
||||||
private bool _requireHttps;
|
|
||||||
private string _scopeSecret;
|
|
||||||
private List<ClaimToThing> _configHeaderExtractorProperties;
|
private List<ClaimToThing> _configHeaderExtractorProperties;
|
||||||
private List<ClaimToThing> _claimToClaims;
|
private List<ClaimToThing> _claimToClaims;
|
||||||
private Dictionary<string, string> _routeClaimRequirement;
|
private Dictionary<string, string> _routeClaimRequirement;
|
||||||
@ -23,19 +21,41 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private string _requestIdHeaderKey;
|
private string _requestIdHeaderKey;
|
||||||
private bool _isCached;
|
private bool _isCached;
|
||||||
private CacheOptions _fileCacheOptions;
|
private CacheOptions _fileCacheOptions;
|
||||||
|
private string _downstreamScheme;
|
||||||
|
private string _downstreamHost;
|
||||||
|
private int _downstreamPort;
|
||||||
|
private string _loadBalancer;
|
||||||
|
private ServiceProviderConfiguraion _serviceProviderConfiguraion;
|
||||||
|
private bool _useQos;
|
||||||
|
private QoSOptions _qosOptions;
|
||||||
|
public bool _enableRateLimiting;
|
||||||
|
public RateLimitOptions _rateLimitOptions;
|
||||||
|
|
||||||
public ReRouteBuilder()
|
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
||||||
{
|
{
|
||||||
_additionalScopes = new List<string>();
|
_loadBalancer = loadBalancer;
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithDownstreamTemplate(string input)
|
|
||||||
{
|
|
||||||
_downstreamTemplate = input;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReRouteBuilder WithUpstreamTemplate(string input)
|
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
|
||||||
|
{
|
||||||
|
_downstreamScheme = downstreamScheme;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamHost(string downstreamHost)
|
||||||
|
{
|
||||||
|
_downstreamHost = downstreamHost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamPathTemplate(string input)
|
||||||
|
{
|
||||||
|
_downstreamPathTemplate = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithUpstreamPathTemplate(string input)
|
||||||
{
|
{
|
||||||
_upstreamTemplate = input;
|
_upstreamTemplate = input;
|
||||||
return this;
|
return this;
|
||||||
@ -63,42 +83,6 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReRouteBuilder WithAuthenticationProvider(string input)
|
|
||||||
{
|
|
||||||
_authenticationProvider = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithAuthenticationProviderUrl(string input)
|
|
||||||
{
|
|
||||||
_authenticationProviderUrl = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithAuthenticationProviderScopeName(string input)
|
|
||||||
{
|
|
||||||
_scopeName = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithAuthenticationProviderAdditionalScopes(List<string> input)
|
|
||||||
{
|
|
||||||
_additionalScopes = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithRequireHttps(bool input)
|
|
||||||
{
|
|
||||||
_requireHttps = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithScopeSecret(string input)
|
|
||||||
{
|
|
||||||
_scopeSecret = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithRequestIdKey(string input)
|
public ReRouteBuilder WithRequestIdKey(string input)
|
||||||
{
|
{
|
||||||
_requestIdHeaderKey = input;
|
_requestIdHeaderKey = input;
|
||||||
@ -141,12 +125,83 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamPort(int port)
|
||||||
|
{
|
||||||
|
_downstreamPort = port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithIsQos(bool input)
|
||||||
|
{
|
||||||
|
_useQos = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithQosOptions(QoSOptions input)
|
||||||
|
{
|
||||||
|
_qosOptions = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey)
|
||||||
|
{
|
||||||
|
_loadBalancerKey = loadBalancerKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion)
|
||||||
|
{
|
||||||
|
_serviceProviderConfiguraion = serviceProviderConfiguraion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions)
|
||||||
|
{
|
||||||
|
_authenticationOptions = authenticationOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithEnableRateLimiting(bool input)
|
||||||
|
{
|
||||||
|
_enableRateLimiting = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithRateLimitOptions(RateLimitOptions input)
|
||||||
|
{
|
||||||
|
_rateLimitOptions = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ReRoute Build()
|
public ReRoute Build()
|
||||||
{
|
{
|
||||||
return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
|
return new ReRoute(
|
||||||
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
|
new PathTemplate(_downstreamPathTemplate),
|
||||||
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
|
new PathTemplate(_upstreamTemplate),
|
||||||
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions);
|
new HttpMethod(_upstreamHttpMethod),
|
||||||
|
_upstreamTemplatePattern,
|
||||||
|
_isAuthenticated,
|
||||||
|
_authenticationOptions,
|
||||||
|
_configHeaderExtractorProperties,
|
||||||
|
_claimToClaims,
|
||||||
|
_routeClaimRequirement,
|
||||||
|
_isAuthorised,
|
||||||
|
_claimToQueries,
|
||||||
|
_requestIdHeaderKey,
|
||||||
|
_isCached,
|
||||||
|
_fileCacheOptions,
|
||||||
|
_downstreamScheme,
|
||||||
|
_loadBalancer,
|
||||||
|
_downstreamHost,
|
||||||
|
_downstreamPort,
|
||||||
|
_loadBalancerKey,
|
||||||
|
_serviceProviderConfiguraion,
|
||||||
|
_useQos,
|
||||||
|
_qosOptions,
|
||||||
|
_enableRateLimiting,
|
||||||
|
_rateLimitOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
namespace Ocelot.Configuration.Builder
|
||||||
|
{
|
||||||
|
public class ServiceProviderConfiguraionBuilder
|
||||||
|
{
|
||||||
|
private string _serviceName;
|
||||||
|
private string _downstreamHost;
|
||||||
|
private int _downstreamPort;
|
||||||
|
private bool _userServiceDiscovery;
|
||||||
|
private string _serviceDiscoveryProvider;
|
||||||
|
private string _serviceDiscoveryProviderHost;
|
||||||
|
private int _serviceDiscoveryProviderPort;
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithServiceName(string serviceName)
|
||||||
|
{
|
||||||
|
_serviceName = serviceName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithDownstreamHost(string downstreamHost)
|
||||||
|
{
|
||||||
|
_downstreamHost = downstreamHost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithDownstreamPort(int downstreamPort)
|
||||||
|
{
|
||||||
|
_downstreamPort = downstreamPort;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithUseServiceDiscovery(bool userServiceDiscovery)
|
||||||
|
{
|
||||||
|
_userServiceDiscovery = userServiceDiscovery;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider)
|
||||||
|
{
|
||||||
|
_serviceDiscoveryProvider = serviceDiscoveryProvider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost)
|
||||||
|
{
|
||||||
|
_serviceDiscoveryProviderHost = serviceDiscoveryProviderHost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort)
|
||||||
|
{
|
||||||
|
_serviceDiscoveryProviderPort = serviceDiscoveryProviderPort;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceProviderConfiguraion Build()
|
||||||
|
{
|
||||||
|
return new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery,
|
||||||
|
_serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Parser;
|
using Ocelot.Configuration.Parser;
|
||||||
using Ocelot.Configuration.Validator;
|
using Ocelot.Configuration.Validator;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Requester.QoS;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Utilities;
|
using Ocelot.Utilities;
|
||||||
|
|
||||||
@ -21,36 +25,52 @@ namespace Ocelot.Configuration.Creator
|
|||||||
private const string RegExMatchEverything = ".*";
|
private const string RegExMatchEverything = ".*";
|
||||||
private const string RegExMatchEndString = "$";
|
private const string RegExMatchEndString = "$";
|
||||||
private const string RegExIgnoreCase = "(?i)";
|
private const string RegExIgnoreCase = "(?i)";
|
||||||
|
private const string RegExForwardSlashOnly = "^/$";
|
||||||
|
|
||||||
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
||||||
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
||||||
|
private readonly ILoadBalancerFactory _loadBalanceFactory;
|
||||||
|
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||||
|
private readonly IQoSProviderFactory _qoSProviderFactory;
|
||||||
|
private readonly IQosProviderHouse _qosProviderHouse;
|
||||||
|
|
||||||
public FileOcelotConfigurationCreator(
|
public FileOcelotConfigurationCreator(
|
||||||
IOptions<FileConfiguration> options,
|
IOptions<FileConfiguration> options,
|
||||||
IConfigurationValidator configurationValidator,
|
IConfigurationValidator configurationValidator,
|
||||||
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
||||||
ILogger<FileOcelotConfigurationCreator> logger)
|
ILogger<FileOcelotConfigurationCreator> logger,
|
||||||
|
ILoadBalancerFactory loadBalancerFactory,
|
||||||
|
ILoadBalancerHouse loadBalancerHouse,
|
||||||
|
IQoSProviderFactory qoSProviderFactory,
|
||||||
|
IQosProviderHouse qosProviderHouse)
|
||||||
{
|
{
|
||||||
|
_loadBalanceFactory = loadBalancerFactory;
|
||||||
|
_loadBalancerHouse = loadBalancerHouse;
|
||||||
|
_qoSProviderFactory = qoSProviderFactory;
|
||||||
|
_qosProviderHouse = qosProviderHouse;
|
||||||
_options = options;
|
_options = options;
|
||||||
_configurationValidator = configurationValidator;
|
_configurationValidator = configurationValidator;
|
||||||
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response<IOcelotConfiguration> Create()
|
public async Task<Response<IOcelotConfiguration>> Create()
|
||||||
{
|
{
|
||||||
var config = SetUpConfiguration();
|
var config = await SetUpConfiguration(_options.Value);
|
||||||
|
|
||||||
return new OkResponse<IOcelotConfiguration>(config);
|
return new OkResponse<IOcelotConfiguration>(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public async Task<Response<IOcelotConfiguration>> Create(FileConfiguration fileConfiguration)
|
||||||
/// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see
|
|
||||||
/// will need a refactor at some point as its crap
|
|
||||||
/// </summary>
|
|
||||||
private IOcelotConfiguration SetUpConfiguration()
|
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
@ -66,59 +86,190 @@ 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 = 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 ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration globalConfiguration)
|
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
|
{
|
||||||
|
var isAuthenticated = IsAuthenticated(fileReRoute);
|
||||||
|
|
||||||
|
var isAuthorised = IsAuthorised(fileReRoute);
|
||||||
|
|
||||||
|
var isCached = IsCached(fileReRoute);
|
||||||
|
|
||||||
|
var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration);
|
||||||
|
|
||||||
|
var reRouteKey = BuildReRouteKey(fileReRoute);
|
||||||
|
|
||||||
|
var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute);
|
||||||
|
|
||||||
|
var isQos = IsQoS(fileReRoute);
|
||||||
|
|
||||||
|
var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration);
|
||||||
|
|
||||||
|
var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute);
|
||||||
|
|
||||||
|
var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest);
|
||||||
|
|
||||||
|
var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest);
|
||||||
|
|
||||||
|
var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest);
|
||||||
|
|
||||||
|
var qosOptions = BuildQoSOptions(fileReRoute);
|
||||||
|
|
||||||
|
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
|
||||||
|
|
||||||
|
var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, enableRateLimiting);
|
||||||
|
|
||||||
|
var reRoute = new ReRouteBuilder()
|
||||||
|
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
||||||
|
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
||||||
|
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
||||||
|
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
||||||
|
.WithIsAuthenticated(isAuthenticated)
|
||||||
|
.WithAuthenticationOptions(authOptionsForRoute)
|
||||||
|
.WithClaimsToHeaders(claimsToHeaders)
|
||||||
|
.WithClaimsToClaims(claimsToClaims)
|
||||||
|
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
|
||||||
|
.WithIsAuthorised(isAuthorised)
|
||||||
|
.WithClaimsToQueries(claimsToQueries)
|
||||||
|
.WithRequestIdKey(requestIdKey)
|
||||||
|
.WithIsCached(isCached)
|
||||||
|
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds))
|
||||||
|
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
|
||||||
|
.WithLoadBalancer(fileReRoute.LoadBalancer)
|
||||||
|
.WithDownstreamHost(fileReRoute.DownstreamHost)
|
||||||
|
.WithDownstreamPort(fileReRoute.DownstreamPort)
|
||||||
|
.WithLoadBalancerKey(reRouteKey)
|
||||||
|
.WithServiceProviderConfiguraion(serviceProviderConfiguration)
|
||||||
|
.WithIsQos(isQos)
|
||||||
|
.WithQosOptions(qosOptions)
|
||||||
|
.WithEnableRateLimiting(enableRateLimiting)
|
||||||
|
.WithRateLimitOptions(rateLimitOption)
|
||||||
|
.Build();
|
||||||
|
await SetupLoadBalancer(reRoute);
|
||||||
|
SetupQosProvider(reRoute);
|
||||||
|
return reRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RateLimitOptions BuildRateLimitOptions(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting)
|
||||||
|
{
|
||||||
|
RateLimitOptions rateLimitOption = null;
|
||||||
|
if (enableRateLimiting)
|
||||||
|
{
|
||||||
|
rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader,
|
||||||
|
fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders,
|
||||||
|
globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix,
|
||||||
|
new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit)
|
||||||
|
, globalConfiguration.RateLimitOptions.HttpStatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rateLimitOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QoSOptions BuildQoSOptions(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return new QoSOptionsBuilder()
|
||||||
|
.WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking)
|
||||||
|
.WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak)
|
||||||
|
.WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsQoS(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAuthenticated(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAuthorised(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsCached(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
{
|
{
|
||||||
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
||||||
|
|
||||||
var upstreamTemplate = BuildUpstreamTemplate(reRoute);
|
|
||||||
|
|
||||||
var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider);
|
|
||||||
|
|
||||||
var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0;
|
|
||||||
|
|
||||||
var isCached = reRoute.FileCacheOptions.TtlSeconds > 0;
|
|
||||||
|
|
||||||
var requestIdKey = globalRequestIdConfiguration
|
var requestIdKey = globalRequestIdConfiguration
|
||||||
? globalConfiguration.RequestIdKey
|
? globalConfiguration.RequestIdKey
|
||||||
: reRoute.RequestIdKey;
|
: fileReRoute.RequestIdKey;
|
||||||
|
|
||||||
if (isAuthenticated)
|
return requestIdKey;
|
||||||
{
|
|
||||||
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
|
|
||||||
reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName,
|
|
||||||
reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes,
|
|
||||||
reRoute.AuthenticationOptions.ScopeSecret);
|
|
||||||
|
|
||||||
var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest);
|
|
||||||
var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest);
|
|
||||||
var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest);
|
|
||||||
|
|
||||||
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
|
|
||||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
|
||||||
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
|
||||||
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
|
||||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
|
private string BuildReRouteKey(FileReRoute fileReRoute)
|
||||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
{
|
||||||
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
|
||||||
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}";
|
||||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds));
|
return loadBalancerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildUpstreamTemplate(FileReRoute reRoute)
|
private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
var upstreamTemplate = reRoute.UpstreamTemplate;
|
return new AuthenticationOptionsBuilder()
|
||||||
|
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
|
||||||
|
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
|
||||||
|
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
|
||||||
|
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
|
||||||
|
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
|
||||||
|
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetupLoadBalancer(ReRoute reRoute)
|
||||||
|
{
|
||||||
|
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
|
||||||
|
_loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupQosProvider(ReRoute reRoute)
|
||||||
|
{
|
||||||
|
var loadBalancer = _qoSProviderFactory.Get(reRoute);
|
||||||
|
_qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
|
{
|
||||||
|
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
|
||||||
|
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
|
||||||
|
|
||||||
|
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||||
|
|
||||||
|
return new ServiceProviderConfiguraionBuilder()
|
||||||
|
.WithServiceName(fileReRoute.ServiceName)
|
||||||
|
.WithDownstreamHost(fileReRoute.DownstreamHost)
|
||||||
|
.WithDownstreamPort(fileReRoute.DownstreamPort)
|
||||||
|
.WithUseServiceDiscovery(useServiceDiscovery)
|
||||||
|
.WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider)
|
||||||
|
.WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
|
||||||
|
.WithServiceDiscoveryProviderPort(serviceProviderPort)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildUpstreamTemplatePattern(FileReRoute reRoute)
|
||||||
|
{
|
||||||
|
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
||||||
|
|
||||||
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
|
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
|
||||||
|
|
||||||
@ -140,6 +291,11 @@ namespace Ocelot.Configuration.Creator
|
|||||||
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
|
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (upstreamTemplate == "/")
|
||||||
|
{
|
||||||
|
return RegExForwardSlashOnly;
|
||||||
|
}
|
||||||
|
|
||||||
var route = reRoute.ReRouteIsCaseSensitive
|
var route = reRoute.ReRouteIsCaseSensitive
|
||||||
? $"{upstreamTemplate}{RegExMatchEndString}"
|
? $"{upstreamTemplate}{RegExMatchEndString}"
|
||||||
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
||||||
@ -147,7 +303,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ClaimToThing> GetAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
|
private List<ClaimToThing> BuildAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
|
||||||
{
|
{
|
||||||
var claimsToTHings = new List<ClaimToThing>();
|
var claimsToTHings = new List<ClaimToThing>();
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public interface IOcelotConfigurationCreator
|
public interface IOcelotConfigurationCreator
|
||||||
{
|
{
|
||||||
Response<IOcelotConfiguration> Create();
|
Task<Response<IOcelotConfiguration>> Create();
|
||||||
|
Task<Response<IOcelotConfiguration>> Create(FileConfiguration fileConfiguration);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,19 @@
|
|||||||
namespace Ocelot.Configuration.File
|
|
||||||
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
public class FileGlobalConfiguration
|
public class FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
|
public FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
|
||||||
|
RateLimitOptions = new FileRateLimitOptions();
|
||||||
|
}
|
||||||
|
|
||||||
public string RequestIdKey { get; set; }
|
public string RequestIdKey { get; set; }
|
||||||
|
|
||||||
|
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
|
||||||
|
public string AdministrationPath {get;set;}
|
||||||
|
|
||||||
|
public FileRateLimitOptions RateLimitOptions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
11
src/Ocelot/Configuration/File/FileQoSOptions.cs
Normal file
11
src/Ocelot/Configuration/File/FileQoSOptions.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileQoSOptions
|
||||||
|
{
|
||||||
|
public int ExceptionsAllowedBeforeBreaking { get; set; }
|
||||||
|
|
||||||
|
public int DurationOfBreak { get; set; }
|
||||||
|
|
||||||
|
public int TimeoutValue { get; set; }
|
||||||
|
}
|
||||||
|
}
|
39
src/Ocelot/Configuration/File/FileRateLimitOptions.cs
Normal file
39
src/Ocelot/Configuration/File/FileRateLimitOptions.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileRateLimitOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
|
||||||
|
/// </summary>
|
||||||
|
public string ClientIdHeader { get; set; } = "ClientId";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
|
||||||
|
/// If none specified the default will be:
|
||||||
|
/// API calls quota exceeded! maximum admitted {0} per {1}
|
||||||
|
/// </summary>
|
||||||
|
public string QuotaExceededMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
|
||||||
|
/// </summary>
|
||||||
|
public string RateLimitCounterPrefix { get; set; } = "ocelot";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables X-Rate-Limit and Rety-After headers
|
||||||
|
/// </summary>
|
||||||
|
public bool DisableRateLimitHeaders { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
||||||
|
/// </summary>
|
||||||
|
public int HttpStatusCode { get; set; } = 429;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
34
src/Ocelot/Configuration/File/FileRateLimitRule.cs
Normal file
34
src/Ocelot/Configuration/File/FileRateLimitRule.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
|
||||||
|
public class FileRateLimitRule
|
||||||
|
{
|
||||||
|
public FileRateLimitRule()
|
||||||
|
{
|
||||||
|
ClientWhitelist = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> ClientWhitelist { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables endpoint rate limiting based URL path and HTTP verb
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableRateLimiting { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rate limit period as in 1s, 1m, 1h
|
||||||
|
/// </summary>
|
||||||
|
public string Period { get; set; }
|
||||||
|
|
||||||
|
public double PeriodTimespan { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of requests that a client can make in a defined period
|
||||||
|
/// </summary>
|
||||||
|
public long Limit { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,12 @@ namespace Ocelot.Configuration.File
|
|||||||
AddQueriesToRequest = new Dictionary<string, string>();
|
AddQueriesToRequest = new Dictionary<string, string>();
|
||||||
AuthenticationOptions = new FileAuthenticationOptions();
|
AuthenticationOptions = new FileAuthenticationOptions();
|
||||||
FileCacheOptions = new FileCacheOptions();
|
FileCacheOptions = new FileCacheOptions();
|
||||||
|
QoSOptions = new FileQoSOptions();
|
||||||
|
RateLimitOptions = new FileRateLimitRule();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DownstreamTemplate { get; set; }
|
public string DownstreamPathTemplate { get; set; }
|
||||||
public string UpstreamTemplate { get; set; }
|
public string UpstreamPathTemplate { get; set; }
|
||||||
public string UpstreamHttpMethod { get; set; }
|
public string UpstreamHttpMethod { get; set; }
|
||||||
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
||||||
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
||||||
@ -25,5 +27,12 @@ namespace Ocelot.Configuration.File
|
|||||||
public string RequestIdKey { get; set; }
|
public string RequestIdKey { get; set; }
|
||||||
public FileCacheOptions FileCacheOptions { get; set; }
|
public FileCacheOptions FileCacheOptions { get; set; }
|
||||||
public bool ReRouteIsCaseSensitive { get; set; }
|
public bool ReRouteIsCaseSensitive { get; set; }
|
||||||
|
public string ServiceName { get; set; }
|
||||||
|
public string DownstreamScheme {get;set;}
|
||||||
|
public string DownstreamHost {get;set;}
|
||||||
|
public int DownstreamPort { get; set; }
|
||||||
|
public FileQoSOptions QoSOptions { get; set; }
|
||||||
|
public string LoadBalancer {get;set;}
|
||||||
|
public FileRateLimitRule RateLimitOptions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
public string Provider {get;set;}
|
||||||
|
public string Host {get;set;}
|
||||||
|
public int Port { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -5,5 +5,6 @@ namespace Ocelot.Configuration
|
|||||||
public interface IOcelotConfiguration
|
public interface IOcelotConfiguration
|
||||||
{
|
{
|
||||||
List<ReRoute> ReRoutes { get; }
|
List<ReRoute> ReRoutes { get; }
|
||||||
|
string AdministrationPath {get;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Provider
|
||||||
|
{
|
||||||
|
public interface IFileConfigurationProvider
|
||||||
|
{
|
||||||
|
Response<FileConfiguration> Get();
|
||||||
|
}
|
||||||
|
}
|
@ -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;}
|
||||||
|
}
|
||||||
|
}
|
@ -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;}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Repository;
|
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Provider
|
namespace Ocelot.Configuration.Provider
|
||||||
@ -10,13 +9,10 @@ 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 Response<IOcelotConfiguration> Get()
|
public Response<IOcelotConfiguration> Get()
|
||||||
@ -28,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 = _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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
src/Ocelot/Configuration/Provider/User.cs
Normal file
17
src/Ocelot/Configuration/Provider/User.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
30
src/Ocelot/Configuration/QoSOptions.cs
Normal file
30
src/Ocelot/Configuration/QoSOptions.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using Polly.Timeout;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class QoSOptions
|
||||||
|
{
|
||||||
|
public QoSOptions(
|
||||||
|
int exceptionsAllowedBeforeBreaking,
|
||||||
|
int durationofBreak,
|
||||||
|
int timeoutValue,
|
||||||
|
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
|
||||||
|
{
|
||||||
|
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
||||||
|
DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak);
|
||||||
|
TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue);
|
||||||
|
TimeoutStrategy = timeoutStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int ExceptionsAllowedBeforeBreaking { get; private set; }
|
||||||
|
|
||||||
|
public TimeSpan DurationOfBreak { get; private set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeoutValue { get; private set; }
|
||||||
|
|
||||||
|
public TimeoutStrategy TimeoutStrategy { get; private set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
83
src/Ocelot/Configuration/RateLimitOptions.cs
Normal file
83
src/Ocelot/Configuration/RateLimitOptions.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// RateLimit Options
|
||||||
|
/// </summary>
|
||||||
|
public class RateLimitOptions
|
||||||
|
{
|
||||||
|
public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List<string> clientWhitelist,bool disableRateLimitHeaders,
|
||||||
|
string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode)
|
||||||
|
{
|
||||||
|
EnableRateLimiting = enbleRateLimiting;
|
||||||
|
ClientIdHeader = clientIdHeader;
|
||||||
|
ClientWhitelist = clientWhitelist?? new List<string>();
|
||||||
|
DisableRateLimitHeaders = disableRateLimitHeaders;
|
||||||
|
QuotaExceededMessage = quotaExceededMessage;
|
||||||
|
RateLimitCounterPrefix = rateLimitCounterPrefix;
|
||||||
|
RateLimitRule = rateLimitRule;
|
||||||
|
HttpStatusCode = httpStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RateLimitRule RateLimitRule { get; private set; }
|
||||||
|
|
||||||
|
public List<string> ClientWhitelist { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
|
||||||
|
/// </summary>
|
||||||
|
public string ClientIdHeader { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
||||||
|
/// </summary>
|
||||||
|
public int HttpStatusCode { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
|
||||||
|
/// If none specified the default will be:
|
||||||
|
/// API calls quota exceeded! maximum admitted {0} per {1}
|
||||||
|
/// </summary>
|
||||||
|
public string QuotaExceededMessage { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
|
||||||
|
/// </summary>
|
||||||
|
public string RateLimitCounterPrefix { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables endpoint rate limiting based URL path and HTTP verb
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableRateLimiting { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables X-Rate-Limit and Rety-After headers
|
||||||
|
/// </summary>
|
||||||
|
public bool DisableRateLimitHeaders { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RateLimitRule
|
||||||
|
{
|
||||||
|
public RateLimitRule(string period, TimeSpan periodTimespan, long limit)
|
||||||
|
{
|
||||||
|
Period = period;
|
||||||
|
PeriodTimespan = periodTimespan;
|
||||||
|
Limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rate limit period as in 1s, 1m, 1h,1d
|
||||||
|
/// </summary>
|
||||||
|
public string Period { get; private set; }
|
||||||
|
|
||||||
|
public TimeSpan PeriodTimespan { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of requests that a client can make in a defined period
|
||||||
|
/// </summary>
|
||||||
|
public long Limit { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,43 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
public class ReRoute
|
public class ReRoute
|
||||||
{
|
{
|
||||||
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern,
|
public ReRoute(PathTemplate downstreamPathTemplate,
|
||||||
bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties,
|
PathTemplate upstreamTemplate,
|
||||||
List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries,
|
HttpMethod upstreamHttpMethod,
|
||||||
string requestIdKey, bool isCached, CacheOptions fileCacheOptions)
|
string upstreamTemplatePattern,
|
||||||
|
bool isAuthenticated,
|
||||||
|
AuthenticationOptions authenticationOptions,
|
||||||
|
List<ClaimToThing> configurationHeaderExtractorProperties,
|
||||||
|
List<ClaimToThing> claimsToClaims,
|
||||||
|
Dictionary<string, string> routeClaimsRequirement,
|
||||||
|
bool isAuthorised,
|
||||||
|
List<ClaimToThing> claimsToQueries,
|
||||||
|
string requestIdKey,
|
||||||
|
bool isCached,
|
||||||
|
CacheOptions fileCacheOptions,
|
||||||
|
string downstreamScheme,
|
||||||
|
string loadBalancer,
|
||||||
|
string downstreamHost,
|
||||||
|
int downstreamPort,
|
||||||
|
string reRouteKey,
|
||||||
|
ServiceProviderConfiguraion serviceProviderConfiguraion,
|
||||||
|
bool isQos,
|
||||||
|
QoSOptions qos,
|
||||||
|
bool enableRateLimit,
|
||||||
|
RateLimitOptions ratelimitOptions)
|
||||||
{
|
{
|
||||||
DownstreamTemplate = downstreamTemplate;
|
ReRouteKey = reRouteKey;
|
||||||
UpstreamTemplate = upstreamTemplate;
|
ServiceProviderConfiguraion = serviceProviderConfiguraion;
|
||||||
|
LoadBalancer = loadBalancer;
|
||||||
|
DownstreamHost = downstreamHost;
|
||||||
|
DownstreamPort = downstreamPort;
|
||||||
|
DownstreamPathTemplate = downstreamPathTemplate;
|
||||||
|
UpstreamPathTemplate = upstreamTemplate;
|
||||||
UpstreamHttpMethod = upstreamHttpMethod;
|
UpstreamHttpMethod = upstreamHttpMethod;
|
||||||
UpstreamTemplatePattern = upstreamTemplatePattern;
|
UpstreamTemplatePattern = upstreamTemplatePattern;
|
||||||
IsAuthenticated = isAuthenticated;
|
IsAuthenticated = isAuthenticated;
|
||||||
@ -26,12 +53,18 @@ namespace Ocelot.Configuration
|
|||||||
?? new List<ClaimToThing>();
|
?? new List<ClaimToThing>();
|
||||||
ClaimsToHeaders = configurationHeaderExtractorProperties
|
ClaimsToHeaders = configurationHeaderExtractorProperties
|
||||||
?? new List<ClaimToThing>();
|
?? new List<ClaimToThing>();
|
||||||
|
DownstreamScheme = downstreamScheme;
|
||||||
|
IsQos = isQos;
|
||||||
|
QosOptions = qos;
|
||||||
|
EnableEndpointRateLimiting = enableRateLimit;
|
||||||
|
RateLimitOptions = ratelimitOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DownstreamTemplate { get; private set; }
|
public string ReRouteKey {get;private set;}
|
||||||
public string UpstreamTemplate { get; private set; }
|
public PathTemplate DownstreamPathTemplate { get; private set; }
|
||||||
|
public PathTemplate UpstreamPathTemplate { get; private set; }
|
||||||
public string UpstreamTemplatePattern { get; private set; }
|
public string UpstreamTemplatePattern { get; private set; }
|
||||||
public string UpstreamHttpMethod { get; private set; }
|
public HttpMethod UpstreamHttpMethod { get; private set; }
|
||||||
public bool IsAuthenticated { get; private set; }
|
public bool IsAuthenticated { get; private set; }
|
||||||
public bool IsAuthorised { get; private set; }
|
public bool IsAuthorised { get; private set; }
|
||||||
public AuthenticationOptions AuthenticationOptions { get; private set; }
|
public AuthenticationOptions AuthenticationOptions { get; private set; }
|
||||||
@ -42,5 +75,14 @@ namespace Ocelot.Configuration
|
|||||||
public string RequestIdKey { get; private set; }
|
public string RequestIdKey { get; private set; }
|
||||||
public bool IsCached { get; private set; }
|
public bool IsCached { get; private set; }
|
||||||
public CacheOptions FileCacheOptions { get; private set; }
|
public CacheOptions FileCacheOptions { get; private set; }
|
||||||
|
public string DownstreamScheme {get;private set;}
|
||||||
|
public bool IsQos { get; private set; }
|
||||||
|
public QoSOptions QosOptions { get; private set; }
|
||||||
|
public string LoadBalancer {get;private set;}
|
||||||
|
public string DownstreamHost { get; private set; }
|
||||||
|
public int DownstreamPort { get; private set; }
|
||||||
|
public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; }
|
||||||
|
public bool EnableEndpointRateLimiting { get; private set; }
|
||||||
|
public RateLimitOptions RateLimitOptions { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
25
src/Ocelot/Configuration/ServiceProviderConfiguraion.cs
Normal file
25
src/Ocelot/Configuration/ServiceProviderConfiguraion.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class ServiceProviderConfiguraion
|
||||||
|
{
|
||||||
|
public ServiceProviderConfiguraion(string serviceName, string downstreamHost,
|
||||||
|
int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort)
|
||||||
|
{
|
||||||
|
ServiceName = serviceName;
|
||||||
|
DownstreamHost = downstreamHost;
|
||||||
|
DownstreamPort = downstreamPort;
|
||||||
|
UseServiceDiscovery = useServiceDiscovery;
|
||||||
|
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
||||||
|
ServiceProviderHost = serviceProviderHost;
|
||||||
|
ServiceProviderPort = serviceProviderPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ServiceName { get; }
|
||||||
|
public string DownstreamHost { get; }
|
||||||
|
public int DownstreamPort { get; }
|
||||||
|
public bool UseServiceDiscovery { get; }
|
||||||
|
public string ServiceDiscoveryProvider { get; }
|
||||||
|
public string ServiceProviderHost { get; private set; }
|
||||||
|
public int ServiceProviderPort { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
42
src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs
Normal file
42
src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs
Normal file
11
src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class DownstreamPathTemplateAlreadyUsedError : Error
|
||||||
|
{
|
||||||
|
public DownstreamPathTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreampathTemplateAlreadyUsedError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class DownstreamPathTemplateContainsSchemeError : Error
|
||||||
|
{
|
||||||
|
public DownstreamPathTemplateContainsSchemeError(string message)
|
||||||
|
: base(message, OcelotErrorCode.DownstreamPathTemplateContainsSchemeError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class DownstreamTemplateAlreadyUsedError : Error
|
|
||||||
{
|
|
||||||
public DownstreamTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreamTemplateAlreadyUsedError)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,6 +26,13 @@ namespace Ocelot.Configuration.Validator
|
|||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration);
|
||||||
|
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +54,7 @@ namespace Ocelot.Configuration.Validator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamTemplate}, upstream method is {reRoute.UpstreamHttpMethod}");
|
var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}");
|
||||||
errors.Add(error);
|
errors.Add(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,21 +70,42 @@ namespace Ocelot.Configuration.Validator
|
|||||||
return Enum.TryParse(provider, true, out supportedProvider);
|
return Enum.TryParse(provider, true, out supportedProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConfigurationValidationResult CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(FileConfiguration configuration)
|
||||||
|
{
|
||||||
|
var errors = new List<Error>();
|
||||||
|
|
||||||
|
foreach(var reRoute in configuration.ReRoutes)
|
||||||
|
{
|
||||||
|
if(reRoute.DownstreamPathTemplate.Contains("https://")
|
||||||
|
|| reRoute.DownstreamPathTemplate.Contains("http://"))
|
||||||
|
{
|
||||||
|
errors.Add(new DownstreamPathTemplateContainsSchemeError($"{reRoute.DownstreamPathTemplate} contains scheme"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(errors.Any())
|
||||||
|
{
|
||||||
|
return new ConfigurationValidationResult(true, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConfigurationValidationResult(false, errors);
|
||||||
|
}
|
||||||
|
|
||||||
private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration)
|
private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration)
|
||||||
{
|
{
|
||||||
var hasDupes = configuration.ReRoutes
|
var hasDupes = configuration.ReRoutes
|
||||||
.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any());
|
.GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any());
|
||||||
|
|
||||||
if (!hasDupes)
|
if (!hasDupes)
|
||||||
{
|
{
|
||||||
return new ConfigurationValidationResult(false);
|
return new ConfigurationValidationResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod })
|
var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod })
|
||||||
.Where(x => x.Skip(1).Any());
|
.Where(x => x.Skip(1).Any());
|
||||||
|
|
||||||
var errors = dupes
|
var errors = dupes
|
||||||
.Select(d => new DownstreamTemplateAlreadyUsedError(string.Format("Duplicate DownstreamTemplate: {0}", d.Key.UpstreamTemplate)))
|
.Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamPathTemplate)))
|
||||||
.Cast<Error>()
|
.Cast<Error>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
49
src/Ocelot/Controllers/FileConfigurationController.cs
Normal file
49
src/Ocelot/Controllers/FileConfigurationController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +1,121 @@
|
|||||||
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;
|
||||||
|
using Ocelot.DownstreamUrlCreator;
|
||||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||||
using Ocelot.Headers;
|
using Ocelot.Headers;
|
||||||
using Ocelot.Infrastructure.Claims.Parser;
|
using Ocelot.Infrastructure.Claims.Parser;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
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.Responder;
|
using Ocelot.Responder;
|
||||||
|
using Ocelot.ServiceDiscovery;
|
||||||
|
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
|
||||||
|
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();
|
||||||
|
|
||||||
|
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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddOcelot(this IServiceCollection services)
|
services.AddMvcCore()
|
||||||
{
|
.AddAuthorization()
|
||||||
services.AddMvcCore().AddJsonFormatters();
|
.AddJsonFormatters();
|
||||||
services.AddLogging();
|
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<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||||
|
services.AddSingleton<IUrlBuilder, UrlBuilder>();
|
||||||
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||||
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
|
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
|
||||||
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
||||||
@ -69,7 +126,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
services.AddSingleton<IClaimsParser, ClaimsParser>();
|
services.AddSingleton<IClaimsParser, ClaimsParser>();
|
||||||
services.AddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
services.AddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
||||||
services.AddSingleton<IUrlPathPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
services.AddSingleton<IUrlPathPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
||||||
services.AddSingleton<IDownstreamUrlPathPlaceholderReplacer, DownstreamUrlPathPlaceholderReplacer>();
|
services.AddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
||||||
services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
|
services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
|
||||||
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||||
services.AddSingleton<IHttpResponder, HttpContextResponder>();
|
services.AddSingleton<IHttpResponder, HttpContextResponder>();
|
||||||
@ -77,12 +134,13 @@ namespace Ocelot.DependencyInjection
|
|||||||
services.AddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
|
services.AddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
|
||||||
services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
|
services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
|
||||||
services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
|
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
|
// 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
|
// could maybe use a scoped data repository
|
||||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.AddScoped<IRequestScopedDataRepository, HttpDataRepository>();
|
services.AddScoped<IRequestScopedDataRepository, HttpDataRepository>();
|
||||||
|
services.AddMemoryCache();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,15 +25,22 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
{
|
{
|
||||||
var configuration = _configProvider.Get();
|
var configuration = _configProvider.Get();
|
||||||
|
|
||||||
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));
|
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
||||||
foreach (var reRoute in applicableReRoutes)
|
foreach (var reRoute in applicableReRoutes)
|
||||||
{
|
{
|
||||||
|
if (upstreamUrlPath == reRoute.UpstreamTemplatePattern)
|
||||||
|
{
|
||||||
|
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
|
||||||
|
|
||||||
|
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
|
||||||
|
}
|
||||||
|
|
||||||
var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);
|
var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);
|
||||||
|
|
||||||
if (urlMatch.Data.Match)
|
if (urlMatch.Data.Match)
|
||||||
{
|
{
|
||||||
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate);
|
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
|
||||||
|
|
||||||
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
|
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -44,7 +43,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamTemplate}", downstreamRoute.Data.ReRoute.DownstreamTemplate);
|
_logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate);
|
||||||
|
|
||||||
SetDownstreamRouteForThisRequest(downstreamRoute.Data);
|
SetDownstreamRouteForThisRequest(downstreamRoute.Data);
|
||||||
|
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamUrlCreator
|
||||||
|
{
|
||||||
|
public class DownstreamHostNullOrEmptyError : Error
|
||||||
|
{
|
||||||
|
public DownstreamHostNullOrEmptyError()
|
||||||
|
: base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamUrlCreator
|
||||||
|
{
|
||||||
|
public class DownstreamPathNullOrEmptyError : Error
|
||||||
|
{
|
||||||
|
public DownstreamPathNullOrEmptyError()
|
||||||
|
: base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamUrlCreator
|
||||||
|
{
|
||||||
|
public class DownstreamSchemeNullOrEmptyError : Error
|
||||||
|
{
|
||||||
|
public DownstreamSchemeNullOrEmptyError()
|
||||||
|
: base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs
Normal file
10
src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamUrlCreator
|
||||||
|
{
|
||||||
|
public interface IUrlBuilder
|
||||||
|
{
|
||||||
|
Response<DownstreamUrl> Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort);
|
||||||
|
}
|
||||||
|
}
|
@ -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.DownstreamUrlCreator.UrlTemplateReplacer;
|
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
@ -11,17 +10,20 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IDownstreamUrlPathPlaceholderReplacer _urlReplacer;
|
private readonly IDownstreamPathPlaceholderReplacer _replacer;
|
||||||
private readonly IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly IUrlBuilder _urlBuilder;
|
||||||
|
|
||||||
public DownstreamUrlCreatorMiddleware(RequestDelegate next,
|
public DownstreamUrlCreatorMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IDownstreamUrlPathPlaceholderReplacer urlReplacer,
|
IDownstreamPathPlaceholderReplacer replacer,
|
||||||
IRequestScopedDataRepository requestScopedDataRepository)
|
IRequestScopedDataRepository requestScopedDataRepository,
|
||||||
|
IUrlBuilder urlBuilder)
|
||||||
:base(requestScopedDataRepository)
|
:base(requestScopedDataRepository)
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_urlReplacer = urlReplacer;
|
_replacer = replacer;
|
||||||
|
_urlBuilder = urlBuilder;
|
||||||
_logger = loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>();
|
_logger = loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,19 +31,34 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("started calling downstream url creator middleware");
|
_logger.LogDebug("started calling downstream url creator middleware");
|
||||||
|
|
||||||
var downstreamUrl = _urlReplacer.Replace(DownstreamRoute.ReRoute.DownstreamTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues);
|
var dsPath = _replacer
|
||||||
|
.Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues);
|
||||||
|
|
||||||
if (downstreamUrl.IsError)
|
if (dsPath.IsError)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("IDownstreamUrlPathPlaceholderReplacer returned an error, setting pipeline error");
|
_logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
|
||||||
|
|
||||||
SetPipelineError(downstreamUrl.Errors);
|
SetPipelineError(dsPath.Errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", downstreamUrl.Data.Value);
|
var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme;
|
||||||
|
|
||||||
SetDownstreamUrlForThisRequest(downstreamUrl.Data.Value);
|
var dsHostAndPort = HostAndPort;
|
||||||
|
|
||||||
|
var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort);
|
||||||
|
|
||||||
|
if (dsUrl.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("IUrlBuilder returned an error, setting pipeline error");
|
||||||
|
|
||||||
|
SetPipelineError(dsUrl.Errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", dsUrl.Data.Value);
|
||||||
|
|
||||||
|
SetDownstreamUrlForThisRequest(dsUrl.Data.Value);
|
||||||
|
|
||||||
_logger.LogDebug("calling next middleware");
|
_logger.LogDebug("calling next middleware");
|
||||||
|
|
||||||
|
45
src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs
Normal file
45
src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamUrlCreator
|
||||||
|
{
|
||||||
|
public class UrlBuilder : IUrlBuilder
|
||||||
|
{
|
||||||
|
public Response<DownstreamUrl> Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(downstreamPath))
|
||||||
|
{
|
||||||
|
return new ErrorResponse<DownstreamUrl>(new List<Error> {new DownstreamPathNullOrEmptyError()});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(downstreamScheme))
|
||||||
|
{
|
||||||
|
return new ErrorResponse<DownstreamUrl>(new List<Error> { new DownstreamSchemeNullOrEmptyError() });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost))
|
||||||
|
{
|
||||||
|
return new ErrorResponse<DownstreamUrl>(new List<Error> { new DownstreamHostNullOrEmptyError() });
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new UriBuilder
|
||||||
|
{
|
||||||
|
Host = downstreamHostAndPort.DownstreamHost,
|
||||||
|
Path = downstreamPath,
|
||||||
|
Scheme = downstreamScheme
|
||||||
|
};
|
||||||
|
|
||||||
|
if (downstreamHostAndPort.DownstreamPort > 0)
|
||||||
|
{
|
||||||
|
builder.Port = downstreamHostAndPort.DownstreamPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = builder.Uri.ToString();
|
||||||
|
|
||||||
|
return new OkResponse<DownstreamUrl>(new DownstreamUrl(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,25 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Ocelot.DownstreamRouteFinder;
|
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
||||||
{
|
{
|
||||||
public class DownstreamUrlPathPlaceholderReplacer : IDownstreamUrlPathPlaceholderReplacer
|
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
public Response<DownstreamUrl> Replace(string downstreamTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
public Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
var upstreamUrl = new StringBuilder();
|
var downstreamPath = new StringBuilder();
|
||||||
|
|
||||||
upstreamUrl.Append(downstreamTemplate);
|
downstreamPath.Append(downstreamPathTemplate.Value);
|
||||||
|
|
||||||
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
upstreamUrl.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue);
|
downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<DownstreamUrl>(new DownstreamUrl(upstreamUrl.ToString()));
|
return new OkResponse<DownstreamPath>(new DownstreamPath(downstreamPath.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
||||||
{
|
{
|
||||||
public interface IDownstreamUrlPathPlaceholderReplacer
|
public interface IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
Response<DownstreamUrl> Replace(string downstreamTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
|
Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
|
||||||
@ -41,13 +39,13 @@ namespace Ocelot.Errors.Middleware
|
|||||||
|
|
||||||
var message = CreateMessage(context, e);
|
var message = CreateMessage(context, e);
|
||||||
_logger.LogError(message, e);
|
_logger.LogError(message, e);
|
||||||
await SetInternalServerErrorOnResponse(context);
|
SetInternalServerErrorOnResponse(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("ocelot pipeline finished");
|
_logger.LogDebug("ocelot pipeline finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetInternalServerErrorOnResponse(HttpContext context)
|
private void SetInternalServerErrorOnResponse(HttpContext context)
|
||||||
{
|
{
|
||||||
context.Response.OnStarting(x =>
|
context.Response.OnStarting(x =>
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
UnauthenticatedError,
|
UnauthenticatedError,
|
||||||
UnknownError,
|
UnknownError,
|
||||||
DownstreamTemplateAlreadyUsedError,
|
DownstreampathTemplateAlreadyUsedError,
|
||||||
UnableToFindDownstreamRouteError,
|
UnableToFindDownstreamRouteError,
|
||||||
CannotAddDataError,
|
CannotAddDataError,
|
||||||
CannotFindDataError,
|
CannotFindDataError,
|
||||||
@ -17,6 +17,16 @@
|
|||||||
InstructionNotForClaimsError,
|
InstructionNotForClaimsError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
ClaimValueNotAuthorisedError,
|
ClaimValueNotAuthorisedError,
|
||||||
UserDoesNotHaveClaimError
|
UserDoesNotHaveClaimError,
|
||||||
|
DownstreamPathTemplateContainsSchemeError,
|
||||||
|
DownstreamPathNullOrEmptyError,
|
||||||
|
DownstreamSchemeNullOrEmptyError,
|
||||||
|
DownstreamHostNullOrEmptyError,
|
||||||
|
ServicesAreNullError,
|
||||||
|
ServicesAreEmptyError,
|
||||||
|
UnableToFindServiceDiscoveryProviderError,
|
||||||
|
UnableToFindLoadBalancerError,
|
||||||
|
RequestTimedOutError,
|
||||||
|
UnableToFindQoSProviderError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
24
src/Ocelot/Infrastructure/Extensions/StringExtensions.cs
Normal file
24
src/Ocelot/Infrastructure/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ocelot.Infrastructure.Extensions
|
||||||
|
{
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string TrimStart(this string source, string trim, StringComparison stringComparison = StringComparison.Ordinal)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string s = source;
|
||||||
|
while (s.StartsWith(trim, stringComparison))
|
||||||
|
{
|
||||||
|
s = s.Substring(trim.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs
Normal file
12
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public interface ILoadBalancer
|
||||||
|
{
|
||||||
|
Task<Response<HostAndPort>> Lease();
|
||||||
|
void Release(HostAndPort hostAndPort);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public interface ILoadBalancerFactory
|
||||||
|
{
|
||||||
|
Task<ILoadBalancer> Get(ReRoute reRoute);
|
||||||
|
}
|
||||||
|
}
|
10
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs
Normal file
10
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public interface ILoadBalancerHouse
|
||||||
|
{
|
||||||
|
Response<ILoadBalancer> Get(string key);
|
||||||
|
Response Add(string key, ILoadBalancer loadBalancer);
|
||||||
|
}
|
||||||
|
}
|
15
src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs
Normal file
15
src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class Lease
|
||||||
|
{
|
||||||
|
public Lease(HostAndPort hostAndPort, int connections)
|
||||||
|
{
|
||||||
|
HostAndPort = hostAndPort;
|
||||||
|
Connections = connections;
|
||||||
|
}
|
||||||
|
public HostAndPort HostAndPort { get; private set; }
|
||||||
|
public int Connections { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class LeastConnectionLoadBalancer : ILoadBalancer
|
||||||
|
{
|
||||||
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
|
private readonly List<Lease> _leases;
|
||||||
|
private readonly string _serviceName;
|
||||||
|
private static readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
public LeastConnectionLoadBalancer(Func<Task<List<Service>>> services, string serviceName)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
_serviceName = serviceName;
|
||||||
|
_leases = new List<Lease>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<HostAndPort>> Lease()
|
||||||
|
{
|
||||||
|
var services = await _services.Invoke();
|
||||||
|
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for {_serviceName}") });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!services.Any())
|
||||||
|
{
|
||||||
|
return new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreEmptyError($"services were empty for {_serviceName}") });
|
||||||
|
}
|
||||||
|
|
||||||
|
lock(_syncLock)
|
||||||
|
{
|
||||||
|
//todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something?
|
||||||
|
UpdateServices(services);
|
||||||
|
|
||||||
|
var leaseWithLeastConnections = GetLeaseWithLeastConnections();
|
||||||
|
|
||||||
|
_leases.Remove(leaseWithLeastConnections);
|
||||||
|
|
||||||
|
leaseWithLeastConnections = AddConnection(leaseWithLeastConnections);
|
||||||
|
|
||||||
|
_leases.Add(leaseWithLeastConnections);
|
||||||
|
|
||||||
|
return new OkResponse<HostAndPort>(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(HostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
lock(_syncLock)
|
||||||
|
{
|
||||||
|
var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost
|
||||||
|
&& l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort);
|
||||||
|
|
||||||
|
if (matchingLease != null)
|
||||||
|
{
|
||||||
|
var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1);
|
||||||
|
|
||||||
|
_leases.Remove(matchingLease);
|
||||||
|
|
||||||
|
_leases.Add(replacementLease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lease AddConnection(Lease lease)
|
||||||
|
{
|
||||||
|
return new Lease(lease.HostAndPort, lease.Connections + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lease GetLeaseWithLeastConnections()
|
||||||
|
{
|
||||||
|
//now get the service with the least connections?
|
||||||
|
Lease leaseWithLeastConnections = null;
|
||||||
|
|
||||||
|
for (var i = 0; i < _leases.Count; i++)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
leaseWithLeastConnections = _leases[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_leases[i].Connections < leaseWithLeastConnections.Connections)
|
||||||
|
{
|
||||||
|
leaseWithLeastConnections = _leases[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return leaseWithLeastConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response UpdateServices(List<Service> services)
|
||||||
|
{
|
||||||
|
if (_leases.Count > 0)
|
||||||
|
{
|
||||||
|
var leasesToRemove = new List<Lease>();
|
||||||
|
|
||||||
|
foreach (var lease in _leases)
|
||||||
|
{
|
||||||
|
var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost
|
||||||
|
&& s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort);
|
||||||
|
|
||||||
|
if (match == null)
|
||||||
|
{
|
||||||
|
leasesToRemove.Add(lease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var lease in leasesToRemove)
|
||||||
|
{
|
||||||
|
_leases.Remove(lease);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var service in services)
|
||||||
|
{
|
||||||
|
var exists = _leases.FirstOrDefault(l => l.HostAndPort.ToString() == service.HostAndPort.ToString());
|
||||||
|
|
||||||
|
if (exists == null)
|
||||||
|
{
|
||||||
|
_leases.Add(new Lease(service.HostAndPort, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var service in services)
|
||||||
|
{
|
||||||
|
_leases.Add(new Lease(service.HostAndPort, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs
Normal file
30
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.ServiceDiscovery;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class LoadBalancerFactory : ILoadBalancerFactory
|
||||||
|
{
|
||||||
|
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
|
||||||
|
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
|
||||||
|
{
|
||||||
|
_serviceProviderFactory = serviceProviderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ILoadBalancer> Get(ReRoute reRoute)
|
||||||
|
{
|
||||||
|
var serviceProvider = _serviceProviderFactory.Get(reRoute.ServiceProviderConfiguraion);
|
||||||
|
|
||||||
|
switch (reRoute.LoadBalancer)
|
||||||
|
{
|
||||||
|
case "RoundRobin":
|
||||||
|
return new RoundRobinLoadBalancer(await serviceProvider.Get());
|
||||||
|
case "LeastConnection":
|
||||||
|
return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName);
|
||||||
|
default:
|
||||||
|
return new NoLoadBalancer(await serviceProvider.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs
Normal file
42
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class LoadBalancerHouse : ILoadBalancerHouse
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, ILoadBalancer> _loadBalancers;
|
||||||
|
|
||||||
|
public LoadBalancerHouse()
|
||||||
|
{
|
||||||
|
_loadBalancers = new Dictionary<string, ILoadBalancer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response<ILoadBalancer> Get(string key)
|
||||||
|
{
|
||||||
|
ILoadBalancer loadBalancer;
|
||||||
|
|
||||||
|
if(_loadBalancers.TryGetValue(key, out loadBalancer))
|
||||||
|
{
|
||||||
|
return new OkResponse<ILoadBalancer>(_loadBalancers[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||||
|
{
|
||||||
|
new UnableToFindLoadBalancerError($"unabe to find load balancer for {key}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response Add(string key, ILoadBalancer loadBalancer)
|
||||||
|
{
|
||||||
|
if (!_loadBalancers.ContainsKey(key))
|
||||||
|
{
|
||||||
|
_loadBalancers.Add(key, loadBalancer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadBalancers.Remove(key);
|
||||||
|
_loadBalancers.Add(key, loadBalancer);
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs
Normal file
28
src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class NoLoadBalancer : ILoadBalancer
|
||||||
|
{
|
||||||
|
private readonly List<Service> _services;
|
||||||
|
|
||||||
|
public NoLoadBalancer(List<Service> services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<HostAndPort>> Lease()
|
||||||
|
{
|
||||||
|
var service = await Task.FromResult(_services.FirstOrDefault());
|
||||||
|
return new OkResponse<HostAndPort>(service.HostAndPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(HostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class RoundRobinLoadBalancer : ILoadBalancer
|
||||||
|
{
|
||||||
|
private readonly List<Service> _services;
|
||||||
|
private int _last;
|
||||||
|
|
||||||
|
public RoundRobinLoadBalancer(List<Service> services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<HostAndPort>> Lease()
|
||||||
|
{
|
||||||
|
if (_last >= _services.Count)
|
||||||
|
{
|
||||||
|
_last = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = await Task.FromResult(_services[_last]);
|
||||||
|
_last++;
|
||||||
|
return new OkResponse<HostAndPort>(next.HostAndPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(HostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class ServicesAreEmptyError : Error
|
||||||
|
{
|
||||||
|
public ServicesAreEmptyError(string message)
|
||||||
|
: base(message, OcelotErrorCode.ServicesAreEmptyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user