mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
commit
9267918270
4
src/Ocelot.Library/.gitignore → .gitignore
vendored
4
src/Ocelot.Library/.gitignore → .gitignore
vendored
@ -21,11 +21,14 @@ build/
|
|||||||
bld/
|
bld/
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
|
results/
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
# Visual Studio 2015 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
|
.vscode/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
#wwwroot/
|
#wwwroot/
|
||||||
|
site/wwwroot/
|
||||||
|
|
||||||
# MSTest test Results
|
# MSTest test Results
|
||||||
[Tt]est[Rr]esult*/
|
[Tt]est[Rr]esult*/
|
||||||
@ -232,3 +235,4 @@ _Pvt_Extensions
|
|||||||
|
|
||||||
# FAKE - F# Make
|
# FAKE - F# Make
|
||||||
.fake/
|
.fake/
|
||||||
|
tools/
|
4
GitVersion.yml
Normal file
4
GitVersion.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mode: ContinuousDelivery
|
||||||
|
branches: {}
|
||||||
|
ignore:
|
||||||
|
sha: []
|
48
Ocelot.nuspec
Normal file
48
Ocelot.nuspec
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<package >
|
||||||
|
<metadata>
|
||||||
|
<id>Ocelot</id>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<authors>Tom Pallister</authors>
|
||||||
|
<owners>Tom Pallister</owners>
|
||||||
|
<licenseUrl>https://github.com/TomPallister/Ocelot/blob/develop/LICENSE.md</licenseUrl>
|
||||||
|
<projectUrl>https://github.com/TomPallister/Ocelot</projectUrl>
|
||||||
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
|
<description>Ocelot Api Gateway</description>
|
||||||
|
<releaseNotes>Latest Ocelot</releaseNotes>
|
||||||
|
<copyright></copyright>
|
||||||
|
<tags></tags>
|
||||||
|
<dependencies>
|
||||||
|
<dependency id="Microsoft.AspNetCore.Server.IISIntegration" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Configuration.EnvironmentVariables" version= "1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Configuration.FileExtensions" version= "1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Configuration.Json" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Logging" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Logging.Console" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Logging.Debug" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.Extensions.Options.ConfigurationExtensions" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Http" version="1.1.0" />
|
||||||
|
<dependency id="System.Text.RegularExpressions" version="4.3.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.OAuth" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.JwtBearer" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.Cookies" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.Google" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.Facebook" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.Twitter" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication.MicrosoftAccount" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Authentication" version="1.1.0" />
|
||||||
|
<dependency id="IdentityServer4.AccessTokenValidation" version="1.0.2" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Mvc" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.AspNetCore.Server.Kestrel" version="1.1.0" />
|
||||||
|
<dependency id="Microsoft.NETCore.App" version="1.1.0" />
|
||||||
|
<dependency id="CacheManager.Core" version="0.9.2" />
|
||||||
|
<dependency id="CacheManager.Microsoft.Extensions.Configuration" version="0.9.2" />
|
||||||
|
<dependency id="CacheManager.Microsoft.Extensions.Logging" version="0.9.2" />
|
||||||
|
</dependencies>
|
||||||
|
</metadata>
|
||||||
|
<files>
|
||||||
|
<file src="src\Ocelot\bin\Release\netcoreapp1.4\Ocelot.dll" target="lib\netstandard1.4" />
|
||||||
|
<file src="src\Ocelot\bin\Release\netcoreapp1.4\Ocelot.pdb" target="lib\netstandard1.4" />
|
||||||
|
</files>
|
||||||
|
</package>
|
81
Ocelot.sln
Normal file
81
Ocelot.sln
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.25420.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.gitignore = .gitignore
|
||||||
|
appveyor.yml = appveyor.yml
|
||||||
|
build-and-release-unstable.ps1 = build-and-release-unstable.ps1
|
||||||
|
build-and-run-tests.ps1 = build-and-run-tests.ps1
|
||||||
|
build.cake = build.cake
|
||||||
|
build.ps1 = build.ps1
|
||||||
|
build.readme.md = build.readme.md
|
||||||
|
configuration-explanation.txt = configuration-explanation.txt
|
||||||
|
configuration.yaml = configuration.yaml
|
||||||
|
GitVersion.yml = GitVersion.yml
|
||||||
|
global.json = global.json
|
||||||
|
LICENSE.md = LICENSE.md
|
||||||
|
Ocelot.nuspec = Ocelot.nuspec
|
||||||
|
README.md = README.md
|
||||||
|
release.ps1 = release.ps1
|
||||||
|
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
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.xproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.xproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.xproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.xproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{54E84F1A-E525-4443-96EC-039CBD50C263} = {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}
|
||||||
|
{106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
427
README.md
427
README.md
@ -1,3 +1,430 @@
|
|||||||
# Ocelot
|
# 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)
|
||||||
|
|
||||||
Attempt at a .NET Api Gateway
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
{
|
||||||
|
"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.
|
||||||
|
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(env.ContentRootPath)
|
||||||
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||||
|
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
||||||
|
.AddJsonFile("configuration.json")
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
|
Configuration = builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfigurationRoot Configuration { get; }
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
Action<ConfigurationBuilderCachePart> settings = (x) =>
|
||||||
|
{
|
||||||
|
x.WithMicrosoftLogging(log =>
|
||||||
|
{
|
||||||
|
log.AddConsole(LogLevel.Debug);
|
||||||
|
})
|
||||||
|
.WithDictionaryHandle();
|
||||||
|
};
|
||||||
|
|
||||||
|
services.AddOcelotOutputCaching(settings);
|
||||||
|
services.AddOcelotFileConfiguration(Configuration);
|
||||||
|
services.AddOcelot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||||
|
|
||||||
|
app.UseOcelot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 UpstreamTemplate.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
{
|
||||||
|
"ReRoutes": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
In order to set up a ReRoute you need to add one to the json array called ReRoutes like
|
||||||
|
the following.
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamPort": 80,
|
||||||
|
"DownstreamHost" "localhost"
|
||||||
|
"UpstreamTemplate": "/posts/{postId}",
|
||||||
|
"UpstreamHttpMethod": "Put"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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 UpstreamTemplate. 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.
|
||||||
|
|
||||||
|
"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!
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
"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.
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"UpstreamTemplate": "/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.
|
||||||
|
|
||||||
|
"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.
|
||||||
|
|
||||||
|
"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
|
||||||
|
|
||||||
|
"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
|
||||||
|
|
||||||
|
"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
|
||||||
|
|
||||||
|
"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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
"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.
|
||||||
|
|
||||||
|
"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.
|
||||||
|
|
||||||
|
"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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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
|
8
appveyor.yml
Normal file
8
appveyor.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: 1.0.{build}
|
||||||
|
configuration:
|
||||||
|
- Release
|
||||||
|
platform: Any CPU
|
||||||
|
build_script:
|
||||||
|
- build.ps1
|
||||||
|
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
build-and-run-tests.ps1
Normal file
1
build-and-run-tests.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target RunTests
|
347
build.cake
Normal file
347
build.cake
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
#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");
|
||||||
|
var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests";
|
||||||
|
|
||||||
|
// 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 = "";
|
||||||
|
var committedVersion = "0.0.0-dev";
|
||||||
|
var buildVersion = committedVersion;
|
||||||
|
|
||||||
|
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(() =>
|
||||||
|
{
|
||||||
|
var nugetVersion = GetNuGetVersionForCommit();
|
||||||
|
Information("SemVer version number: " + nugetVersion);
|
||||||
|
|
||||||
|
if (AppVeyor.IsRunningOnAppVeyor)
|
||||||
|
{
|
||||||
|
Information("Persisting version number...");
|
||||||
|
PersistVersion(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("RunBenchmarkTests")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var buildSettings = new DotNetCoreRunSettings
|
||||||
|
{
|
||||||
|
Configuration = compileConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
EnsureDirectoryExists(artifactsForBenchmarkTestsDir);
|
||||||
|
|
||||||
|
DoInDirectory(benchmarkTestAssemblies, () =>
|
||||||
|
{
|
||||||
|
DotNetCoreRun(".", "", buildSettings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("RunTests")
|
||||||
|
.IsDependentOn("RunUnitTests")
|
||||||
|
.IsDependentOn("RunAcceptanceTests")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("CreatePackages")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
EnsureDirectoryExists(packagesDir);
|
||||||
|
|
||||||
|
GenerateReleaseNotes();
|
||||||
|
|
||||||
|
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(() =>
|
||||||
|
{
|
||||||
|
PublishPackages(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(nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("Release")
|
||||||
|
.IsDependentOn("ReleasePackagesToStableFeed");
|
||||||
|
|
||||||
|
RunTarget(target);
|
||||||
|
|
||||||
|
/// Gets nuique nuget version for this commit
|
||||||
|
private string GetNuGetVersionForCommit()
|
||||||
|
{
|
||||||
|
GitVersion(new GitVersionSettings{
|
||||||
|
UpdateAssemblyInfo = false,
|
||||||
|
OutputType = GitVersionOutput.BuildServer
|
||||||
|
});
|
||||||
|
|
||||||
|
var versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json });
|
||||||
|
return versionInfo.NuGetVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates project version in all of our projects
|
||||||
|
private void PersistVersion(string version)
|
||||||
|
{
|
||||||
|
Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version));
|
||||||
|
|
||||||
|
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, version);
|
||||||
|
|
||||||
|
System.IO.File.WriteAllText(file, updatedProjectJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generates release notes based on issues closed in GitHub since the last release
|
||||||
|
private void GenerateReleaseNotes()
|
||||||
|
{
|
||||||
|
Information("Generating release notes at " + releaseNotesFile);
|
||||||
|
|
||||||
|
var releaseNotesExitCode = StartProcess(
|
||||||
|
@"tools/GitReleaseNotes/tools/gitreleasenotes.exe",
|
||||||
|
new ProcessSettings { Arguments = ". /o " + releaseNotesFile });
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile)))
|
||||||
|
{
|
||||||
|
System.IO.File.WriteAllText(releaseNotesFile, "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(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"]);
|
||||||
|
var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]);
|
||||||
|
|
||||||
|
NuGetPush(
|
||||||
|
codePackage,
|
||||||
|
new NuGetPushSettings {
|
||||||
|
ApiKey = feedApiKey,
|
||||||
|
Source = codeFeedUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
NuGetPush(
|
||||||
|
symbolsPackage,
|
||||||
|
new NuGetPushSettings {
|
||||||
|
ApiKey = feedApiKey,
|
||||||
|
Source = symbolFeedUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
}
|
||||||
|
}
|
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
|
103
configuration-explanation.txt
Normal file
103
configuration-explanation.txt
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"ReRoutes": [
|
||||||
|
{
|
||||||
|
# The downstream path we are forwarding the request to, ocelot will not add a trailing slash.
|
||||||
|
# Ocelot replaces any placeholders {etc} with matched values from the incoming request.
|
||||||
|
"DownstreamPathTemplate": "/identityserverexample/{someid}/something",
|
||||||
|
# The scheme you want Ocelot to use when making the downstream request
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
# 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
|
||||||
|
"UpstreamTemplate": "/identityserverexample",
|
||||||
|
# The method we are listening for on this re route
|
||||||
|
"UpstreamHttpMethod": "Get",
|
||||||
|
# Only support identity server at the moment
|
||||||
|
"AuthenticationOptions": {
|
||||||
|
"Provider": "IdentityServer",
|
||||||
|
"ProviderRootUrl": "http://localhost:52888",
|
||||||
|
"ScopeName": "api",
|
||||||
|
"AdditionalScopes": [
|
||||||
|
"openid",
|
||||||
|
"offline_access"
|
||||||
|
],
|
||||||
|
# Required if using reference tokens
|
||||||
|
"ScopeSecret": "secret"
|
||||||
|
},
|
||||||
|
# WARNING - will overwrite any headers already in the request with these values.
|
||||||
|
# Ocelot will look in the user claims for the key in [] then return the value and save
|
||||||
|
# it as a header with the given key before the colon (:). The index selection on value
|
||||||
|
# means that Ocelot will use the delimiter specified after the next > to split the
|
||||||
|
# claim value and return the index specified.
|
||||||
|
"AddHeadersToRequest": {
|
||||||
|
"CustomerId": "Claims[CustomerId] > value",
|
||||||
|
"LocationId": "Claims[LocationId] > value",
|
||||||
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
|
},
|
||||||
|
# WARNING - will overwrite any claims already in the request with these values.
|
||||||
|
# Ocelot will look in the user claims for the key in [] then return the value and save
|
||||||
|
# it as a claim with the given key before the colon (:). The index selection on value
|
||||||
|
# means that Ocelot will use the delimiter specified after the next > to split the
|
||||||
|
# claim value and return the index specified.
|
||||||
|
"AddClaimsToRequest": {
|
||||||
|
"CustomerId": "Claims[CustomerId] > value",
|
||||||
|
"LocationId": "Claims[LocationId] > value",
|
||||||
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
|
},
|
||||||
|
# WARNING - will overwrite any query string entries already in the request with these values.
|
||||||
|
# Ocelot will look in the user claims for the key in [] then return the value and save
|
||||||
|
# it as a query string with the given key before the colon (:). The index selection on value
|
||||||
|
# means that Ocelot will use the delimiter specified after the next > to split the
|
||||||
|
# claim value and return the index specified.
|
||||||
|
"AddQueriesToRequest": {
|
||||||
|
"CustomerId": "Claims[CustomerId] > value",
|
||||||
|
"LocationId": "Claims[LocationId] > value",
|
||||||
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
|
},
|
||||||
|
# This specifies any claims that are required for the user to access this re route.
|
||||||
|
# In this example the user must have the claim type UserType and
|
||||||
|
# the value must be registered
|
||||||
|
"RouteClaimsRequirement": {
|
||||||
|
"UserType": "registered"
|
||||||
|
},
|
||||||
|
# This tells Ocelot to look for a header and use its value as a request/correlation id.
|
||||||
|
# If it is set here then the id will be forwarded to the downstream service. If it
|
||||||
|
# does not then it will not be forwarded
|
||||||
|
"RequestIdKey": "OcRequestId",
|
||||||
|
# If this is set the response from the downstream service will be cached using the key that called it.
|
||||||
|
# This gives the user a chance to influence the key by adding some random query string paramter for
|
||||||
|
# a user id or something that would get ignored by the downstream service. This is a hack and I
|
||||||
|
# intend to provide a mechanism the user can specify for the ttl caching. Also want to expand
|
||||||
|
# the caching a lot.
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 },
|
||||||
|
# The value of this is used when matching the upstream template to an upstream url.
|
||||||
|
"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
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
# If this is set it will override any route specific request id keys, behaves the same
|
||||||
|
# otherwise
|
||||||
|
"RequestIdKey": "OcRequestId",
|
||||||
|
# If set Ocelot will try and use service discovery to locate downstream hosts and ports
|
||||||
|
"ServiceDiscoveryProvider":
|
||||||
|
{
|
||||||
|
"Provider":"Consul",
|
||||||
|
"Host":"localhost",
|
||||||
|
"Port":8500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
configuration.yaml
Executable file
3
configuration.yaml
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
Routes:
|
||||||
|
- Downstream: http://localhost:51879/
|
||||||
|
Upstream: /heee
|
6
global.json
Normal file
6
global.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"projects": [ "src", "test" ],
|
||||||
|
"sdk": {
|
||||||
|
"version": "1.0.0-preview2-003133"
|
||||||
|
}
|
||||||
|
}
|
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
run-benchmarks.ps1
Normal file
1
run-benchmarks.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target RunBenchmarkTests
|
1
run-unit-tests.ps1
Normal file
1
run-unit-tests.ps1
Normal file
@ -0,0 +1 @@
|
|||||||
|
./build.ps1 -target RunUnitTests
|
@ -1,19 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Ocelot.ApiGateway
|
|
||||||
{
|
|
||||||
public static class HelloExtensions
|
|
||||||
{
|
|
||||||
public static IRouteBuilder AddRouter(this IRouteBuilder routeBuilder,
|
|
||||||
IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
routeBuilder.Routes.Add(new Route(new Router(),
|
|
||||||
"{*url}",
|
|
||||||
app.ApplicationServices.GetService<IInlineConstraintResolver>()));
|
|
||||||
|
|
||||||
return routeBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
FROM microsoft/aspnet:1.0.0-rc1-update1
|
|
||||||
|
|
||||||
RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list
|
|
||||||
RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY . /app
|
|
||||||
WORKDIR /app
|
|
||||||
RUN ["dnu", "restore"]
|
|
||||||
|
|
||||||
EXPOSE 5000/tcp
|
|
||||||
ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"]
|
|
@ -1,24 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace Ocelot.ApiGateway
|
|
||||||
{
|
|
||||||
public class Router : IRouter
|
|
||||||
{
|
|
||||||
public Task RouteAsync(RouteContext context)
|
|
||||||
{
|
|
||||||
context.Handler = async c =>
|
|
||||||
{
|
|
||||||
await c.Response.WriteAsync($"Hi, Tom!");
|
|
||||||
};
|
|
||||||
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.NETCore.App": {
|
|
||||||
"version": "1.0.0-rc2-3002702",
|
|
||||||
"type": "platform"
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.Extensions.Logging": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
|
|
||||||
"Microsoft.AspNetCore.Routing": "1.0.0-rc2-final"
|
|
||||||
},
|
|
||||||
|
|
||||||
"tools": {
|
|
||||||
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
|
|
||||||
"version": "1.0.0-preview1-final",
|
|
||||||
"imports": "portable-net45+win8+dnxcore50"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"frameworks": {
|
|
||||||
"netcoreapp1.0": {
|
|
||||||
"imports": [
|
|
||||||
"dotnet5.6",
|
|
||||||
"dnxcore50",
|
|
||||||
"portable-net45+win8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"buildOptions": {
|
|
||||||
"emitEntryPoint": true,
|
|
||||||
"preserveCompilationContext": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"runtimeOptions": {
|
|
||||||
"gcServer": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"publishOptions": {
|
|
||||||
"include": [
|
|
||||||
"wwwroot",
|
|
||||||
"Views",
|
|
||||||
"appsettings.json",
|
|
||||||
"web.config"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"scripts": {
|
|
||||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
|
||||||
},
|
|
||||||
|
|
||||||
"tooling": {
|
|
||||||
"defaultNamespace": "Ocelot.ApiGateway"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.Library
|
|
||||||
{
|
|
||||||
// This project can output the Class library as a NuGet Package.
|
|
||||||
// To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build".
|
|
||||||
public class RouterMiddleware
|
|
||||||
{
|
|
||||||
public RouterMiddleware()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.0.0-*",
|
|
||||||
|
|
||||||
"dependencies": {
|
|
||||||
"NETStandard.Library": "1.5.0-rc2-24027"
|
|
||||||
},
|
|
||||||
|
|
||||||
"frameworks": {
|
|
||||||
"netstandard1.5": {
|
|
||||||
"imports": "dnxcore50"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"tooling": {
|
|
||||||
"defaultNamespace": "Ocelot.Library"
|
|
||||||
}
|
|
||||||
}
|
|
14
src/Ocelot/Authentication/Handler/AuthenticationHandler.cs
Normal file
14
src/Ocelot/Authentication/Handler/AuthenticationHandler.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Ocelot.Authentication.Handler
|
||||||
|
{
|
||||||
|
public class AuthenticationHandler
|
||||||
|
{
|
||||||
|
public AuthenticationHandler(string provider, IHandler handler)
|
||||||
|
{
|
||||||
|
Provider = provider;
|
||||||
|
Handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Provider { get; private set; }
|
||||||
|
public IHandler Handler { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler.Creator
|
||||||
|
{
|
||||||
|
using AuthenticationOptions = Configuration.AuthenticationOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot unit test things in this class due to use of extension methods
|
||||||
|
/// </summary>
|
||||||
|
public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator
|
||||||
|
{
|
||||||
|
public Response<RequestDelegate> CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions)
|
||||||
|
{
|
||||||
|
var builder = app.New();
|
||||||
|
|
||||||
|
builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
||||||
|
{
|
||||||
|
Authority = authOptions.ProviderRootUrl,
|
||||||
|
ApiName = authOptions.ScopeName,
|
||||||
|
RequireHttpsMetadata = authOptions.RequireHttps,
|
||||||
|
AllowedScopes = authOptions.AdditionalScopes,
|
||||||
|
SupportedTokens = SupportedTokens.Both,
|
||||||
|
ApiSecret = authOptions.ScopeSecret
|
||||||
|
});
|
||||||
|
|
||||||
|
var authenticationNext = builder.Build();
|
||||||
|
|
||||||
|
return new OkResponse<RequestDelegate>(authenticationNext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler.Creator
|
||||||
|
{
|
||||||
|
using AuthenticationOptions = Configuration.AuthenticationOptions;
|
||||||
|
|
||||||
|
public interface IAuthenticationHandlerCreator
|
||||||
|
{
|
||||||
|
Response<RequestDelegate> CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Ocelot.Authentication.Handler.Creator;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler.Factory
|
||||||
|
{
|
||||||
|
using AuthenticationOptions = Configuration.AuthenticationOptions;
|
||||||
|
|
||||||
|
public class AuthenticationHandlerFactory : IAuthenticationHandlerFactory
|
||||||
|
{
|
||||||
|
private readonly IAuthenticationHandlerCreator _creator;
|
||||||
|
|
||||||
|
public AuthenticationHandlerFactory(IAuthenticationHandlerCreator creator)
|
||||||
|
{
|
||||||
|
_creator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response<AuthenticationHandler> Get(IApplicationBuilder app, AuthenticationOptions authOptions)
|
||||||
|
{
|
||||||
|
var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions);
|
||||||
|
|
||||||
|
if (!handler.IsError)
|
||||||
|
{
|
||||||
|
return new OkResponse<AuthenticationHandler>(
|
||||||
|
new AuthenticationHandler(authOptions.Provider, new RequestDelegateHandler(handler.Data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ErrorResponse<AuthenticationHandler>(new List<Error>
|
||||||
|
{
|
||||||
|
new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for {authOptions.Provider}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler.Factory
|
||||||
|
{
|
||||||
|
using AuthenticationOptions = Configuration.AuthenticationOptions;
|
||||||
|
|
||||||
|
public interface IAuthenticationHandlerFactory
|
||||||
|
{
|
||||||
|
Response<AuthenticationHandler> Get(IApplicationBuilder app, AuthenticationOptions authOptions);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler.Factory
|
||||||
|
{
|
||||||
|
public class UnableToCreateAuthenticationHandlerError : Error
|
||||||
|
{
|
||||||
|
public UnableToCreateAuthenticationHandlerError(string message)
|
||||||
|
: base(message, OcelotErrorCode.UnableToCreateAuthenticationHandlerError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Ocelot/Authentication/Handler/IHandler.cs
Normal file
10
src/Ocelot/Authentication/Handler/IHandler.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler
|
||||||
|
{
|
||||||
|
public interface IHandler
|
||||||
|
{
|
||||||
|
Task Handle(HttpContext context);
|
||||||
|
}
|
||||||
|
}
|
20
src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs
Normal file
20
src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Handler
|
||||||
|
{
|
||||||
|
public class RequestDelegateHandler : IHandler
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _requestDelegate;
|
||||||
|
|
||||||
|
public RequestDelegateHandler(RequestDelegate requestDelegate)
|
||||||
|
{
|
||||||
|
_requestDelegate = requestDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(HttpContext context)
|
||||||
|
{
|
||||||
|
await _requestDelegate.Invoke(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Authentication.Handler
|
||||||
|
{
|
||||||
|
public enum SupportedAuthenticationProviders
|
||||||
|
{
|
||||||
|
IdentityServer
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.Authentication.Handler.Factory;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Middleware
|
||||||
|
{
|
||||||
|
public class AuthenticationMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IApplicationBuilder _app;
|
||||||
|
private readonly IAuthenticationHandlerFactory _authHandlerFactory;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public AuthenticationMiddleware(RequestDelegate next,
|
||||||
|
IApplicationBuilder app,
|
||||||
|
IRequestScopedDataRepository requestScopedDataRepository,
|
||||||
|
IAuthenticationHandlerFactory authHandlerFactory,
|
||||||
|
IOcelotLoggerFactory loggerFactory)
|
||||||
|
: base(requestScopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_authHandlerFactory = authHandlerFactory;
|
||||||
|
_app = app;
|
||||||
|
_logger = loggerFactory.CreateLogger<AuthenticationMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("started authentication");
|
||||||
|
|
||||||
|
if (IsAuthenticatedRoute(DownstreamRoute.ReRoute))
|
||||||
|
{
|
||||||
|
var authenticationHandler = _authHandlerFactory.Get(_app, DownstreamRoute.ReRoute.AuthenticationOptions);
|
||||||
|
|
||||||
|
if (!authenticationHandler.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("calling authentication handler for ReRoute");
|
||||||
|
|
||||||
|
await authenticationHandler.Data.Handler.Handle(context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("there was an error getting authentication handler for ReRoute");
|
||||||
|
|
||||||
|
SetPipelineError(authenticationHandler.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("the user was authenticated");
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("the user was not authenticated");
|
||||||
|
|
||||||
|
SetPipelineError(new List<Error> { new UnauthenticatedError($"Request for authenticated route {context.Request.Path} by {context.User.Identity.Name} was unauthenticated") });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("calling next middleware");
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAuthenticatedRoute(ReRoute reRoute)
|
||||||
|
{
|
||||||
|
return reRoute.IsAuthenticated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication.Middleware
|
||||||
|
{
|
||||||
|
public static class AuthenticationMiddlewareMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<AuthenticationMiddleware>(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs
Normal file
12
src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
public class ClaimValueNotAuthorisedError : Error
|
||||||
|
{
|
||||||
|
public ClaimValueNotAuthorisedError(string message)
|
||||||
|
: base(message, OcelotErrorCode.ClaimValueNotAuthorisedError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
src/Ocelot/Authorisation/ClaimsAuthoriser.cs
Normal file
54
src/Ocelot/Authorisation/ClaimsAuthoriser.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
using Infrastructure.Claims.Parser;
|
||||||
|
|
||||||
|
public class ClaimsAuthoriser : IAuthoriser
|
||||||
|
{
|
||||||
|
private readonly IClaimsParser _claimsParser;
|
||||||
|
|
||||||
|
public ClaimsAuthoriser(IClaimsParser claimsParser)
|
||||||
|
{
|
||||||
|
_claimsParser = claimsParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, Dictionary<string, string> routeClaimsRequirement)
|
||||||
|
{
|
||||||
|
foreach (var required in routeClaimsRequirement)
|
||||||
|
{
|
||||||
|
var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0);
|
||||||
|
|
||||||
|
if (value.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(value.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Data != null)
|
||||||
|
{
|
||||||
|
var authorised = value.Data == required.Value;
|
||||||
|
if (!authorised)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(new List<Error>
|
||||||
|
{
|
||||||
|
new ClaimValueNotAuthorisedError(
|
||||||
|
$"claim value: {value.Data} is not the same as required value: {required.Value} for type: {required.Key}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(new List<Error>
|
||||||
|
{
|
||||||
|
new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new OkResponse<bool>(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Ocelot/Authorisation/IAuthoriser.cs
Normal file
13
src/Ocelot/Authorisation/IAuthoriser.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public interface IAuthoriser
|
||||||
|
{
|
||||||
|
Response<bool> Authorise(ClaimsPrincipal claimsPrincipal,
|
||||||
|
Dictionary<string, string> routeClaimsRequirement);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation.Middleware
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Errors;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
public class AuthorisationMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IAuthoriser _authoriser;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public AuthorisationMiddleware(RequestDelegate next,
|
||||||
|
IRequestScopedDataRepository requestScopedDataRepository,
|
||||||
|
IAuthoriser authoriser,
|
||||||
|
IOcelotLoggerFactory loggerFactory)
|
||||||
|
: base(requestScopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_authoriser = authoriser;
|
||||||
|
_logger = loggerFactory.CreateLogger<AuthorisationMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("started authorisation");
|
||||||
|
|
||||||
|
if (DownstreamRoute.ReRoute.IsAuthorised)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("route is authorised");
|
||||||
|
|
||||||
|
var authorised = _authoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement);
|
||||||
|
|
||||||
|
if (authorised.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("error authorising user");
|
||||||
|
|
||||||
|
SetPipelineError(authorised.Errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAuthorised(authorised))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("user is authorised calling next middleware");
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("user is not authorised setting pipeline error");
|
||||||
|
|
||||||
|
SetPipelineError(new List<Error>
|
||||||
|
{
|
||||||
|
new UnauthorisedError(
|
||||||
|
$"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamTemplate}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("AuthorisationMiddleware.Invoke route is not authorised calling next middleware");
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAuthorised(Response<bool> authorised)
|
||||||
|
{
|
||||||
|
return authorised.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ocelot.Authorisation.Middleware
|
||||||
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
public static class AuthorisationMiddlewareMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<AuthorisationMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Authorisation/UnauthorisedError.cs
Normal file
12
src/Ocelot/Authorisation/UnauthorisedError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
public class UnauthorisedError : Error
|
||||||
|
{
|
||||||
|
public UnauthorisedError(string message)
|
||||||
|
: base(message, OcelotErrorCode.UnauthorizedError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs
Normal file
12
src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
public class UserDoesNotHaveClaimError : Error
|
||||||
|
{
|
||||||
|
public UserDoesNotHaveClaimError(string message)
|
||||||
|
: base(message, OcelotErrorCode.UserDoesNotHaveClaimError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Ocelot/Cache/IOcelotCache.cs
Normal file
10
src/Ocelot/Cache/IOcelotCache.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ocelot.Cache
|
||||||
|
{
|
||||||
|
public interface IOcelotCache<T>
|
||||||
|
{
|
||||||
|
void Add(string key, T value, TimeSpan ttl);
|
||||||
|
T Get(string key);
|
||||||
|
}
|
||||||
|
}
|
74
src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
Normal file
74
src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
namespace Ocelot.Cache.Middleware
|
||||||
|
{
|
||||||
|
public class OutputCacheMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly IOcelotCache<HttpResponseMessage> _outputCache;
|
||||||
|
|
||||||
|
public OutputCacheMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IRequestScopedDataRepository scopedDataRepository,
|
||||||
|
IOcelotCache<HttpResponseMessage> outputCache)
|
||||||
|
:base(scopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_outputCache = outputCache;
|
||||||
|
_logger = loggerFactory.CreateLogger<OutputCacheMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
var downstreamUrlKey = DownstreamUrl;
|
||||||
|
|
||||||
|
if (!DownstreamRoute.ReRoute.IsCached)
|
||||||
|
{
|
||||||
|
await _next.Invoke(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey);
|
||||||
|
|
||||||
|
var cached = _outputCache.Get(downstreamUrlKey);
|
||||||
|
|
||||||
|
if (cached != null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey);
|
||||||
|
|
||||||
|
SetHttpResponseMessageThisRequest(cached);
|
||||||
|
|
||||||
|
_logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("no resonse cached for {downstreamUrlKey}", downstreamUrlKey);
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
|
||||||
|
if (PipelineError)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = HttpResponseMessage;
|
||||||
|
|
||||||
|
_outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds));
|
||||||
|
|
||||||
|
_logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Ocelot.Cache.Middleware
|
||||||
|
{
|
||||||
|
public static class OutputCacheMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<OutputCacheMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Ocelot/Cache/OcelotCacheManagerCache.cs
Normal file
25
src/Ocelot/Cache/OcelotCacheManagerCache.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using CacheManager.Core;
|
||||||
|
|
||||||
|
namespace Ocelot.Cache
|
||||||
|
{
|
||||||
|
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
|
||||||
|
{
|
||||||
|
private readonly ICacheManager<T> _cacheManager;
|
||||||
|
|
||||||
|
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
|
||||||
|
{
|
||||||
|
_cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string key, T value, TimeSpan ttl)
|
||||||
|
{
|
||||||
|
_cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl));
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get(string key)
|
||||||
|
{
|
||||||
|
return _cacheManager.Get<T>(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/Ocelot/Claims/AddClaimsToRequest.cs
Normal file
46
src/Ocelot/Claims/AddClaimsToRequest.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Infrastructure.Claims.Parser;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Claims
|
||||||
|
{
|
||||||
|
public class AddClaimsToRequest : IAddClaimsToRequest
|
||||||
|
{
|
||||||
|
private readonly IClaimsParser _claimsParser;
|
||||||
|
|
||||||
|
public AddClaimsToRequest(IClaimsParser claimsParser)
|
||||||
|
{
|
||||||
|
_claimsParser = claimsParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response SetClaimsOnContext(List<ClaimToThing> claimsToThings, HttpContext context)
|
||||||
|
{
|
||||||
|
foreach (var config in claimsToThings)
|
||||||
|
{
|
||||||
|
var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index);
|
||||||
|
|
||||||
|
if (value.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse(value.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists = context.User.Claims.FirstOrDefault(x => x.Type == config.ExistingKey);
|
||||||
|
|
||||||
|
var identity = context.User.Identity as ClaimsIdentity;
|
||||||
|
|
||||||
|
if (exists != null)
|
||||||
|
{
|
||||||
|
identity?.RemoveClaim(exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
identity?.AddClaim(new System.Security.Claims.Claim(config.ExistingKey, value.Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Ocelot/Claims/IAddClaimsToRequest.cs
Normal file
13
src/Ocelot/Claims/IAddClaimsToRequest.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Claims
|
||||||
|
{
|
||||||
|
public interface IAddClaimsToRequest
|
||||||
|
{
|
||||||
|
Response SetClaimsOnContext(List<ClaimToThing> claimsToThings,
|
||||||
|
HttpContext context);
|
||||||
|
}
|
||||||
|
}
|
54
src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs
Normal file
54
src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
namespace Ocelot.Claims.Middleware
|
||||||
|
{
|
||||||
|
public class ClaimsBuilderMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IAddClaimsToRequest _addClaimsToRequest;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public ClaimsBuilderMiddleware(RequestDelegate next,
|
||||||
|
IRequestScopedDataRepository requestScopedDataRepository,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IAddClaimsToRequest addClaimsToRequest)
|
||||||
|
: base(requestScopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_addClaimsToRequest = addClaimsToRequest;
|
||||||
|
_logger = loggerFactory.CreateLogger<ClaimsBuilderMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("started claims middleware");
|
||||||
|
|
||||||
|
if (DownstreamRoute.ReRoute.ClaimsToClaims.Any())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("this route has instructions to convert claims to other claims");
|
||||||
|
|
||||||
|
var result = _addClaimsToRequest.SetClaimsOnContext(DownstreamRoute.ReRoute.ClaimsToClaims, context);
|
||||||
|
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("error converting claims to other claims, setting pipeline error");
|
||||||
|
|
||||||
|
SetPipelineError(result.Errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("calling next middleware");
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Ocelot.Claims.Middleware
|
||||||
|
{
|
||||||
|
public static class ClaimsBuilderMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseClaimsBuilderMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<ClaimsBuilderMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Ocelot/Configuration/AuthenticationOptions.cs
Normal file
25
src/Ocelot/Configuration/AuthenticationOptions.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class AuthenticationOptions
|
||||||
|
{
|
||||||
|
public AuthenticationOptions(string provider, string providerRootUrl, string scopeName, bool requireHttps, List<string> additionalScopes, string scopeSecret)
|
||||||
|
{
|
||||||
|
Provider = provider;
|
||||||
|
ProviderRootUrl = providerRootUrl;
|
||||||
|
ScopeName = scopeName;
|
||||||
|
RequireHttps = requireHttps;
|
||||||
|
AdditionalScopes = additionalScopes;
|
||||||
|
ScopeSecret = scopeSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Provider { get; private set; }
|
||||||
|
public string ProviderRootUrl { get; private set; }
|
||||||
|
public string ScopeName { get; private set; }
|
||||||
|
public string ScopeSecret { get; private set; }
|
||||||
|
public bool RequireHttps { get; private set; }
|
||||||
|
public List<string> AdditionalScopes { get; private set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
232
src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
Normal file
232
src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Builder
|
||||||
|
{
|
||||||
|
public class ReRouteBuilder
|
||||||
|
{
|
||||||
|
private string _loadBalancerKey;
|
||||||
|
private string _downstreamPathTemplate;
|
||||||
|
private string _upstreamTemplate;
|
||||||
|
private string _upstreamTemplatePattern;
|
||||||
|
private string _upstreamHttpMethod;
|
||||||
|
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> _claimToClaims;
|
||||||
|
private Dictionary<string, string> _routeClaimRequirement;
|
||||||
|
private bool _isAuthorised;
|
||||||
|
private List<ClaimToThing> _claimToQueries;
|
||||||
|
private string _requestIdHeaderKey;
|
||||||
|
private bool _isCached;
|
||||||
|
private CacheOptions _fileCacheOptions;
|
||||||
|
private bool _useServiceDiscovery;
|
||||||
|
private string _serviceName;
|
||||||
|
private string _serviceDiscoveryProvider;
|
||||||
|
private string _serviceDiscoveryAddress;
|
||||||
|
private string _downstreamScheme;
|
||||||
|
private string _downstreamHost;
|
||||||
|
private int _dsPort;
|
||||||
|
private string _loadBalancer;
|
||||||
|
private string _serviceProviderHost;
|
||||||
|
private int _serviceProviderPort;
|
||||||
|
|
||||||
|
public ReRouteBuilder()
|
||||||
|
{
|
||||||
|
_additionalScopes = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
||||||
|
{
|
||||||
|
_loadBalancer = loadBalancer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
|
||||||
|
{
|
||||||
|
_downstreamScheme = downstreamScheme;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamHost(string downstreamHost)
|
||||||
|
{
|
||||||
|
_downstreamHost = downstreamHost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress)
|
||||||
|
{
|
||||||
|
_serviceDiscoveryAddress = serviceDiscoveryAddress;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider)
|
||||||
|
{
|
||||||
|
_serviceDiscoveryProvider = serviceDiscoveryProvider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithServiceName(string serviceName)
|
||||||
|
{
|
||||||
|
_serviceName = serviceName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery)
|
||||||
|
{
|
||||||
|
_useServiceDiscovery = useServiceDiscovery;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamPathTemplate(string input)
|
||||||
|
{
|
||||||
|
_downstreamPathTemplate = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithUpstreamTemplate(string input)
|
||||||
|
{
|
||||||
|
_upstreamTemplate = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithUpstreamTemplatePattern(string input)
|
||||||
|
{
|
||||||
|
_upstreamTemplatePattern = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ReRouteBuilder WithUpstreamHttpMethod(string input)
|
||||||
|
{
|
||||||
|
_upstreamHttpMethod = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ReRouteBuilder WithIsAuthenticated(bool input)
|
||||||
|
{
|
||||||
|
_isAuthenticated = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithIsAuthorised(bool input)
|
||||||
|
{
|
||||||
|
_isAuthorised = input;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_requestIdHeaderKey = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithClaimsToHeaders(List<ClaimToThing> input)
|
||||||
|
{
|
||||||
|
_configHeaderExtractorProperties = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithClaimsToClaims(List<ClaimToThing> input)
|
||||||
|
{
|
||||||
|
_claimToClaims = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithRouteClaimsRequirement(Dictionary<string, string> input)
|
||||||
|
{
|
||||||
|
_routeClaimRequirement = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithClaimsToQueries(List<ClaimToThing> input)
|
||||||
|
{
|
||||||
|
_claimToQueries = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithIsCached(bool input)
|
||||||
|
{
|
||||||
|
_isCached = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithCacheOptions(CacheOptions input)
|
||||||
|
{
|
||||||
|
_fileCacheOptions = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamPort(int port)
|
||||||
|
{
|
||||||
|
_dsPort = port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey)
|
||||||
|
{
|
||||||
|
_loadBalancerKey = loadBalancerKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost)
|
||||||
|
{
|
||||||
|
_serviceProviderHost = serviceProviderHost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort)
|
||||||
|
{
|
||||||
|
_serviceProviderPort = serviceProviderPort;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRoute Build()
|
||||||
|
{
|
||||||
|
return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
|
||||||
|
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
|
||||||
|
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
|
||||||
|
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer,
|
||||||
|
_downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Configuration/CacheOptions.cs
Normal file
12
src/Ocelot/Configuration/CacheOptions.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class CacheOptions
|
||||||
|
{
|
||||||
|
public CacheOptions(int ttlSeconds)
|
||||||
|
{
|
||||||
|
TtlSeconds = ttlSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TtlSeconds { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot/Configuration/ClaimToThing.cs
Normal file
18
src/Ocelot/Configuration/ClaimToThing.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class ClaimToThing
|
||||||
|
{
|
||||||
|
public ClaimToThing(string existingKey, string newKey, string delimiter, int index)
|
||||||
|
{
|
||||||
|
NewKey = newKey;
|
||||||
|
Delimiter = delimiter;
|
||||||
|
Index = index;
|
||||||
|
ExistingKey = existingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ExistingKey { get; private set; }
|
||||||
|
public string NewKey { get; private set; }
|
||||||
|
public string Delimiter { get; private set; }
|
||||||
|
public int Index { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Configuration.Parser;
|
||||||
|
using Ocelot.Configuration.Validator;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Utilities;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Creator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register as singleton
|
||||||
|
/// </summary>
|
||||||
|
public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator
|
||||||
|
{
|
||||||
|
private readonly IOptions<FileConfiguration> _options;
|
||||||
|
private readonly IConfigurationValidator _configurationValidator;
|
||||||
|
private const string RegExMatchEverything = ".*";
|
||||||
|
private const string RegExMatchEndString = "$";
|
||||||
|
private const string RegExIgnoreCase = "(?i)";
|
||||||
|
|
||||||
|
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
||||||
|
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
||||||
|
private readonly ILoadBalancerFactory _loadBalanceFactory;
|
||||||
|
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||||
|
|
||||||
|
public FileOcelotConfigurationCreator(
|
||||||
|
IOptions<FileConfiguration> options,
|
||||||
|
IConfigurationValidator configurationValidator,
|
||||||
|
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
||||||
|
ILogger<FileOcelotConfigurationCreator> logger,
|
||||||
|
ILoadBalancerFactory loadBalancerFactory,
|
||||||
|
ILoadBalancerHouse loadBalancerHouse)
|
||||||
|
{
|
||||||
|
_loadBalanceFactory = loadBalancerFactory;
|
||||||
|
_loadBalancerHouse = loadBalancerHouse;
|
||||||
|
_options = options;
|
||||||
|
_configurationValidator = configurationValidator;
|
||||||
|
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<IOcelotConfiguration>> Create()
|
||||||
|
{
|
||||||
|
var config = await SetUpConfiguration();
|
||||||
|
|
||||||
|
return new OkResponse<IOcelotConfiguration>(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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 async Task<IOcelotConfiguration> SetUpConfiguration()
|
||||||
|
{
|
||||||
|
var response = _configurationValidator.IsValid(_options.Value);
|
||||||
|
|
||||||
|
if (response.Data.IsError)
|
||||||
|
{
|
||||||
|
var errorBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var error in response.Errors)
|
||||||
|
{
|
||||||
|
errorBuilder.AppendLine(error.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Unable to start Ocelot..configuration, errors were {errorBuilder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var reRoutes = new List<ReRoute>();
|
||||||
|
|
||||||
|
foreach (var reRoute in _options.Value.ReRoutes)
|
||||||
|
{
|
||||||
|
var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
|
||||||
|
reRoutes.Add(ocelotReRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OcelotConfiguration(reRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
|
{
|
||||||
|
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
||||||
|
|
||||||
|
var upstreamTemplate = BuildUpstreamTemplate(fileReRoute);
|
||||||
|
|
||||||
|
var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
|
||||||
|
|
||||||
|
var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||||
|
|
||||||
|
var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||||
|
|
||||||
|
var requestIdKey = globalRequestIdConfiguration
|
||||||
|
? globalConfiguration.RequestIdKey
|
||||||
|
: fileReRoute.RequestIdKey;
|
||||||
|
|
||||||
|
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
|
||||||
|
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
|
||||||
|
|
||||||
|
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
|
||||||
|
var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}";
|
||||||
|
|
||||||
|
ReRoute reRoute;
|
||||||
|
|
||||||
|
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||||
|
|
||||||
|
var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName,
|
||||||
|
fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery,
|
||||||
|
globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host,
|
||||||
|
serviceProviderPort);
|
||||||
|
|
||||||
|
if (isAuthenticated)
|
||||||
|
{
|
||||||
|
var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider,
|
||||||
|
fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName,
|
||||||
|
fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes,
|
||||||
|
fileReRoute.AuthenticationOptions.ScopeSecret);
|
||||||
|
|
||||||
|
var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest);
|
||||||
|
var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest);
|
||||||
|
var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest);
|
||||||
|
|
||||||
|
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
|
||||||
|
fileReRoute.UpstreamTemplate,
|
||||||
|
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||||
|
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
||||||
|
fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||||
|
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)
|
||||||
|
, fileReRoute.DownstreamScheme,
|
||||||
|
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
|
||||||
|
serviceProviderConfiguration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
|
||||||
|
fileReRoute.UpstreamTemplate,
|
||||||
|
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||||
|
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
||||||
|
fileReRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
||||||
|
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds),
|
||||||
|
fileReRoute.DownstreamScheme,
|
||||||
|
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
|
||||||
|
serviceProviderConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
|
||||||
|
_loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
|
return reRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildUpstreamTemplate(FileReRoute reRoute)
|
||||||
|
{
|
||||||
|
var upstreamTemplate = reRoute.UpstreamTemplate;
|
||||||
|
|
||||||
|
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
|
||||||
|
|
||||||
|
var placeholders = new List<string>();
|
||||||
|
|
||||||
|
for (var i = 0; i < upstreamTemplate.Length; i++)
|
||||||
|
{
|
||||||
|
if (IsPlaceHolder(upstreamTemplate, i))
|
||||||
|
{
|
||||||
|
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
|
||||||
|
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
|
||||||
|
var variableName = upstreamTemplate.Substring(i, difference);
|
||||||
|
placeholders.Add(variableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var placeholder in placeholders)
|
||||||
|
{
|
||||||
|
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
|
||||||
|
}
|
||||||
|
|
||||||
|
var route = reRoute.ReRouteIsCaseSensitive
|
||||||
|
? $"{upstreamTemplate}{RegExMatchEndString}"
|
||||||
|
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
||||||
|
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClaimToThing> GetAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
|
||||||
|
{
|
||||||
|
var claimsToTHings = new List<ClaimToThing>();
|
||||||
|
|
||||||
|
foreach (var add in thingBeingAdded)
|
||||||
|
{
|
||||||
|
var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value);
|
||||||
|
|
||||||
|
if (claimToHeader.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogCritical(new EventId(1, "Application Failed to start"),
|
||||||
|
$"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect");
|
||||||
|
|
||||||
|
throw new Exception(claimToHeader.Errors[0].Message);
|
||||||
|
}
|
||||||
|
claimsToTHings.Add(claimToHeader.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return claimsToTHings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPlaceHolder(string upstreamTemplate, int i)
|
||||||
|
{
|
||||||
|
return upstreamTemplate[i] == '{';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Creator
|
||||||
|
{
|
||||||
|
public interface IOcelotConfigurationCreator
|
||||||
|
{
|
||||||
|
Task<Response<IOcelotConfiguration>> Create();
|
||||||
|
}
|
||||||
|
}
|
19
src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
Normal file
19
src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileAuthenticationOptions
|
||||||
|
{
|
||||||
|
public FileAuthenticationOptions()
|
||||||
|
{
|
||||||
|
AdditionalScopes = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Provider { get; set; }
|
||||||
|
public string ProviderRootUrl { get; set; }
|
||||||
|
public string ScopeName { get; set; }
|
||||||
|
public bool RequireHttps { get; set; }
|
||||||
|
public List<string> AdditionalScopes { get; set; }
|
||||||
|
public string ScopeSecret { get; set; }
|
||||||
|
}
|
||||||
|
}
|
7
src/Ocelot/Configuration/File/FileCacheOptions.cs
Normal file
7
src/Ocelot/Configuration/File/FileCacheOptions.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileCacheOptions
|
||||||
|
{
|
||||||
|
public int TtlSeconds { get; set; }
|
||||||
|
}
|
||||||
|
}
|
16
src/Ocelot/Configuration/File/FileConfiguration.cs
Normal file
16
src/Ocelot/Configuration/File/FileConfiguration.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileConfiguration
|
||||||
|
{
|
||||||
|
public FileConfiguration()
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>();
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FileReRoute> ReRoutes { get; set; }
|
||||||
|
public FileGlobalConfiguration GlobalConfiguration { get; set; }
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
Normal file
12
src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileGlobalConfiguration
|
||||||
|
{
|
||||||
|
public FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
|
||||||
|
}
|
||||||
|
public string RequestIdKey { get; set; }
|
||||||
|
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
|
||||||
|
}
|
||||||
|
}
|
34
src/Ocelot/Configuration/File/FileReRoute.cs
Normal file
34
src/Ocelot/Configuration/File/FileReRoute.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileReRoute
|
||||||
|
{
|
||||||
|
public FileReRoute()
|
||||||
|
{
|
||||||
|
AddHeadersToRequest = new Dictionary<string, string>();
|
||||||
|
AddClaimsToRequest = new Dictionary<string, string>();
|
||||||
|
RouteClaimsRequirement = new Dictionary<string, string>();
|
||||||
|
AddQueriesToRequest = new Dictionary<string, string>();
|
||||||
|
AuthenticationOptions = new FileAuthenticationOptions();
|
||||||
|
FileCacheOptions = new FileCacheOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DownstreamPathTemplate { get; set; }
|
||||||
|
public string UpstreamTemplate { get; set; }
|
||||||
|
public string UpstreamHttpMethod { get; set; }
|
||||||
|
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
||||||
|
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
||||||
|
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
||||||
|
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
||||||
|
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
||||||
|
public string RequestIdKey { get; set; }
|
||||||
|
public FileCacheOptions FileCacheOptions { 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 string LoadBalancer {get;set;}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
public string Provider {get;set;}
|
||||||
|
public string Host {get;set;}
|
||||||
|
public int Port { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
src/Ocelot/Configuration/IOcelotConfiguration.cs
Normal file
9
src/Ocelot/Configuration/IOcelotConfiguration.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public interface IOcelotConfiguration
|
||||||
|
{
|
||||||
|
List<ReRoute> ReRoutes { get; }
|
||||||
|
}
|
||||||
|
}
|
14
src/Ocelot/Configuration/OcelotConfiguration.cs
Normal file
14
src/Ocelot/Configuration/OcelotConfiguration.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class OcelotConfiguration : IOcelotConfiguration
|
||||||
|
{
|
||||||
|
public OcelotConfiguration(List<ReRoute> reRoutes)
|
||||||
|
{
|
||||||
|
ReRoutes = reRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ReRoute> ReRoutes { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Parser
|
||||||
|
{
|
||||||
|
public class ClaimToThingConfigurationParser : IClaimToThingConfigurationParser
|
||||||
|
{
|
||||||
|
private readonly Regex _claimRegex = new Regex("Claims\\[.*\\]");
|
||||||
|
private readonly Regex _indexRegex = new Regex("value\\[.*\\]");
|
||||||
|
private const string SplitToken = ">";
|
||||||
|
|
||||||
|
public Response<ClaimToThing> Extract(string existingKey, string value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var instructions = value.Split(SplitToken.ToCharArray());
|
||||||
|
|
||||||
|
if (instructions.Length <= 1)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ClaimToThing>(
|
||||||
|
new List<Error>
|
||||||
|
{
|
||||||
|
new NoInstructionsError(SplitToken)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimMatch = _claimRegex.IsMatch(instructions[0]);
|
||||||
|
|
||||||
|
if (!claimMatch)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ClaimToThing>(
|
||||||
|
new List<Error>
|
||||||
|
{
|
||||||
|
new InstructionNotForClaimsError()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var newKey = GetIndexValue(instructions[0]);
|
||||||
|
var index = 0;
|
||||||
|
var delimiter = string.Empty;
|
||||||
|
|
||||||
|
if (instructions.Length > 2 && _indexRegex.IsMatch(instructions[1]))
|
||||||
|
{
|
||||||
|
index = int.Parse(GetIndexValue(instructions[1]));
|
||||||
|
delimiter = instructions[2].Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<ClaimToThing>(
|
||||||
|
new ClaimToThing(existingKey, newKey, delimiter, index));
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ClaimToThing>(
|
||||||
|
new List<Error>
|
||||||
|
{
|
||||||
|
new ParsingConfigurationHeaderError(exception)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetIndexValue(string instruction)
|
||||||
|
{
|
||||||
|
var firstIndexer = instruction.IndexOf("[", StringComparison.Ordinal);
|
||||||
|
var lastIndexer = instruction.IndexOf("]", StringComparison.Ordinal);
|
||||||
|
var length = lastIndexer - firstIndexer;
|
||||||
|
var claimKey = instruction.Substring(firstIndexer + 1, length - 1);
|
||||||
|
return claimKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Parser
|
||||||
|
{
|
||||||
|
public interface IClaimToThingConfigurationParser
|
||||||
|
{
|
||||||
|
Response<ClaimToThing> Extract(string existingKey, string value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Parser
|
||||||
|
{
|
||||||
|
public class InstructionNotForClaimsError : Error
|
||||||
|
{
|
||||||
|
public InstructionNotForClaimsError()
|
||||||
|
: base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Configuration/Parser/NoInstructionsError.cs
Normal file
12
src/Ocelot/Configuration/Parser/NoInstructionsError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Parser
|
||||||
|
{
|
||||||
|
public class NoInstructionsError : Error
|
||||||
|
{
|
||||||
|
public NoInstructionsError(string splitToken)
|
||||||
|
: base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Parser
|
||||||
|
{
|
||||||
|
public class ParsingConfigurationHeaderError : Error
|
||||||
|
{
|
||||||
|
public ParsingConfigurationHeaderError(Exception exception)
|
||||||
|
: base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Provider
|
||||||
|
{
|
||||||
|
public interface IOcelotConfigurationProvider
|
||||||
|
{
|
||||||
|
Task<Response<IOcelotConfiguration>> Get();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using Ocelot.Configuration.Repository;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Provider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register as singleton
|
||||||
|
/// </summary>
|
||||||
|
public class OcelotConfigurationProvider : IOcelotConfigurationProvider
|
||||||
|
{
|
||||||
|
private readonly IOcelotConfigurationRepository _repo;
|
||||||
|
private readonly IOcelotConfigurationCreator _creator;
|
||||||
|
|
||||||
|
public OcelotConfigurationProvider(IOcelotConfigurationRepository repo,
|
||||||
|
IOcelotConfigurationCreator creator)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
_creator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<IOcelotConfiguration>> Get()
|
||||||
|
{
|
||||||
|
var repoConfig = _repo.Get();
|
||||||
|
|
||||||
|
if (repoConfig.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<IOcelotConfiguration>(repoConfig.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repoConfig.Data == null)
|
||||||
|
{
|
||||||
|
var creatorConfig = await _creator.Create();
|
||||||
|
|
||||||
|
if (creatorConfig.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<IOcelotConfiguration>(creatorConfig.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
_repo.AddOrReplace(creatorConfig.Data);
|
||||||
|
|
||||||
|
return new OkResponse<IOcelotConfiguration>(creatorConfig.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<IOcelotConfiguration>(repoConfig.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
src/Ocelot/Configuration/ReRoute.cs
Normal file
67
src/Ocelot/Configuration/ReRoute.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class ReRoute
|
||||||
|
{
|
||||||
|
public ReRoute(DownstreamPathTemplate downstreamPathTemplate,
|
||||||
|
string upstreamTemplate, string upstreamHttpMethod,
|
||||||
|
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 loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion)
|
||||||
|
{
|
||||||
|
LoadBalancerKey = loadBalancerKey;
|
||||||
|
ServiceProviderConfiguraion = serviceProviderConfiguraion;
|
||||||
|
LoadBalancer = loadBalancer;
|
||||||
|
DownstreamHost = downstreamHost;
|
||||||
|
DownstreamPort = downstreamPort;
|
||||||
|
DownstreamPathTemplate = downstreamPathTemplate;
|
||||||
|
UpstreamTemplate = upstreamTemplate;
|
||||||
|
UpstreamHttpMethod = upstreamHttpMethod;
|
||||||
|
UpstreamTemplatePattern = upstreamTemplatePattern;
|
||||||
|
IsAuthenticated = isAuthenticated;
|
||||||
|
AuthenticationOptions = authenticationOptions;
|
||||||
|
RouteClaimsRequirement = routeClaimsRequirement;
|
||||||
|
IsAuthorised = isAuthorised;
|
||||||
|
RequestIdKey = requestIdKey;
|
||||||
|
IsCached = isCached;
|
||||||
|
FileCacheOptions = fileCacheOptions;
|
||||||
|
ClaimsToQueries = claimsToQueries
|
||||||
|
?? new List<ClaimToThing>();
|
||||||
|
ClaimsToClaims = claimsToClaims
|
||||||
|
?? new List<ClaimToThing>();
|
||||||
|
ClaimsToHeaders = configurationHeaderExtractorProperties
|
||||||
|
?? new List<ClaimToThing>();
|
||||||
|
DownstreamScheme = downstreamScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LoadBalancerKey {get;private set;}
|
||||||
|
public DownstreamPathTemplate DownstreamPathTemplate { get; private set; }
|
||||||
|
public string UpstreamTemplate { get; private set; }
|
||||||
|
public string UpstreamTemplatePattern { get; private set; }
|
||||||
|
public string UpstreamHttpMethod { get; private set; }
|
||||||
|
public bool IsAuthenticated { get; private set; }
|
||||||
|
public bool IsAuthorised { get; private set; }
|
||||||
|
public AuthenticationOptions AuthenticationOptions { get; private set; }
|
||||||
|
public List<ClaimToThing> ClaimsToQueries { get; private set; }
|
||||||
|
public List<ClaimToThing> ClaimsToHeaders { get; private set; }
|
||||||
|
public List<ClaimToThing> ClaimsToClaims { get; private set; }
|
||||||
|
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
|
||||||
|
public string RequestIdKey { get; private set; }
|
||||||
|
public bool IsCached { get; private set; }
|
||||||
|
public CacheOptions FileCacheOptions { get; private set; }
|
||||||
|
public string DownstreamScheme {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; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Repository
|
||||||
|
{
|
||||||
|
public interface IOcelotConfigurationRepository
|
||||||
|
{
|
||||||
|
Response<IOcelotConfiguration> Get();
|
||||||
|
Response AddOrReplace(IOcelotConfiguration ocelotConfiguration);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Repository
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register as singleton
|
||||||
|
/// </summary>
|
||||||
|
public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository
|
||||||
|
{
|
||||||
|
private static readonly object LockObject = new object();
|
||||||
|
|
||||||
|
private IOcelotConfiguration _ocelotConfiguration;
|
||||||
|
|
||||||
|
public Response<IOcelotConfiguration> Get()
|
||||||
|
{
|
||||||
|
return new OkResponse<IOcelotConfiguration>(_ocelotConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration)
|
||||||
|
{
|
||||||
|
lock (LockObject)
|
||||||
|
{
|
||||||
|
_ocelotConfiguration = ocelotConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class ConfigurationValidationResult
|
||||||
|
{
|
||||||
|
public ConfigurationValidationResult(bool isError)
|
||||||
|
{
|
||||||
|
IsError = isError;
|
||||||
|
Errors = new List<Error>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationResult(bool isError, List<Error> errors)
|
||||||
|
{
|
||||||
|
IsError = isError;
|
||||||
|
Errors = errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsError { get; private set; }
|
||||||
|
|
||||||
|
public List<Error> Errors { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs
Normal file
115
src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Ocelot.Authentication.Handler;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class FileConfigurationValidator : IConfigurationValidator
|
||||||
|
{
|
||||||
|
public Response<ConfigurationValidationResult> IsValid(FileConfiguration configuration)
|
||||||
|
{
|
||||||
|
var result = CheckForDupliateReRoutes(configuration);
|
||||||
|
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CheckForUnsupportedAuthenticationProviders(configuration);
|
||||||
|
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration);
|
||||||
|
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigurationValidationResult CheckForUnsupportedAuthenticationProviders(FileConfiguration configuration)
|
||||||
|
{
|
||||||
|
var errors = new List<Error>();
|
||||||
|
|
||||||
|
foreach (var reRoute in configuration.ReRoutes)
|
||||||
|
{
|
||||||
|
var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider);
|
||||||
|
|
||||||
|
if (!isAuthenticated)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsSupportedAuthenticationProvider(reRoute.AuthenticationOptions?.Provider))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamTemplate}, upstream method is {reRoute.UpstreamHttpMethod}");
|
||||||
|
errors.Add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Count > 0
|
||||||
|
? new ConfigurationValidationResult(true, errors)
|
||||||
|
: new ConfigurationValidationResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSupportedAuthenticationProvider(string provider)
|
||||||
|
{
|
||||||
|
SupportedAuthenticationProviders 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)
|
||||||
|
{
|
||||||
|
var hasDupes = configuration.ReRoutes
|
||||||
|
.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any());
|
||||||
|
|
||||||
|
if (!hasDupes)
|
||||||
|
{
|
||||||
|
return new ConfigurationValidationResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod })
|
||||||
|
.Where(x => x.Skip(1).Any());
|
||||||
|
|
||||||
|
var errors = dupes
|
||||||
|
.Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate)))
|
||||||
|
.Cast<Error>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new ConfigurationValidationResult(true, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public interface IConfigurationValidator
|
||||||
|
{
|
||||||
|
Response<ConfigurationValidationResult> IsValid(FileConfiguration configuration);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class UnsupportedAuthenticationProviderError : Error
|
||||||
|
{
|
||||||
|
public UnsupportedAuthenticationProviderError(string message)
|
||||||
|
: base(message, OcelotErrorCode.UnsupportedAuthenticationProviderError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using CacheManager.Core;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.Authentication.Handler.Creator;
|
||||||
|
using Ocelot.Authentication.Handler.Factory;
|
||||||
|
using Ocelot.Authorisation;
|
||||||
|
using Ocelot.Cache;
|
||||||
|
using Ocelot.Claims;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Configuration.Parser;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using Ocelot.Configuration.Repository;
|
||||||
|
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.QueryStrings;
|
||||||
|
using Ocelot.Request.Builder;
|
||||||
|
using Ocelot.Requester;
|
||||||
|
using Ocelot.Responder;
|
||||||
|
using Ocelot.ServiceDiscovery;
|
||||||
|
|
||||||
|
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 AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
||||||
|
{
|
||||||
|
services.Configure<FileConfiguration>(configurationRoot);
|
||||||
|
|
||||||
|
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
||||||
|
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
||||||
|
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddOcelot(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddMvcCore().AddJsonFormatters();
|
||||||
|
services.AddLogging();
|
||||||
|
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>();
|
||||||
|
|
||||||
|
// 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>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs
Normal file
17
src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder
|
||||||
|
{
|
||||||
|
public class DownstreamRoute
|
||||||
|
{
|
||||||
|
public DownstreamRoute(List<UrlPathPlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
|
||||||
|
{
|
||||||
|
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
|
||||||
|
ReRoute = reRoute;
|
||||||
|
}
|
||||||
|
public List<UrlPathPlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
||||||
|
public ReRoute ReRoute { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||||
|
{
|
||||||
|
public class DownstreamRouteFinder : IDownstreamRouteFinder
|
||||||
|
{
|
||||||
|
private readonly IOcelotConfigurationProvider _configProvider;
|
||||||
|
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
|
||||||
|
private readonly IUrlPathPlaceholderNameAndValueFinder _urlPathPlaceholderNameAndValueFinder;
|
||||||
|
|
||||||
|
public DownstreamRouteFinder(IOcelotConfigurationProvider configProvider, IUrlPathToUrlTemplateMatcher urlMatcher, IUrlPathPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder)
|
||||||
|
{
|
||||||
|
_configProvider = configProvider;
|
||||||
|
_urlMatcher = urlMatcher;
|
||||||
|
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
|
||||||
|
{
|
||||||
|
var configuration = await _configProvider.Get();
|
||||||
|
|
||||||
|
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
||||||
|
foreach (var reRoute in applicableReRoutes)
|
||||||
|
{
|
||||||
|
var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);
|
||||||
|
|
||||||
|
if (urlMatch.Data.Match)
|
||||||
|
{
|
||||||
|
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate);
|
||||||
|
|
||||||
|
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ErrorResponse<DownstreamRoute>(new List<Error>
|
||||||
|
{
|
||||||
|
new UnableToFindDownstreamRouteError()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||||
|
{
|
||||||
|
public interface IDownstreamRouteFinder
|
||||||
|
{
|
||||||
|
Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||||
|
{
|
||||||
|
public class UnableToFindDownstreamRouteError : Error
|
||||||
|
{
|
||||||
|
public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError", OcelotErrorCode.UnableToFindDownstreamRouteError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Finder;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Utilities;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||||
|
{
|
||||||
|
public class DownstreamRouteFinderMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IDownstreamRouteFinder _downstreamRouteFinder;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public DownstreamRouteFinderMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IDownstreamRouteFinder downstreamRouteFinder,
|
||||||
|
IRequestScopedDataRepository requestScopedDataRepository)
|
||||||
|
:base(requestScopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_downstreamRouteFinder = downstreamRouteFinder;
|
||||||
|
_logger = loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("started calling downstream route finder middleware");
|
||||||
|
|
||||||
|
var upstreamUrlPath = context.Request.Path.ToString().SetLastCharacterAs('/');
|
||||||
|
|
||||||
|
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath);
|
||||||
|
|
||||||
|
var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
|
||||||
|
|
||||||
|
if (downstreamRoute.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("IDownstreamRouteFinder returned an error, setting pipeline error");
|
||||||
|
|
||||||
|
SetPipelineError(downstreamRoute.Errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate);
|
||||||
|
|
||||||
|
SetDownstreamRouteForThisRequest(downstreamRoute.Data);
|
||||||
|
|
||||||
|
_logger.LogDebug("calling next middleware");
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
_logger.LogDebug("succesfully called next middleware");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||||
|
{
|
||||||
|
public static class DownstreamRouteFinderMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<DownstreamRouteFinderMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
|
{
|
||||||
|
public interface IUrlPathPlaceholderNameAndValueFinder
|
||||||
|
{
|
||||||
|
Response<List<UrlPathPlaceholderNameAndValue>> Find(string upstreamUrlPath, string upstreamUrlPathTemplate);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
|
{
|
||||||
|
public interface IUrlPathToUrlTemplateMatcher
|
||||||
|
{
|
||||||
|
Response<UrlMatch> Match(string upstreamUrlPath, string upstreamUrlPathTemplate);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
|
{
|
||||||
|
public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
|
||||||
|
{
|
||||||
|
public Response<UrlMatch> Match(string upstreamUrlPath, string upstreamUrlPathTemplate)
|
||||||
|
{
|
||||||
|
var regex = new Regex(upstreamUrlPathTemplate);
|
||||||
|
|
||||||
|
return regex.IsMatch(upstreamUrlPath)
|
||||||
|
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
||||||
|
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs
Normal file
11
src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
|
{
|
||||||
|
public class UrlMatch
|
||||||
|
{
|
||||||
|
public UrlMatch(bool match)
|
||||||
|
{
|
||||||
|
Match = match;
|
||||||
|
}
|
||||||
|
public bool Match {get;private set;}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
|
{
|
||||||
|
public class UrlPathPlaceholderNameAndValue
|
||||||
|
{
|
||||||
|
public UrlPathPlaceholderNameAndValue(string templateVariableName, string templateVariableValue)
|
||||||
|
{
|
||||||
|
TemplateVariableName = templateVariableName;
|
||||||
|
TemplateVariableValue = templateVariableValue;
|
||||||
|
}
|
||||||
|
public string TemplateVariableName {get;private set;}
|
||||||
|
public string TemplateVariableValue {get;private set;}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
|
{
|
||||||
|
public class UrlPathPlaceholderNameAndValueFinder : IUrlPathPlaceholderNameAndValueFinder
|
||||||
|
{
|
||||||
|
public Response<List<UrlPathPlaceholderNameAndValue>> Find(string upstreamUrlPath, string upstreamUrlPathTemplate)
|
||||||
|
{
|
||||||
|
var templateKeysAndValues = new List<UrlPathPlaceholderNameAndValue>();
|
||||||
|
|
||||||
|
int counterForUrl = 0;
|
||||||
|
|
||||||
|
for (int counterForTemplate = 0; counterForTemplate < upstreamUrlPathTemplate.Length; counterForTemplate++)
|
||||||
|
{
|
||||||
|
if (CharactersDontMatch(upstreamUrlPathTemplate[counterForTemplate], upstreamUrlPath[counterForUrl]) && ContinueScanningUrl(counterForUrl,upstreamUrlPath.Length))
|
||||||
|
{
|
||||||
|
if (IsPlaceholder(upstreamUrlPathTemplate[counterForTemplate]))
|
||||||
|
{
|
||||||
|
var variableName = GetPlaceholderVariableName(upstreamUrlPathTemplate, counterForTemplate);
|
||||||
|
|
||||||
|
var variableValue = GetPlaceholderVariableValue(upstreamUrlPath, counterForUrl);
|
||||||
|
|
||||||
|
var templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, variableValue);
|
||||||
|
|
||||||
|
templateKeysAndValues.Add(templateVariableNameAndValue);
|
||||||
|
|
||||||
|
counterForTemplate = GetNextCounterPosition(upstreamUrlPathTemplate, counterForTemplate, '}');
|
||||||
|
|
||||||
|
counterForUrl = GetNextCounterPosition(upstreamUrlPath, counterForUrl, '/');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<List<UrlPathPlaceholderNameAndValue>>(templateKeysAndValues);
|
||||||
|
}
|
||||||
|
counterForUrl++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<List<UrlPathPlaceholderNameAndValue>>(templateKeysAndValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlaceholderVariableValue(string urlPath, int counterForUrl)
|
||||||
|
{
|
||||||
|
var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl);
|
||||||
|
|
||||||
|
if(positionOfNextSlash == -1)
|
||||||
|
{
|
||||||
|
positionOfNextSlash = urlPath.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl);
|
||||||
|
|
||||||
|
return variableValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlaceholderVariableName(string urlPathTemplate, int counterForTemplate)
|
||||||
|
{
|
||||||
|
var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1;
|
||||||
|
|
||||||
|
var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate);
|
||||||
|
|
||||||
|
return variableName;
|
||||||
|
}
|
||||||
|
private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter)
|
||||||
|
{
|
||||||
|
var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate);
|
||||||
|
return closingPlaceHolderPositionOnTemplate + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CharactersDontMatch(char characterOne, char characterTwo)
|
||||||
|
{
|
||||||
|
return characterOne != characterTwo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ContinueScanningUrl(int counterForUrl, int urlLength)
|
||||||
|
{
|
||||||
|
return counterForUrl < urlLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPlaceholder(char character)
|
||||||
|
{
|
||||||
|
return character == '{';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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