mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-23 00:52:51 +08:00
Merge branch 'release/1.1.0'
This commit is contained in:
commit
19f96b72d8
4
src/Ocelot.Library/.gitignore → .gitignore
vendored
4
src/Ocelot.Library/.gitignore → .gitignore
vendored
@ -21,11 +21,14 @@ build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
results/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
.vscode/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
site/wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
@ -232,3 +235,4 @@ _Pvt_Extensions
|
||||
|
||||
# FAKE - F# Make
|
||||
.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>
|
76
Ocelot.sln
Normal file
76
Ocelot.sln
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
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-run-tests.ps1 = build-and-run-tests.ps1
|
||||
build.cake = build.cake
|
||||
build.ps1 = build.ps1
|
||||
configuration-explanation.txt = configuration-explanation.txt
|
||||
global.json = global.json
|
||||
LICENSE.md = LICENSE.md
|
||||
Ocelot.nuspec = Ocelot.nuspec
|
||||
README.md = README.md
|
||||
release.ps1 = release.ps1
|
||||
run-acceptance-tests.ps1 = run-acceptance-tests.ps1
|
||||
run-benchmarks.bat = run-benchmarks.bat
|
||||
run-benchmarks.ps1 = run-benchmarks.ps1
|
||||
run-unit-tests.ps1 = run-unit-tests.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
|
388
README.md
388
README.md
@ -1,3 +1,391 @@
|
||||
# Ocelot
|
||||
|
||||
[](https://ci.appveyor.com/project/TomPallister/ocelot)
|
||||
|
||||
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
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.4 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
|
||||
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 -Pre`
|
||||
|
||||
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!
|
||||
|
||||
## 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 buildVersion = committedVersion;
|
||||
var committedVersion = "0.0.0-dev";
|
||||
|
||||
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.
|
||||
|
91
configuration-explanation.txt
Normal file
91
configuration-explanation.txt
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"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
|
||||
},
|
||||
# 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",
|
||||
}
|
||||
}
|
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; }
|
||||
|
||||
}
|
||||
}
|
206
src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
Normal file
206
src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
Normal file
@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.Configuration.Builder
|
||||
{
|
||||
public class ReRouteBuilder
|
||||
{
|
||||
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;
|
||||
|
||||
public ReRouteBuilder()
|
||||
{
|
||||
_additionalScopes = new List<string>();
|
||||
}
|
||||
|
||||
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 ReRoute Build()
|
||||
{
|
||||
Func<HostAndPort> downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort);
|
||||
|
||||
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, _serviceName,
|
||||
_useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme);
|
||||
}
|
||||
}
|
||||
}
|
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,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Parser;
|
||||
using Ocelot.Configuration.Validator;
|
||||
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;
|
||||
|
||||
public FileOcelotConfigurationCreator(
|
||||
IOptions<FileConfiguration> options,
|
||||
IConfigurationValidator configurationValidator,
|
||||
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
||||
ILogger<FileOcelotConfigurationCreator> logger)
|
||||
{
|
||||
_options = options;
|
||||
_configurationValidator = configurationValidator;
|
||||
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Response<IOcelotConfiguration> Create()
|
||||
{
|
||||
var config = 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 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 = SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
|
||||
reRoutes.Add(ocelotReRoute);
|
||||
}
|
||||
|
||||
return new OcelotConfiguration(reRoutes);
|
||||
}
|
||||
|
||||
private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
||||
|
||||
var upstreamTemplate = BuildUpstreamTemplate(reRoute);
|
||||
|
||||
var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider);
|
||||
|
||||
var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0;
|
||||
|
||||
var isCached = reRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
|
||||
var requestIdKey = globalRequestIdConfiguration
|
||||
? globalConfiguration.RequestIdKey
|
||||
: reRoute.RequestIdKey;
|
||||
|
||||
var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName)
|
||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address)
|
||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
|
||||
|
||||
|
||||
Func<HostAndPort> downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort);
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
|
||||
reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName,
|
||||
reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes,
|
||||
reRoute.AuthenticationOptions.ScopeSecret);
|
||||
|
||||
var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest);
|
||||
var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest);
|
||||
var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest);
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
||||
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme);
|
||||
}
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
||||
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme);
|
||||
}
|
||||
|
||||
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,9 @@
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public interface IOcelotConfigurationCreator
|
||||
{
|
||||
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;}
|
||||
}
|
||||
}
|
33
src/Ocelot/Configuration/File/FileReRoute.cs
Normal file
33
src/Ocelot/Configuration/File/FileReRoute.cs
Normal file
@ -0,0 +1,33 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileServiceDiscoveryProvider
|
||||
{
|
||||
public string Provider {get;set;}
|
||||
public string Address {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,9 @@
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public interface IOcelotConfigurationProvider
|
||||
{
|
||||
Response<IOcelotConfiguration> Get();
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
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 Response<IOcelotConfiguration> Get()
|
||||
{
|
||||
var repoConfig = _repo.Get();
|
||||
|
||||
if (repoConfig.IsError)
|
||||
{
|
||||
return new ErrorResponse<IOcelotConfiguration>(repoConfig.Errors);
|
||||
}
|
||||
|
||||
if (repoConfig.Data == null)
|
||||
{
|
||||
var creatorConfig = _creator.Create();
|
||||
|
||||
if (creatorConfig.IsError)
|
||||
{
|
||||
return new ErrorResponse<IOcelotConfiguration>(creatorConfig.Errors);
|
||||
}
|
||||
|
||||
_repo.AddOrReplace(creatorConfig.Data);
|
||||
|
||||
return new OkResponse<IOcelotConfiguration>(creatorConfig.Data);
|
||||
}
|
||||
|
||||
return new OkResponse<IOcelotConfiguration>(repoConfig.Data);
|
||||
}
|
||||
}
|
||||
}
|
61
src/Ocelot/Configuration/ReRoute.cs
Normal file
61
src/Ocelot/Configuration/ReRoute.cs
Normal file
@ -0,0 +1,61 @@
|
||||
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 serviceName, bool useServiceDiscovery,
|
||||
string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func<HostAndPort> downstreamHostAndPort, string downstreamScheme)
|
||||
{
|
||||
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>();
|
||||
ServiceName = serviceName;
|
||||
UseServiceDiscovery = useServiceDiscovery;
|
||||
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
||||
ServiceDiscoveryAddress = serviceDiscoveryAddress;
|
||||
DownstreamHostAndPort = downstreamHostAndPort;
|
||||
DownstreamScheme = downstreamScheme;
|
||||
}
|
||||
|
||||
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 ServiceName { get; private set;}
|
||||
public bool UseServiceDiscovery { get; private set;}
|
||||
public string ServiceDiscoveryProvider { get; private set;}
|
||||
public string ServiceDiscoveryAddress { get; private set;}
|
||||
public Func<HostAndPort> DownstreamHostAndPort {get;private set;}
|
||||
public string DownstreamScheme {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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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,91 @@
|
||||
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.Logging;
|
||||
using Ocelot.QueryStrings;
|
||||
using Ocelot.Request.Builder;
|
||||
using Ocelot.Requester;
|
||||
using Ocelot.Responder;
|
||||
|
||||
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<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,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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 Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
|
||||
{
|
||||
var configuration = _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,9 @@
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
{
|
||||
public interface IDownstreamRouteFinder
|
||||
{
|
||||
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 = _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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs
Normal file
12
src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.DownstreamUrlCreator
|
||||
{
|
||||
public interface IUrlBuilder
|
||||
{
|
||||
Response<DownstreamUrl> Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort);
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
{
|
||||
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IDownstreamPathPlaceholderReplacer _replacer;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IUrlBuilder _urlBuilder;
|
||||
|
||||
public DownstreamUrlCreatorMiddleware(RequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IDownstreamPathPlaceholderReplacer replacer,
|
||||
IRequestScopedDataRepository requestScopedDataRepository,
|
||||
IUrlBuilder urlBuilder)
|
||||
:base(requestScopedDataRepository)
|
||||
{
|
||||
_next = next;
|
||||
_replacer = replacer;
|
||||
_urlBuilder = urlBuilder;
|
||||
_logger = loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>();
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
_logger.LogDebug("started calling downstream url creator middleware");
|
||||
|
||||
var dsPath = _replacer
|
||||
.Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues);
|
||||
|
||||
if (dsPath.IsError)
|
||||
{
|
||||
_logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
|
||||
|
||||
SetPipelineError(dsPath.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme;
|
||||
|
||||
var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort();
|
||||
|
||||
var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort);
|
||||
|
||||
if (dsUrl.IsError)
|
||||
{
|
||||
_logger.LogDebug("IUrlBuilder returned an error, setting pipeline error");
|
||||
|
||||
SetPipelineError(dsUrl.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", dsUrl.Data.Value);
|
||||
|
||||
SetDownstreamUrlForThisRequest(dsUrl.Data.Value);
|
||||
|
||||
_logger.LogDebug("calling next middleware");
|
||||
|
||||
await _next.Invoke(context);
|
||||
|
||||
_logger.LogDebug("succesfully called next middleware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
{
|
||||
public static class DownstreamUrlCreatorMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
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