diff --git a/src/Ocelot.Library/.gitignore b/.gitignore
similarity index 98%
rename from src/Ocelot.Library/.gitignore
rename to .gitignore
index 0ca27f04..1a7759c2 100644
--- a/src/Ocelot.Library/.gitignore
+++ b/.gitignore
@@ -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/
diff --git a/GitVersion.yml b/GitVersion.yml
new file mode 100644
index 00000000..05e9ac41
--- /dev/null
+++ b/GitVersion.yml
@@ -0,0 +1,4 @@
+mode: ContinuousDelivery
+branches: {}
+ignore:
+ sha: []
diff --git a/Ocelot.nuspec b/Ocelot.nuspec
new file mode 100644
index 00000000..d236d60d
--- /dev/null
+++ b/Ocelot.nuspec
@@ -0,0 +1,48 @@
+
+
+
+ Ocelot
+ 1.0.0
+ Tom Pallister
+ Tom Pallister
+ https://github.com/TomPallister/Ocelot/blob/develop/LICENSE.md
+ https://github.com/TomPallister/Ocelot
+ false
+ Ocelot Api Gateway
+ Latest Ocelot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ocelot.sln b/Ocelot.sln
new file mode 100644
index 00000000..2f8fd206
--- /dev/null
+++ b/Ocelot.sln
@@ -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
diff --git a/README.md b/README.md
index 85884775..249a99a3 100644
--- a/README.md
+++ b/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 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 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)
+
+
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
new file mode 100644
index 00000000..a647c6c3
--- /dev/null
+++ b/ReleaseNotes.md
@@ -0,0 +1 @@
+No issues closed since last release
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 00000000..b5cd7c0c
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,8 @@
+version: 1.0.{build}
+configuration:
+- Release
+platform: Any CPU
+build_script:
+- ./build.ps1
+cache:
+- '%USERPROFILE%\.nuget\packages'
\ No newline at end of file
diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1
new file mode 100644
index 00000000..51c6f0d5
--- /dev/null
+++ b/build-and-release-unstable.ps1
@@ -0,0 +1 @@
+./build.ps1 -target BuildAndReleaseUnstable
\ No newline at end of file
diff --git a/build-and-run-tests.ps1 b/build-and-run-tests.ps1
new file mode 100644
index 00000000..f82502e5
--- /dev/null
+++ b/build-and-run-tests.ps1
@@ -0,0 +1 @@
+./build.ps1 -target RunTests
\ No newline at end of file
diff --git a/build.cake b/build.cake
new file mode 100644
index 00000000..1d798d74
--- /dev/null
+++ b/build.cake
@@ -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();
+
+ foreach(var asset in DeserializeJson(GetResource(assets_url)))
+ {
+ var file = packagesDir + File(asset.Value("name"));
+ Information("Downloading " + file);
+ DownloadFile(asset.Value("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();
+ }
+}
\ No newline at end of file
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 00000000..44de5793
--- /dev/null
+++ b/build.ps1
@@ -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
\ No newline at end of file
diff --git a/build.readme.md b/build.readme.md
new file mode 100644
index 00000000..fccff4d5
--- /dev/null
+++ b/build.readme.md
@@ -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.
+
diff --git a/configuration-explanation.txt b/configuration-explanation.txt
new file mode 100644
index 00000000..ad020469
--- /dev/null
+++ b/configuration-explanation.txt
@@ -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",
+ }
+ }
\ No newline at end of file
diff --git a/configuration.yaml b/configuration.yaml
new file mode 100755
index 00000000..2e47e77d
--- /dev/null
+++ b/configuration.yaml
@@ -0,0 +1,3 @@
+Routes:
+- Downstream: http://localhost:51879/
+ Upstream: /heee
diff --git a/global.json b/global.json
new file mode 100644
index 00000000..ff8d898e
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+ {
+ "projects": [ "src", "test" ],
+ "sdk": {
+ "version": "1.0.0-preview2-003133"
+ }
+}
diff --git a/release.ps1 b/release.ps1
new file mode 100644
index 00000000..6cf4c66b
--- /dev/null
+++ b/release.ps1
@@ -0,0 +1 @@
+./build.ps1 -target Release
\ No newline at end of file
diff --git a/run-acceptance-tests.ps1 b/run-acceptance-tests.ps1
new file mode 100644
index 00000000..480e1d4c
--- /dev/null
+++ b/run-acceptance-tests.ps1
@@ -0,0 +1 @@
+./build -target RunAcceptanceTests
\ No newline at end of file
diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1
new file mode 100644
index 00000000..e05490fd
--- /dev/null
+++ b/run-benchmarks.ps1
@@ -0,0 +1 @@
+./build.ps1 -target RunBenchmarkTests
\ No newline at end of file
diff --git a/run-unit-tests.ps1 b/run-unit-tests.ps1
new file mode 100644
index 00000000..0e6a91bd
--- /dev/null
+++ b/run-unit-tests.ps1
@@ -0,0 +1 @@
+./build.ps1 -target RunUnitTests
\ No newline at end of file
diff --git a/src/Ocelot.ApiGateway/AddRouteHandler.cs b/src/Ocelot.ApiGateway/AddRouteHandler.cs
deleted file mode 100644
index baaa05a4..00000000
--- a/src/Ocelot.ApiGateway/AddRouteHandler.cs
+++ /dev/null
@@ -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()));
-
- return routeBuilder;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Ocelot.ApiGateway/Dockerfile b/src/Ocelot.ApiGateway/Dockerfile
deleted file mode 100644
index 5ea50452..00000000
--- a/src/Ocelot.ApiGateway/Dockerfile
+++ /dev/null
@@ -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"]
diff --git a/src/Ocelot.ApiGateway/Routers.cs b/src/Ocelot.ApiGateway/Routers.cs
deleted file mode 100644
index af8881bc..00000000
--- a/src/Ocelot.ApiGateway/Routers.cs
+++ /dev/null
@@ -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;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Ocelot.ApiGateway/project.json b/src/Ocelot.ApiGateway/project.json
deleted file mode 100644
index 05f7e266..00000000
--- a/src/Ocelot.ApiGateway/project.json
+++ /dev/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"
- }
-}
diff --git a/src/Ocelot.Library/RouterMiddleware.cs b/src/Ocelot.Library/RouterMiddleware.cs
deleted file mode 100644
index d4474325..00000000
--- a/src/Ocelot.Library/RouterMiddleware.cs
+++ /dev/null
@@ -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()
- {
- }
- }
-}
diff --git a/src/Ocelot.Library/project.json b/src/Ocelot.Library/project.json
deleted file mode 100644
index b72a5c6a..00000000
--- a/src/Ocelot.Library/project.json
+++ /dev/null
@@ -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"
- }
-}
diff --git a/src/Ocelot/Authentication/Handler/AuthenticationHandler.cs b/src/Ocelot/Authentication/Handler/AuthenticationHandler.cs
new file mode 100644
index 00000000..3cb662b8
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/AuthenticationHandler.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs
new file mode 100644
index 00000000..65260d64
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs
@@ -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;
+
+ ///
+ /// Cannot unit test things in this class due to use of extension methods
+ ///
+ public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator
+ {
+ public Response 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(authenticationNext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs
new file mode 100644
index 00000000..6baa0385
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs
@@ -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 CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions);
+ }
+}
diff --git a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs
new file mode 100644
index 00000000..6379cc1f
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs
@@ -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 Get(IApplicationBuilder app, AuthenticationOptions authOptions)
+ {
+ var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions);
+
+ if (!handler.IsError)
+ {
+ return new OkResponse(
+ new AuthenticationHandler(authOptions.Provider, new RequestDelegateHandler(handler.Data)));
+ }
+
+ return new ErrorResponse(new List
+ {
+ new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for {authOptions.Provider}")
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authentication/Handler/Factory/IAuthenticationHandlerFactory.cs b/src/Ocelot/Authentication/Handler/Factory/IAuthenticationHandlerFactory.cs
new file mode 100644
index 00000000..abc09ed8
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/Factory/IAuthenticationHandlerFactory.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Builder;
+using Ocelot.Responses;
+
+namespace Ocelot.Authentication.Handler.Factory
+{
+ using AuthenticationOptions = Configuration.AuthenticationOptions;
+
+ public interface IAuthenticationHandlerFactory
+ {
+ Response Get(IApplicationBuilder app, AuthenticationOptions authOptions);
+ }
+}
diff --git a/src/Ocelot/Authentication/Handler/Factory/UnableToCreateAuthenticationHandlerError.cs b/src/Ocelot/Authentication/Handler/Factory/UnableToCreateAuthenticationHandlerError.cs
new file mode 100644
index 00000000..7e18b203
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/Factory/UnableToCreateAuthenticationHandlerError.cs
@@ -0,0 +1,12 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Authentication.Handler.Factory
+{
+ public class UnableToCreateAuthenticationHandlerError : Error
+ {
+ public UnableToCreateAuthenticationHandlerError(string message)
+ : base(message, OcelotErrorCode.UnableToCreateAuthenticationHandlerError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Authentication/Handler/IHandler.cs b/src/Ocelot/Authentication/Handler/IHandler.cs
new file mode 100644
index 00000000..99d240e8
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/IHandler.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Ocelot.Authentication.Handler
+{
+ public interface IHandler
+ {
+ Task Handle(HttpContext context);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs b/src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs
new file mode 100644
index 00000000..291e8ec3
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs
new file mode 100644
index 00000000..2a815ee0
--- /dev/null
+++ b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs
@@ -0,0 +1,7 @@
+namespace Ocelot.Authentication.Handler
+{
+ public enum SupportedAuthenticationProviders
+ {
+ IdentityServer
+ }
+}
diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs
new file mode 100644
index 00000000..ad30e166
--- /dev/null
+++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs
@@ -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();
+ }
+
+ 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 { 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;
+ }
+ }
+}
diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs
new file mode 100644
index 00000000..8e1a97e8
--- /dev/null
+++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs
@@ -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(builder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs
new file mode 100644
index 00000000..166430ed
--- /dev/null
+++ b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs
@@ -0,0 +1,12 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Authorisation
+{
+ public class ClaimValueNotAuthorisedError : Error
+ {
+ public ClaimValueNotAuthorisedError(string message)
+ : base(message, OcelotErrorCode.ClaimValueNotAuthorisedError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs
new file mode 100644
index 00000000..cb7849e9
--- /dev/null
+++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs
@@ -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 Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement)
+ {
+ foreach (var required in routeClaimsRequirement)
+ {
+ var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0);
+
+ if (value.IsError)
+ {
+ return new ErrorResponse(value.Errors);
+ }
+
+ if (value.Data != null)
+ {
+ var authorised = value.Data == required.Value;
+ if (!authorised)
+ {
+ return new ErrorResponse(new List
+ {
+ new ClaimValueNotAuthorisedError(
+ $"claim value: {value.Data} is not the same as required value: {required.Value} for type: {required.Key}")
+ });
+ }
+ }
+ else
+ {
+ return new ErrorResponse(new List
+ {
+ new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")
+ });
+ }
+ }
+ return new OkResponse(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authorisation/IAuthoriser.cs b/src/Ocelot/Authorisation/IAuthoriser.cs
new file mode 100644
index 00000000..08a7307f
--- /dev/null
+++ b/src/Ocelot/Authorisation/IAuthoriser.cs
@@ -0,0 +1,13 @@
+using System.Security.Claims;
+using Ocelot.Responses;
+
+namespace Ocelot.Authorisation
+{
+ using System.Collections.Generic;
+
+ public interface IAuthoriser
+ {
+ Response Authorise(ClaimsPrincipal claimsPrincipal,
+ Dictionary routeClaimsRequirement);
+ }
+}
diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs
new file mode 100644
index 00000000..547fc7f7
--- /dev/null
+++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs
@@ -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();
+ }
+
+ 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
+ {
+ 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 authorised)
+ {
+ return authorised.Data;
+ }
+ }
+}
diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs
new file mode 100644
index 00000000..e2c8af2d
--- /dev/null
+++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Authorisation/UnauthorisedError.cs b/src/Ocelot/Authorisation/UnauthorisedError.cs
new file mode 100644
index 00000000..766c1a7b
--- /dev/null
+++ b/src/Ocelot/Authorisation/UnauthorisedError.cs
@@ -0,0 +1,12 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Authorisation
+{
+ public class UnauthorisedError : Error
+ {
+ public UnauthorisedError(string message)
+ : base(message, OcelotErrorCode.UnauthorizedError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs b/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs
new file mode 100644
index 00000000..38525f00
--- /dev/null
+++ b/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs
@@ -0,0 +1,12 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Authorisation
+{
+ public class UserDoesNotHaveClaimError : Error
+ {
+ public UserDoesNotHaveClaimError(string message)
+ : base(message, OcelotErrorCode.UserDoesNotHaveClaimError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs
new file mode 100644
index 00000000..1ceb220c
--- /dev/null
+++ b/src/Ocelot/Cache/IOcelotCache.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Ocelot.Cache
+{
+ public interface IOcelotCache
+ {
+ void Add(string key, T value, TimeSpan ttl);
+ T Get(string key);
+ }
+}
diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
new file mode 100644
index 00000000..77024e70
--- /dev/null
+++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
@@ -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 _outputCache;
+
+ public OutputCacheMiddleware(RequestDelegate next,
+ IOcelotLoggerFactory loggerFactory,
+ IRequestScopedDataRepository scopedDataRepository,
+ IOcelotCache outputCache)
+ :base(scopedDataRepository)
+ {
+ _next = next;
+ _outputCache = outputCache;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs
new file mode 100644
index 00000000..76e406ee
--- /dev/null
+++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs
@@ -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();
+ }
+ }
+}
diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs
new file mode 100644
index 00000000..84bdc679
--- /dev/null
+++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs
@@ -0,0 +1,25 @@
+using System;
+using CacheManager.Core;
+
+namespace Ocelot.Cache
+{
+ public class OcelotCacheManagerCache : IOcelotCache
+ {
+ private readonly ICacheManager _cacheManager;
+
+ public OcelotCacheManagerCache(ICacheManager cacheManager)
+ {
+ _cacheManager = cacheManager;
+ }
+
+ public void Add(string key, T value, TimeSpan ttl)
+ {
+ _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl));
+ }
+
+ public T Get(string key)
+ {
+ return _cacheManager.Get(key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Claims/AddClaimsToRequest.cs b/src/Ocelot/Claims/AddClaimsToRequest.cs
new file mode 100644
index 00000000..120b0003
--- /dev/null
+++ b/src/Ocelot/Claims/AddClaimsToRequest.cs
@@ -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 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Claims/IAddClaimsToRequest.cs b/src/Ocelot/Claims/IAddClaimsToRequest.cs
new file mode 100644
index 00000000..02ae85e9
--- /dev/null
+++ b/src/Ocelot/Claims/IAddClaimsToRequest.cs
@@ -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 claimsToThings,
+ HttpContext context);
+ }
+}
diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs
new file mode 100644
index 00000000..1f6486f6
--- /dev/null
+++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs
@@ -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();
+ }
+
+ 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");
+ }
+ }
+}
diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs
new file mode 100644
index 00000000..fd3bef39
--- /dev/null
+++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/AuthenticationOptions.cs b/src/Ocelot/Configuration/AuthenticationOptions.cs
new file mode 100644
index 00000000..3b36453c
--- /dev/null
+++ b/src/Ocelot/Configuration/AuthenticationOptions.cs
@@ -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 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 AdditionalScopes { get; private set; }
+
+ }
+}
diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
new file mode 100644
index 00000000..1e06a440
--- /dev/null
+++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
@@ -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 _additionalScopes;
+ private bool _requireHttps;
+ private string _scopeSecret;
+ private List _configHeaderExtractorProperties;
+ private List _claimToClaims;
+ private Dictionary _routeClaimRequirement;
+ private bool _isAuthorised;
+ private List _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();
+ }
+
+ 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 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 input)
+ {
+ _configHeaderExtractorProperties = input;
+ return this;
+ }
+
+ public ReRouteBuilder WithClaimsToClaims(List input)
+ {
+ _claimToClaims = input;
+ return this;
+ }
+
+ public ReRouteBuilder WithRouteClaimsRequirement(Dictionary input)
+ {
+ _routeClaimRequirement = input;
+ return this;
+ }
+
+ public ReRouteBuilder WithClaimsToQueries(List 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 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);
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs
new file mode 100644
index 00000000..2fdaf2bb
--- /dev/null
+++ b/src/Ocelot/Configuration/CacheOptions.cs
@@ -0,0 +1,12 @@
+namespace Ocelot.Configuration
+{
+ public class CacheOptions
+ {
+ public CacheOptions(int ttlSeconds)
+ {
+ TtlSeconds = ttlSeconds;
+ }
+
+ public int TtlSeconds { get; private set; }
+ }
+}
diff --git a/src/Ocelot/Configuration/ClaimToThing.cs b/src/Ocelot/Configuration/ClaimToThing.cs
new file mode 100644
index 00000000..5a677667
--- /dev/null
+++ b/src/Ocelot/Configuration/ClaimToThing.cs
@@ -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; }
+ }
+}
diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs
new file mode 100644
index 00000000..8884f0d9
--- /dev/null
+++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs
@@ -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
+{
+ ///
+ /// Register as singleton
+ ///
+ public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator
+ {
+ private readonly IOptions _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 _logger;
+
+ public FileOcelotConfigurationCreator(
+ IOptions options,
+ IConfigurationValidator configurationValidator,
+ IClaimToThingConfigurationParser claimToThingConfigurationParser,
+ ILogger logger)
+ {
+ _options = options;
+ _configurationValidator = configurationValidator;
+ _claimToThingConfigurationParser = claimToThingConfigurationParser;
+ _logger = logger;
+ }
+
+ public Response Create()
+ {
+ var config = SetUpConfiguration();
+
+ return new OkResponse(config);
+ }
+
+ ///
+ /// 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
+ ///
+ 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();
+
+ 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 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(), new List(),
+ reRoute.RouteClaimsRequirement, isAuthorised, new List(),
+ 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();
+
+ 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 GetAddThingsToRequest(Dictionary thingBeingAdded)
+ {
+ var claimsToTHings = new List();
+
+ 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] == '{';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs
new file mode 100644
index 00000000..6cc7c2e8
--- /dev/null
+++ b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs
@@ -0,0 +1,9 @@
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Creator
+{
+ public interface IOcelotConfigurationCreator
+ {
+ Response Create();
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
new file mode 100644
index 00000000..0904d87e
--- /dev/null
+++ b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace Ocelot.Configuration.File
+{
+ public class FileAuthenticationOptions
+ {
+ public FileAuthenticationOptions()
+ {
+ AdditionalScopes = new List();
+ }
+
+ public string Provider { get; set; }
+ public string ProviderRootUrl { get; set; }
+ public string ScopeName { get; set; }
+ public bool RequireHttps { get; set; }
+ public List AdditionalScopes { get; set; }
+ public string ScopeSecret { get; set; }
+ }
+}
diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs
new file mode 100644
index 00000000..3f86006b
--- /dev/null
+++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs
@@ -0,0 +1,7 @@
+namespace Ocelot.Configuration.File
+{
+ public class FileCacheOptions
+ {
+ public int TtlSeconds { get; set; }
+ }
+}
diff --git a/src/Ocelot/Configuration/File/FileConfiguration.cs b/src/Ocelot/Configuration/File/FileConfiguration.cs
new file mode 100644
index 00000000..18938a0e
--- /dev/null
+++ b/src/Ocelot/Configuration/File/FileConfiguration.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace Ocelot.Configuration.File
+{
+ public class FileConfiguration
+ {
+ public FileConfiguration()
+ {
+ ReRoutes = new List();
+ GlobalConfiguration = new FileGlobalConfiguration();
+ }
+
+ public List ReRoutes { get; set; }
+ public FileGlobalConfiguration GlobalConfiguration { get; set; }
+ }
+}
diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
new file mode 100644
index 00000000..f414bc83
--- /dev/null
+++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
@@ -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;}
+ }
+}
diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs
new file mode 100644
index 00000000..a653224a
--- /dev/null
+++ b/src/Ocelot/Configuration/File/FileReRoute.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+
+namespace Ocelot.Configuration.File
+{
+ public class FileReRoute
+ {
+ public FileReRoute()
+ {
+ AddHeadersToRequest = new Dictionary();
+ AddClaimsToRequest = new Dictionary();
+ RouteClaimsRequirement = new Dictionary();
+ AddQueriesToRequest = new Dictionary();
+ 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 AddHeadersToRequest { get; set; }
+ public Dictionary AddClaimsToRequest { get; set; }
+ public Dictionary RouteClaimsRequirement { get; set; }
+ public Dictionary 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; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs
new file mode 100644
index 00000000..47efc6df
--- /dev/null
+++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs
@@ -0,0 +1,8 @@
+namespace Ocelot.Configuration.File
+{
+ public class FileServiceDiscoveryProvider
+ {
+ public string Provider {get;set;}
+ public string Address {get;set;}
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IOcelotConfiguration.cs
new file mode 100644
index 00000000..8359a2e1
--- /dev/null
+++ b/src/Ocelot/Configuration/IOcelotConfiguration.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Ocelot.Configuration
+{
+ public interface IOcelotConfiguration
+ {
+ List ReRoutes { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/OcelotConfiguration.cs b/src/Ocelot/Configuration/OcelotConfiguration.cs
new file mode 100644
index 00000000..3b0858eb
--- /dev/null
+++ b/src/Ocelot/Configuration/OcelotConfiguration.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Ocelot.Configuration
+{
+ public class OcelotConfiguration : IOcelotConfiguration
+ {
+ public OcelotConfiguration(List reRoutes)
+ {
+ ReRoutes = reRoutes;
+ }
+
+ public List ReRoutes { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs
new file mode 100644
index 00000000..45dfe62d
--- /dev/null
+++ b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs
@@ -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 Extract(string existingKey, string value)
+ {
+ try
+ {
+ var instructions = value.Split(SplitToken.ToCharArray());
+
+ if (instructions.Length <= 1)
+ {
+ return new ErrorResponse(
+ new List
+ {
+ new NoInstructionsError(SplitToken)
+ });
+ }
+
+ var claimMatch = _claimRegex.IsMatch(instructions[0]);
+
+ if (!claimMatch)
+ {
+ return new ErrorResponse(
+ new List
+ {
+ 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(
+ new ClaimToThing(existingKey, newKey, delimiter, index));
+ }
+ catch (Exception exception)
+ {
+ return new ErrorResponse(
+ new List
+ {
+ 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;
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs b/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs
new file mode 100644
index 00000000..a2712994
--- /dev/null
+++ b/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs
@@ -0,0 +1,9 @@
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Parser
+{
+ public interface IClaimToThingConfigurationParser
+ {
+ Response Extract(string existingKey, string value);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs b/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs
new file mode 100644
index 00000000..62cf8741
--- /dev/null
+++ b/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Parser/NoInstructionsError.cs b/src/Ocelot/Configuration/Parser/NoInstructionsError.cs
new file mode 100644
index 00000000..09aa0ccb
--- /dev/null
+++ b/src/Ocelot/Configuration/Parser/NoInstructionsError.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs b/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs
new file mode 100644
index 00000000..9b03c95d
--- /dev/null
+++ b/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs
new file mode 100644
index 00000000..30ded2e9
--- /dev/null
+++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs
@@ -0,0 +1,9 @@
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Provider
+{
+ public interface IOcelotConfigurationProvider
+ {
+ Response Get();
+ }
+}
diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs
new file mode 100644
index 00000000..4b6c5fd2
--- /dev/null
+++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs
@@ -0,0 +1,48 @@
+using Ocelot.Configuration.Creator;
+using Ocelot.Configuration.Repository;
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Provider
+{
+ ///
+ /// Register as singleton
+ ///
+ public class OcelotConfigurationProvider : IOcelotConfigurationProvider
+ {
+ private readonly IOcelotConfigurationRepository _repo;
+ private readonly IOcelotConfigurationCreator _creator;
+
+ public OcelotConfigurationProvider(IOcelotConfigurationRepository repo,
+ IOcelotConfigurationCreator creator)
+ {
+ _repo = repo;
+ _creator = creator;
+ }
+
+ public Response Get()
+ {
+ var repoConfig = _repo.Get();
+
+ if (repoConfig.IsError)
+ {
+ return new ErrorResponse(repoConfig.Errors);
+ }
+
+ if (repoConfig.Data == null)
+ {
+ var creatorConfig = _creator.Create();
+
+ if (creatorConfig.IsError)
+ {
+ return new ErrorResponse(creatorConfig.Errors);
+ }
+
+ _repo.AddOrReplace(creatorConfig.Data);
+
+ return new OkResponse(creatorConfig.Data);
+ }
+
+ return new OkResponse(repoConfig.Data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs
new file mode 100644
index 00000000..960374cc
--- /dev/null
+++ b/src/Ocelot/Configuration/ReRoute.cs
@@ -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 configurationHeaderExtractorProperties,
+ List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries,
+ string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery,
+ string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func 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();
+ ClaimsToClaims = claimsToClaims
+ ?? new List();
+ ClaimsToHeaders = configurationHeaderExtractorProperties
+ ?? new List();
+ 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 ClaimsToQueries { get; private set; }
+ public List ClaimsToHeaders { get; private set; }
+ public List ClaimsToClaims { get; private set; }
+ public Dictionary 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 DownstreamHostAndPort {get;private set;}
+ public string DownstreamScheme {get;private set;}
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs
new file mode 100644
index 00000000..312b5553
--- /dev/null
+++ b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs
@@ -0,0 +1,10 @@
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Repository
+{
+ public interface IOcelotConfigurationRepository
+ {
+ Response Get();
+ Response AddOrReplace(IOcelotConfiguration ocelotConfiguration);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs
new file mode 100644
index 00000000..04f34f8d
--- /dev/null
+++ b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs
@@ -0,0 +1,29 @@
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Repository
+{
+ ///
+ /// Register as singleton
+ ///
+ public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository
+ {
+ private static readonly object LockObject = new object();
+
+ private IOcelotConfiguration _ocelotConfiguration;
+
+ public Response Get()
+ {
+ return new OkResponse(_ocelotConfiguration);
+ }
+
+ public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration)
+ {
+ lock (LockObject)
+ {
+ _ocelotConfiguration = ocelotConfiguration;
+ }
+
+ return new OkResponse();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs
new file mode 100644
index 00000000..32dec98b
--- /dev/null
+++ b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs
@@ -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();
+ }
+
+ public ConfigurationValidationResult(bool isError, List errors)
+ {
+ IsError = isError;
+ Errors = errors;
+ }
+
+ public bool IsError { get; private set; }
+
+ public List Errors { get; private set; }
+ }
+}
diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs
new file mode 100644
index 00000000..e350753c
--- /dev/null
+++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs
@@ -0,0 +1,11 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Configuration.Validator
+{
+ public class DownstreamPathTemplateAlreadyUsedError : Error
+ {
+ public DownstreamPathTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreampathTemplateAlreadyUsedError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs
new file mode 100644
index 00000000..a3dfa309
--- /dev/null
+++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs
@@ -0,0 +1,12 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Configuration.Validator
+{
+ public class DownstreamPathTemplateContainsSchemeError : Error
+ {
+ public DownstreamPathTemplateContainsSchemeError(string message)
+ : base(message, OcelotErrorCode.DownstreamPathTemplateContainsSchemeError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs
new file mode 100644
index 00000000..412613eb
--- /dev/null
+++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs
@@ -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 IsValid(FileConfiguration configuration)
+ {
+ var result = CheckForDupliateReRoutes(configuration);
+
+ if (result.IsError)
+ {
+ return new OkResponse(result);
+ }
+
+ result = CheckForUnsupportedAuthenticationProviders(configuration);
+
+ if (result.IsError)
+ {
+ return new OkResponse(result);
+ }
+
+ result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration);
+
+ if (result.IsError)
+ {
+ return new OkResponse(result);
+ }
+
+ return new OkResponse(result);
+ }
+
+ private ConfigurationValidationResult CheckForUnsupportedAuthenticationProviders(FileConfiguration configuration)
+ {
+ var errors = new List();
+
+ 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();
+
+ 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()
+ .ToList();
+
+ return new ConfigurationValidationResult(true, errors);
+ }
+ }
+}
diff --git a/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs
new file mode 100644
index 00000000..09bf7dae
--- /dev/null
+++ b/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs
@@ -0,0 +1,10 @@
+using Ocelot.Configuration.File;
+using Ocelot.Responses;
+
+namespace Ocelot.Configuration.Validator
+{
+ public interface IConfigurationValidator
+ {
+ Response IsValid(FileConfiguration configuration);
+ }
+}
diff --git a/src/Ocelot/Configuration/Validator/UnsupportedAuthenticationProviderError.cs b/src/Ocelot/Configuration/Validator/UnsupportedAuthenticationProviderError.cs
new file mode 100644
index 00000000..e4f441bf
--- /dev/null
+++ b/src/Ocelot/Configuration/Validator/UnsupportedAuthenticationProviderError.cs
@@ -0,0 +1,12 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Configuration.Validator
+{
+ public class UnsupportedAuthenticationProviderError : Error
+ {
+ public UnsupportedAuthenticationProviderError(string message)
+ : base(message, OcelotErrorCode.UnsupportedAuthenticationProviderError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..9f40b009
--- /dev/null
+++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs
@@ -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 settings)
+ {
+ var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings);
+ var ocelotCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache);
+
+ services.AddSingleton>(cacheManagerOutputCache);
+ services.AddSingleton>(ocelotCacheManager);
+
+ return services;
+ }
+ public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
+ {
+ services.Configure(configurationRoot);
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ return services;
+ }
+
+ public static IServiceCollection AddOcelot(this IServiceCollection services)
+ {
+ services.AddMvcCore().AddJsonFormatters();
+ services.AddLogging();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // 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();
+ services.AddScoped();
+
+ return services;
+ }
+ }
+}
diff --git a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs
new file mode 100644
index 00000000..d4a117c0
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using Ocelot.Configuration;
+using Ocelot.DownstreamRouteFinder.UrlMatcher;
+
+namespace Ocelot.DownstreamRouteFinder
+{
+ public class DownstreamRoute
+ {
+ public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute)
+ {
+ TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
+ ReRoute = reRoute;
+ }
+ public List TemplatePlaceholderNameAndValues { get; private set; }
+ public ReRoute ReRoute { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs
new file mode 100644
index 00000000..752da281
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs
@@ -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 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(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
+ }
+ }
+
+ return new ErrorResponse(new List
+ {
+ new UnableToFindDownstreamRouteError()
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs
new file mode 100644
index 00000000..e351ab2f
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs
@@ -0,0 +1,9 @@
+using Ocelot.Responses;
+
+namespace Ocelot.DownstreamRouteFinder.Finder
+{
+ public interface IDownstreamRouteFinder
+ {
+ Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
+ }
+}
diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs
new file mode 100644
index 00000000..d73587b1
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs
@@ -0,0 +1,11 @@
+using Ocelot.Errors;
+
+namespace Ocelot.DownstreamRouteFinder.Finder
+{
+ public class UnableToFindDownstreamRouteError : Error
+ {
+ public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError", OcelotErrorCode.UnableToFindDownstreamRouteError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs
new file mode 100644
index 00000000..f445b46b
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs
@@ -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();
+ }
+
+ 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");
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs
new file mode 100644
index 00000000..0cd27758
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs
new file mode 100644
index 00000000..788299cb
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using Ocelot.Responses;
+
+namespace Ocelot.DownstreamRouteFinder.UrlMatcher
+{
+ public interface IUrlPathPlaceholderNameAndValueFinder
+ {
+ Response> Find(string upstreamUrlPath, string upstreamUrlPathTemplate);
+ }
+}
diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs
new file mode 100644
index 00000000..6c8956a2
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs
@@ -0,0 +1,9 @@
+using Ocelot.Responses;
+
+namespace Ocelot.DownstreamRouteFinder.UrlMatcher
+{
+ public interface IUrlPathToUrlTemplateMatcher
+ {
+ Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs
new file mode 100644
index 00000000..415ee556
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs
@@ -0,0 +1,17 @@
+using System.Text.RegularExpressions;
+using Ocelot.Responses;
+
+namespace Ocelot.DownstreamRouteFinder.UrlMatcher
+{
+ public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
+ {
+ public Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate)
+ {
+ var regex = new Regex(upstreamUrlPathTemplate);
+
+ return regex.IsMatch(upstreamUrlPath)
+ ? new OkResponse(new UrlMatch(true))
+ : new OkResponse(new UrlMatch(false));
+ }
+ }
+}
diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs
new file mode 100644
index 00000000..aa2a6f06
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs
@@ -0,0 +1,11 @@
+namespace Ocelot.DownstreamRouteFinder.UrlMatcher
+{
+ public class UrlMatch
+ {
+ public UrlMatch(bool match)
+ {
+ Match = match;
+ }
+ public bool Match {get;private set;}
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs
new file mode 100644
index 00000000..cb690666
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs
@@ -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;}
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs
new file mode 100644
index 00000000..249b153e
--- /dev/null
+++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using Ocelot.Responses;
+
+namespace Ocelot.DownstreamRouteFinder.UrlMatcher
+{
+ public class UrlPathPlaceholderNameAndValueFinder : IUrlPathPlaceholderNameAndValueFinder
+ {
+ public Response> Find(string upstreamUrlPath, string upstreamUrlPathTemplate)
+ {
+ var templateKeysAndValues = new List();
+
+ 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>(templateKeysAndValues);
+ }
+ counterForUrl++;
+ }
+
+ return new OkResponse>(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 == '{';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs
new file mode 100644
index 00000000..8978f665
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs
@@ -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)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs
new file mode 100644
index 00000000..fbc1a5f5
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs
@@ -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)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs
new file mode 100644
index 00000000..e52d3488
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs
@@ -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)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs
new file mode 100644
index 00000000..18683e62
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs
@@ -0,0 +1,12 @@
+using Ocelot.Configuration;
+using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
+using Ocelot.Responses;
+using Ocelot.Values;
+
+namespace Ocelot.DownstreamUrlCreator
+{
+ public interface IUrlBuilder
+ {
+ Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort);
+ }
+}
diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs
new file mode 100644
index 00000000..8144b42b
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs
@@ -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();
+ }
+
+ 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");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs
new file mode 100644
index 00000000..34bfaa54
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs
new file mode 100644
index 00000000..2124ce3b
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using Ocelot.Configuration;
+using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
+using Ocelot.Errors;
+using Ocelot.Responses;
+using Ocelot.Values;
+
+namespace Ocelot.DownstreamUrlCreator
+{
+ public class UrlBuilder : IUrlBuilder
+ {
+ public Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort)
+ {
+ if (string.IsNullOrEmpty(downstreamPath))
+ {
+ return new ErrorResponse(new List {new DownstreamPathNullOrEmptyError()});
+ }
+
+ if (string.IsNullOrEmpty(downstreamScheme))
+ {
+ return new ErrorResponse(new List { new DownstreamSchemeNullOrEmptyError() });
+ }
+
+ if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost))
+ {
+ return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() });
+ }
+
+ var builder = new UriBuilder
+ {
+ Host = downstreamHostAndPort.DownstreamHost,
+ Path = downstreamPath,
+ Scheme = downstreamScheme
+ };
+
+ if (downstreamHostAndPort.DownstreamPort > 0)
+ {
+ builder.Port = downstreamHostAndPort.DownstreamPort;
+ }
+
+ var url = builder.Uri.ToString();
+
+ return new OkResponse(new DownstreamUrl(url));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs
new file mode 100644
index 00000000..9e925631
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Text;
+using Ocelot.DownstreamRouteFinder.UrlMatcher;
+using Ocelot.Responses;
+using Ocelot.Values;
+
+namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
+{
+ public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
+ {
+ public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues)
+ {
+ var downstreamPath = new StringBuilder();
+
+ downstreamPath.Append(downstreamPathTemplate.Value);
+
+ foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
+ {
+ downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue);
+ }
+
+ return new OkResponse(new DownstreamPath(downstreamPath.ToString()));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs
new file mode 100644
index 00000000..72d5d4b6
--- /dev/null
+++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Ocelot.DownstreamRouteFinder.UrlMatcher;
+using Ocelot.Responses;
+using Ocelot.Values;
+
+namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
+{
+ public interface IDownstreamPathPlaceholderReplacer
+ {
+ Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Errors/Error.cs b/src/Ocelot/Errors/Error.cs
new file mode 100644
index 00000000..25a9f5d4
--- /dev/null
+++ b/src/Ocelot/Errors/Error.cs
@@ -0,0 +1,14 @@
+namespace Ocelot.Errors
+{
+ public abstract class Error
+ {
+ protected Error(string message, OcelotErrorCode code)
+ {
+ Message = message;
+ Code = code;
+ }
+
+ public string Message { get; private set; }
+ public OcelotErrorCode Code { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs
new file mode 100644
index 00000000..fc3ab200
--- /dev/null
+++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs
@@ -0,0 +1,72 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Ocelot.Infrastructure.RequestData;
+using Ocelot.Logging;
+
+namespace Ocelot.Errors.Middleware
+{
+ public class ExceptionHandlerMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly IOcelotLogger _logger;
+ private readonly IRequestScopedDataRepository _requestScopedDataRepository;
+
+ public ExceptionHandlerMiddleware(RequestDelegate next,
+ IOcelotLoggerFactory loggerFactory,
+ IRequestScopedDataRepository requestScopedDataRepository)
+ {
+ _next = next;
+ _requestScopedDataRepository = requestScopedDataRepository;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ try
+ {
+ _logger.LogDebug("ocelot pipeline started");
+
+ _logger.LogDebug("calling next middleware");
+
+ await _next.Invoke(context);
+
+ _logger.LogDebug("succesfully called middleware");
+ }
+ catch (Exception e)
+ {
+ _logger.LogDebug("error calling middleware");
+
+ var message = CreateMessage(context, e);
+ _logger.LogError(message, e);
+ await SetInternalServerErrorOnResponse(context);
+ }
+
+ _logger.LogDebug("ocelot pipeline finished");
+ }
+
+ private async Task SetInternalServerErrorOnResponse(HttpContext context)
+ {
+ context.Response.OnStarting(x =>
+ {
+ context.Response.StatusCode = 500;
+ return Task.CompletedTask;
+ }, context);
+ }
+
+ private string CreateMessage(HttpContext context, Exception e)
+ {
+ var message =
+ $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
+
+ if (e.InnerException != null)
+ {
+ message =
+ $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
+ }
+ return $"{message} RequestId: {context.TraceIdentifier}";
+ }
+ }
+}
diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs
new file mode 100644
index 00000000..e355c7f8
--- /dev/null
+++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace Ocelot.Errors.Middleware
+{
+ public static class ExceptionHandlerMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs
new file mode 100644
index 00000000..5de770cd
--- /dev/null
+++ b/src/Ocelot/Errors/OcelotErrorCode.cs
@@ -0,0 +1,26 @@
+namespace Ocelot.Errors
+{
+ public enum OcelotErrorCode
+ {
+ UnauthenticatedError,
+ UnknownError,
+ DownstreampathTemplateAlreadyUsedError,
+ UnableToFindDownstreamRouteError,
+ CannotAddDataError,
+ CannotFindDataError,
+ UnableToCompleteRequestError,
+ UnableToCreateAuthenticationHandlerError,
+ UnsupportedAuthenticationProviderError,
+ CannotFindClaimError,
+ ParsingConfigurationHeaderError,
+ NoInstructionsError,
+ InstructionNotForClaimsError,
+ UnauthorizedError,
+ ClaimValueNotAuthorisedError,
+ UserDoesNotHaveClaimError,
+ DownstreamPathTemplateContainsSchemeError,
+ DownstreamPathNullOrEmptyError,
+ DownstreamSchemeNullOrEmptyError,
+ DownstreamHostNullOrEmptyError
+ }
+}
diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs
new file mode 100644
index 00000000..97cd3e69
--- /dev/null
+++ b/src/Ocelot/Headers/AddHeadersToRequest.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+using Ocelot.Configuration;
+using Ocelot.Infrastructure.Claims.Parser;
+using Ocelot.Responses;
+
+namespace Ocelot.Headers
+{
+ public class AddHeadersToRequest : IAddHeadersToRequest
+ {
+ private readonly IClaimsParser _claimsParser;
+
+ public AddHeadersToRequest(IClaimsParser claimsParser)
+ {
+ _claimsParser = claimsParser;
+ }
+
+ public Response SetHeadersOnContext(List 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.Request.Headers.FirstOrDefault(x => x.Key == config.ExistingKey);
+
+ if (!string.IsNullOrEmpty(exists.Key))
+ {
+ context.Request.Headers.Remove(exists);
+ }
+
+ context.Request.Headers.Add(config.ExistingKey, new StringValues(value.Data));
+ }
+
+ return new OkResponse();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Headers/IAddHeadersToRequest.cs b/src/Ocelot/Headers/IAddHeadersToRequest.cs
new file mode 100644
index 00000000..3bf786a4
--- /dev/null
+++ b/src/Ocelot/Headers/IAddHeadersToRequest.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Ocelot.Configuration;
+using Ocelot.Responses;
+
+namespace Ocelot.Headers
+{
+ public interface IAddHeadersToRequest
+ {
+ Response SetHeadersOnContext(List claimsToThings,
+ HttpContext context);
+ }
+}
diff --git a/src/Ocelot/Headers/IRemoveOutputHeaders.cs b/src/Ocelot/Headers/IRemoveOutputHeaders.cs
new file mode 100644
index 00000000..909d78f9
--- /dev/null
+++ b/src/Ocelot/Headers/IRemoveOutputHeaders.cs
@@ -0,0 +1,10 @@
+using System.Net.Http.Headers;
+using Ocelot.Responses;
+
+namespace Ocelot.Headers
+{
+ public interface IRemoveOutputHeaders
+ {
+ Response Remove(HttpResponseHeaders headers);
+ }
+}
diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs
new file mode 100644
index 00000000..cc262048
--- /dev/null
+++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs
@@ -0,0 +1,56 @@
+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.Headers.Middleware
+{
+ public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly IAddHeadersToRequest _addHeadersToRequest;
+ private readonly IOcelotLogger _logger;
+
+ public HttpRequestHeadersBuilderMiddleware(RequestDelegate next,
+ IOcelotLoggerFactory loggerFactory,
+ IRequestScopedDataRepository requestScopedDataRepository,
+ IAddHeadersToRequest addHeadersToRequest)
+ : base(requestScopedDataRepository)
+ {
+ _next = next;
+ _addHeadersToRequest = addHeadersToRequest;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ _logger.LogDebug("started calling headers builder middleware");
+
+ if (DownstreamRoute.ReRoute.ClaimsToHeaders.Any())
+ {
+ _logger.LogDebug("this route has instructions to convert claims to headers");
+
+ var response = _addHeadersToRequest.SetHeadersOnContext(DownstreamRoute.ReRoute.ClaimsToHeaders, context);
+
+ if (response.IsError)
+ {
+ _logger.LogDebug("there was an error setting headers on context, setting pipeline error");
+
+ SetPipelineError(response.Errors);
+ return;
+ }
+
+ _logger.LogDebug("headers have been set on context");
+ }
+
+ _logger.LogDebug("calling next middleware");
+
+ await _next.Invoke(context);
+
+ _logger.LogDebug("succesfully called next middleware");
+ }
+ }
+}
diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs
new file mode 100644
index 00000000..ce010b14
--- /dev/null
+++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace Ocelot.Headers.Middleware
+{
+ public static class HttpRequestHeadersBuilderMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseHttpRequestHeadersBuilderMiddleware(this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Headers/RemoveOutputHeaders.cs b/src/Ocelot/Headers/RemoveOutputHeaders.cs
new file mode 100644
index 00000000..7a40534b
--- /dev/null
+++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs
@@ -0,0 +1,29 @@
+using System.Net.Http.Headers;
+using Ocelot.Responses;
+
+namespace Ocelot.Headers
+{
+ using System;
+
+ public class RemoveOutputHeaders : IRemoveOutputHeaders
+ {
+ ///
+ /// Some webservers return headers that cannot be forwarded to the client
+ /// in a given context such as transfer encoding chunked when ASP.NET is not
+ /// returning the response in this manner
+ ///
+ private readonly string[] _unsupportedRequestHeaders =
+ {
+ "Transfer-Encoding"
+ };
+ public Response Remove(HttpResponseHeaders headers)
+ {
+ foreach (var unsupported in _unsupportedRequestHeaders)
+ {
+ headers.Remove(unsupported);
+ }
+
+ return new OkResponse();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs
new file mode 100644
index 00000000..64a91cb8
--- /dev/null
+++ b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs
@@ -0,0 +1,12 @@
+namespace Ocelot.Infrastructure.Claims.Parser
+{
+ using Errors;
+
+ public class CannotFindClaimError : Error
+ {
+ public CannotFindClaimError(string message)
+ : base(message, OcelotErrorCode.CannotFindClaimError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs
new file mode 100644
index 00000000..e37f48fa
--- /dev/null
+++ b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs
@@ -0,0 +1,55 @@
+namespace Ocelot.Infrastructure.Claims.Parser
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Security.Claims;
+ using Errors;
+ using Responses;
+
+ public class ClaimsParser : IClaimsParser
+ {
+ public Response GetValue(IEnumerable claims, string key, string delimiter, int index)
+ {
+ var claimResponse = GetValue(claims, key);
+
+ if (claimResponse.IsError)
+ {
+ return claimResponse;
+ }
+
+ if (string.IsNullOrEmpty(delimiter))
+ {
+ return claimResponse;
+ }
+
+ var splits = claimResponse.Data.Split(delimiter.ToCharArray());
+
+ if (splits.Length < index || index < 0)
+ {
+ return new ErrorResponse(new List
+ {
+ new CannotFindClaimError($"Cannot find claim for key: {key}, delimiter: {delimiter}, index: {index}")
+ });
+ }
+
+ var value = splits[index];
+
+ return new OkResponse(value);
+ }
+
+ private Response GetValue(IEnumerable claims, string key)
+ {
+ var claim = claims.FirstOrDefault(c => c.Type == key);
+
+ if (claim != null)
+ {
+ return new OkResponse(claim.Value);
+ }
+
+ return new ErrorResponse(new List
+ {
+ new CannotFindClaimError($"Cannot find claim for key: {key}")
+ });
+ }
+ }
+}
diff --git a/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs
new file mode 100644
index 00000000..fa94cd22
--- /dev/null
+++ b/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs
@@ -0,0 +1,11 @@
+namespace Ocelot.Infrastructure.Claims.Parser
+{
+ using System.Collections.Generic;
+ using System.Security.Claims;
+ using Responses;
+
+ public interface IClaimsParser
+ {
+ Response GetValue(IEnumerable claims, string key, string delimiter, int index);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs b/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs
new file mode 100644
index 00000000..806a47e7
--- /dev/null
+++ b/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs
@@ -0,0 +1,11 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Infrastructure.RequestData
+{
+ public class CannotAddDataError : Error
+ {
+ public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs b/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs
new file mode 100644
index 00000000..75a85129
--- /dev/null
+++ b/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs
@@ -0,0 +1,11 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Infrastructure.RequestData
+{
+ public class CannotFindDataError : Error
+ {
+ public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs
new file mode 100644
index 00000000..0f15ca7e
--- /dev/null
+++ b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Ocelot.Errors;
+using Ocelot.Responses;
+
+namespace Ocelot.Infrastructure.RequestData
+{
+ public class HttpDataRepository : IRequestScopedDataRepository
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public HttpDataRepository(IHttpContextAccessor httpContextAccessor)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ public Response Add(string key, T value)
+ {
+ try
+ {
+ _httpContextAccessor.HttpContext.Items.Add(key, value);
+ return new OkResponse();
+ }
+ catch (Exception exception)
+ {
+ return new ErrorResponse(new List
+ {
+ new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}"))
+ });
+ }
+ }
+
+ public Response Get(string key)
+ {
+ object obj;
+
+ if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj))
+ {
+ var data = (T) obj;
+ return new OkResponse(data);
+ }
+
+ return new ErrorResponse(new List
+ {
+ new CannotFindDataError($"Unable to find data for key: {key}")
+ });
+ }
+ }
+}
diff --git a/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs b/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs
new file mode 100644
index 00000000..f707178c
--- /dev/null
+++ b/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs
@@ -0,0 +1,10 @@
+using Ocelot.Responses;
+
+namespace Ocelot.Infrastructure.RequestData
+{
+ public interface IRequestScopedDataRepository
+ {
+ Response Add(string key, T value);
+ Response Get(string key);
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Logging/IOcelotLoggerFactory.cs b/src/Ocelot/Logging/IOcelotLoggerFactory.cs
new file mode 100644
index 00000000..5305088f
--- /dev/null
+++ b/src/Ocelot/Logging/IOcelotLoggerFactory.cs
@@ -0,0 +1,69 @@
+using System;
+using Microsoft.Extensions.Logging;
+using Ocelot.Infrastructure.RequestData;
+
+namespace Ocelot.Logging
+{
+ public interface IOcelotLoggerFactory
+ {
+ IOcelotLogger CreateLogger();
+ }
+
+ public class AspDotNetLoggerFactory : IOcelotLoggerFactory
+ {
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly IRequestScopedDataRepository _scopedDataRepository;
+
+ public AspDotNetLoggerFactory(ILoggerFactory loggerFactory, IRequestScopedDataRepository scopedDataRepository)
+ {
+ _loggerFactory = loggerFactory;
+ _scopedDataRepository = scopedDataRepository;
+ }
+
+ public IOcelotLogger CreateLogger()
+ {
+ var logger = _loggerFactory.CreateLogger();
+ return new AspDotNetLogger(logger, _scopedDataRepository);
+ }
+ }
+
+ public interface IOcelotLogger
+ {
+ void LogDebug(string message, params object[] args);
+ void LogError(string message, Exception exception);
+ }
+
+ public class AspDotNetLogger : IOcelotLogger
+ {
+ private readonly ILogger _logger;
+ private readonly IRequestScopedDataRepository _scopedDataRepository;
+
+ public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository)
+ {
+ _logger = logger;
+ _scopedDataRepository = scopedDataRepository;
+ }
+
+ public void LogDebug(string message, params object[] args)
+ {
+ _logger.LogDebug(GetMessageWithOcelotRequestId(message), args);
+ }
+
+ public void LogError(string message, Exception exception)
+ {
+ _logger.LogError(GetMessageWithOcelotRequestId(message), exception);
+ }
+
+ private string GetMessageWithOcelotRequestId(string message)
+ {
+ var requestId = _scopedDataRepository.Get("RequestId");
+
+ if (requestId != null && !requestId.IsError)
+ {
+ return $"{message} : OcelotRequestId - {requestId.Data}";
+
+ }
+ return $"{message} : OcelotRequestId - not set";
+ }
+ }
+}
diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs
new file mode 100644
index 00000000..0bb51040
--- /dev/null
+++ b/src/Ocelot/Middleware/OcelotMiddleware.cs
@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using Ocelot.DownstreamRouteFinder;
+using Ocelot.Errors;
+using Ocelot.Infrastructure.RequestData;
+
+namespace Ocelot.Middleware
+{
+ public abstract class OcelotMiddleware
+ {
+ private readonly IRequestScopedDataRepository _requestScopedDataRepository;
+
+ protected OcelotMiddleware(IRequestScopedDataRepository requestScopedDataRepository)
+ {
+ _requestScopedDataRepository = requestScopedDataRepository;
+ }
+
+ public bool PipelineError
+ {
+ get
+ {
+ var response = _requestScopedDataRepository.Get("OcelotMiddlewareError");
+ return response.Data;
+ }
+ }
+
+ public List PipelineErrors
+ {
+ get
+ {
+ var response = _requestScopedDataRepository.Get>("OcelotMiddlewareErrors");
+ return response.Data;
+ }
+ }
+
+ public DownstreamRoute DownstreamRoute
+ {
+ get
+ {
+ var downstreamRoute = _requestScopedDataRepository.Get("DownstreamRoute");
+ return downstreamRoute.Data;
+ }
+ }
+
+ public string DownstreamUrl
+ {
+ get
+ {
+ var downstreamUrl = _requestScopedDataRepository.Get("DownstreamUrl");
+ return downstreamUrl.Data;
+ }
+ }
+
+ public Request.Request Request
+ {
+ get
+ {
+ var request = _requestScopedDataRepository.Get("Request");
+ return request.Data;
+ }
+ }
+
+ public HttpResponseMessage HttpResponseMessage
+ {
+ get
+ {
+ var request = _requestScopedDataRepository.Get("HttpResponseMessage");
+ return request.Data;
+ }
+ }
+
+ public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute)
+ {
+ _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute);
+ }
+
+ public void SetDownstreamUrlForThisRequest(string downstreamUrl)
+ {
+ _requestScopedDataRepository.Add("DownstreamUrl", downstreamUrl);
+ }
+
+ public void SetUpstreamRequestForThisRequest(Request.Request request)
+ {
+ _requestScopedDataRepository.Add("Request", request);
+ }
+
+ public void SetHttpResponseMessageThisRequest(HttpResponseMessage responseMessage)
+ {
+ _requestScopedDataRepository.Add("HttpResponseMessage", responseMessage);
+ }
+
+ public void SetPipelineError(List errors)
+ {
+ _requestScopedDataRepository.Add("OcelotMiddlewareError", true);
+ _requestScopedDataRepository.Add("OcelotMiddlewareErrors", errors);
+ }
+ }
+}
diff --git a/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs b/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs
new file mode 100644
index 00000000..555f8df9
--- /dev/null
+++ b/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs
@@ -0,0 +1,44 @@
+namespace Ocelot.Middleware
+{
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.AspNetCore.Http;
+
+ public class OcelotMiddlewareConfiguration
+ {
+ ///
+ /// This is called after the global error handling middleware so any code before calling next.invoke
+ /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called
+ /// in the Ocelot pipeline before we go to the global error handler.
+ ///
+ public Func, Task> PreErrorResponderMiddleware { get; set; }
+
+ ///
+ /// This is to allow the user to run any extra authentication before the Ocelot authentication
+ /// kicks in
+ ///
+ public Func, Task> PreAuthenticationMiddleware { get; set; }
+
+ ///
+ /// This allows the user to completely override the ocelot authentication middleware
+ ///
+ public Func, Task> AuthenticationMiddleware { get; set; }
+
+ ///
+ /// This is to allow the user to run any extra authorisation before the Ocelot authentication
+ /// kicks in
+ ///
+ public Func, Task> PreAuthorisationMiddleware { get; set; }
+
+ ///
+ /// This allows the user to completely override the ocelot authorisation middleware
+ ///
+ public Func, Task> AuthorisationMiddleware { get; set; }
+
+ ///
+ /// This allows the user to implement there own query string manipulation logic
+ ///
+ public Func, Task> PreQueryStringBuilderMiddleware { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs
new file mode 100644
index 00000000..dfa3b3f4
--- /dev/null
+++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs
@@ -0,0 +1,125 @@
+using Microsoft.AspNetCore.Builder;
+using Ocelot.Authentication.Middleware;
+using Ocelot.Cache.Middleware;
+using Ocelot.Claims.Middleware;
+using Ocelot.DownstreamRouteFinder.Middleware;
+using Ocelot.DownstreamUrlCreator.Middleware;
+using Ocelot.Errors.Middleware;
+using Ocelot.Headers.Middleware;
+using Ocelot.QueryStrings.Middleware;
+using Ocelot.Request.Middleware;
+using Ocelot.Requester.Middleware;
+using Ocelot.RequestId.Middleware;
+using Ocelot.Responder.Middleware;
+
+namespace Ocelot.Middleware
+{
+ using System;
+ using System.Threading.Tasks;
+ using Authorisation.Middleware;
+ using Microsoft.AspNetCore.Http;
+
+ public static class OcelotMiddlewareExtensions
+ {
+ ///
+ /// Registers the Ocelot default middlewares
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder)
+ {
+ builder.UseOcelot(new OcelotMiddlewareConfiguration());
+ return builder;
+ }
+
+ ///
+ /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
+ {
+ // This is registered to catch any global exceptions that are not handled
+ builder.UseExceptionHandlerMiddleware();
+
+ // Allow the user to respond with absolutely anything they want.
+ builder.UseIfNotNull(middlewareConfiguration.PreErrorResponderMiddleware);
+
+ // This is registered first so it can catch any errors and issue an appropriate response
+ builder.UseResponderMiddleware();
+
+ // Then we get the downstream route information
+ builder.UseDownstreamRouteFinderMiddleware();
+
+ // Now we can look for the requestId
+ builder.UseRequestIdMiddleware();
+
+ // Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
+ builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware);
+
+ // Now we know where the client is going to go we can authenticate them.
+ // We allow the ocelot middleware to be overriden by whatever the
+ // user wants
+ if (middlewareConfiguration.AuthenticationMiddleware == null)
+ {
+ builder.UseAuthenticationMiddleware();
+ }
+ else
+ {
+ builder.Use(middlewareConfiguration.AuthenticationMiddleware);
+ }
+
+ // The next thing we do is look at any claims transforms in case this is important for authorisation
+ builder.UseClaimsBuilderMiddleware();
+
+ // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in.
+ builder.UseIfNotNull(middlewareConfiguration.PreAuthorisationMiddleware);
+
+ // Now we have authenticated and done any claims transformation we
+ // can authorise the request
+ // We allow the ocelot middleware to be overriden by whatever the
+ // user wants
+ if (middlewareConfiguration.AuthorisationMiddleware == null)
+ {
+ builder.UseAuthorisationMiddleware();
+ }
+ else
+ {
+ builder.Use(middlewareConfiguration.AuthorisationMiddleware);
+ }
+
+ // Now we can run any header transformation logic
+ builder.UseHttpRequestHeadersBuilderMiddleware();
+
+ // Allow the user to implement their own query string manipulation logic
+ builder.UseIfNotNull(middlewareConfiguration.PreQueryStringBuilderMiddleware);
+
+ // Now we can run any query string transformation logic
+ builder.UseQueryStringBuilderMiddleware();
+
+ // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
+ builder.UseDownstreamUrlCreatorMiddleware();
+
+ // Not sure if this is the best place for this but we use the downstream url
+ // as the basis for our cache key.
+ builder.UseOutputCacheMiddleware();
+
+ // Everything should now be ready to build or HttpRequest
+ builder.UseHttpRequestBuilderMiddleware();
+
+ //We fire off the request and set the response on the scoped data repo
+ builder.UseHttpRequesterMiddleware();
+
+ return builder;
+ }
+
+ private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware)
+ {
+ if (middleware != null)
+ {
+ builder.Use(middleware);
+ }
+ }
+ }
+}
diff --git a/src/Ocelot/Middleware/UnauthenticatedError.cs b/src/Ocelot/Middleware/UnauthenticatedError.cs
new file mode 100644
index 00000000..c8b04039
--- /dev/null
+++ b/src/Ocelot/Middleware/UnauthenticatedError.cs
@@ -0,0 +1,11 @@
+using Ocelot.Errors;
+
+namespace Ocelot.Middleware
+{
+ public class UnauthenticatedError : Error
+ {
+ public UnauthenticatedError(string message) : base(message, OcelotErrorCode.UnauthenticatedError)
+ {
+ }
+ }
+}
diff --git a/src/Ocelot/Ocelot.xproj b/src/Ocelot/Ocelot.xproj
new file mode 100644
index 00000000..5f2a7cc5
--- /dev/null
+++ b/src/Ocelot/Ocelot.xproj
@@ -0,0 +1,19 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ d6df4206-0dba-41d8-884d-c3e08290fdbb
+ Ocelot
+ .\obj
+ .\bin\
+ v4.5
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/src/Ocelot/Properties/AssemblyInfo.cs b/src/Ocelot/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..ad12027e
--- /dev/null
+++ b/src/Ocelot/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Ocelot")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
diff --git a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs
new file mode 100644
index 00000000..02fcb63d
--- /dev/null
+++ b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Ocelot.Configuration;
+using Ocelot.Infrastructure.Claims.Parser;
+using Ocelot.Responses;
+
+namespace Ocelot.QueryStrings
+{
+ public class AddQueriesToRequest : IAddQueriesToRequest
+ {
+ private readonly IClaimsParser _claimsParser;
+
+ public AddQueriesToRequest(IClaimsParser claimsParser)
+ {
+ _claimsParser = claimsParser;
+ }
+
+ public Response SetQueriesOnContext(List claimsToThings, HttpContext context)
+ {
+ var queryDictionary = ConvertQueryStringToDictionary(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 = queryDictionary.FirstOrDefault(x => x.Key == config.ExistingKey);
+
+ if (!string.IsNullOrEmpty(exists.Key))
+ {
+ queryDictionary[exists.Key] = value.Data;
+ }
+ else
+ {
+ queryDictionary.Add(config.ExistingKey, value.Data);
+ }
+ }
+
+ context.Request.QueryString = ConvertDictionaryToQueryString(queryDictionary);
+
+ return new OkResponse();
+ }
+
+ private Dictionary ConvertQueryStringToDictionary(HttpContext context)
+ {
+ return Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(context.Request.QueryString.Value)
+ .ToDictionary(q => q.Key, q => q.Value.FirstOrDefault() ?? string.Empty);
+ }
+
+ private Microsoft.AspNetCore.Http.QueryString ConvertDictionaryToQueryString(Dictionary queryDictionary)
+ {
+ var newQueryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString("", queryDictionary);
+
+ return new Microsoft.AspNetCore.Http.QueryString(newQueryString);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs b/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs
new file mode 100644
index 00000000..6fa1b8da
--- /dev/null
+++ b/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Ocelot.Configuration;
+using Ocelot.Responses;
+
+namespace Ocelot.QueryStrings
+{
+ public interface IAddQueriesToRequest
+ {
+ Response SetQueriesOnContext(List claimsToThings,
+ HttpContext context);
+ }
+}
diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs
new file mode 100644
index 00000000..1424d713
--- /dev/null
+++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs
@@ -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.QueryStrings.Middleware
+{
+ public class QueryStringBuilderMiddleware : OcelotMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly IAddQueriesToRequest _addQueriesToRequest;
+ private readonly IOcelotLogger _logger;
+
+ public QueryStringBuilderMiddleware(RequestDelegate next,
+ IOcelotLoggerFactory loggerFactory,
+ IRequestScopedDataRepository requestScopedDataRepository,
+ IAddQueriesToRequest addQueriesToRequest)
+ : base(requestScopedDataRepository)
+ {
+ _next = next;
+ _addQueriesToRequest = addQueriesToRequest;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ _logger.LogDebug("started calling query string builder middleware");
+
+ if (DownstreamRoute.ReRoute.ClaimsToQueries.Any())
+ {
+ _logger.LogDebug("this route has instructions to convert claims to queries");
+
+ var response = _addQueriesToRequest.SetQueriesOnContext(DownstreamRoute.ReRoute.ClaimsToQueries, context);
+
+ if (response.IsError)
+ {
+ _logger.LogDebug("there was an error setting queries on context, setting pipeline error");
+
+ SetPipelineError(response.Errors);
+ return;
+ }
+ }
+
+ _logger.LogDebug("calling next middleware");
+
+ await _next.Invoke(context);
+
+ _logger.LogDebug("succesfully called next middleware");
+ }
+ }
+}
diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs
new file mode 100644
index 00000000..8ff39be2
--- /dev/null
+++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace Ocelot.QueryStrings.Middleware
+{
+ public static class QueryStringBuilderMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseQueryStringBuilderMiddleware(this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs
new file mode 100644
index 00000000..91234331
--- /dev/null
+++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs
@@ -0,0 +1,34 @@
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Ocelot.Responses;
+
+namespace Ocelot.Request.Builder
+{
+ public sealed class HttpRequestCreator : IRequestCreator
+ {
+ public async Task> Build(
+ string httpMethod,
+ string downstreamUrl,
+ Stream content,
+ IHeaderDictionary headers,
+ IRequestCookieCollection cookies,
+ QueryString queryString,
+ string contentType,
+ RequestId.RequestId requestId)
+ {
+ var request = await new RequestBuilder()
+ .WithHttpMethod(httpMethod)
+ .WithDownstreamUrl(downstreamUrl)
+ .WithQueryString(queryString)
+ .WithContent(content)
+ .WithContentType(contentType)
+ .WithHeaders(headers)
+ .WithRequestId(requestId)
+ .WithCookies(cookies)
+ .Build();
+
+ return new OkResponse