Merge pull request #43 from ThreeMammals/develop

merge newest code
This commit is contained in:
geffzhang 2018-04-25 21:30:14 +08:00 committed by GitHub
commit 2d01e96369
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
306 changed files with 8298 additions and 3592 deletions

2
.gitignore vendored
View File

@ -243,7 +243,7 @@ tools/
.DS_Store .DS_Store
# Ocelot acceptance test config # Ocelot acceptance test config
test/Ocelot.AcceptanceTests/configuration.json test/Ocelot.AcceptanceTests/ocelot.json
# Read the docstates # Read the docstates
_build/ _build/

31
.travis.yml Normal file
View File

@ -0,0 +1,31 @@
language: csharp
os:
- osx
- linux
# Ubuntu 14.04
sudo: required
dist: trusty
# OS X 10.12
osx_image: xcode9.2
mono:
- 4.4.2
dotnet: 2.1.4
before_install:
- git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags
- git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
- git fetch origin
script:
- ./build.sh
cache:
directories:
- .packages
- tools/Addins
- tools/gitreleasemanager
- tools/GitVersion.CommandLine

View File

@ -1,10 +1,11 @@
[<img src="http://threemammals.com/images/ocelot_logo.png">](http://threemammals.com/ocelot) [<img src="http://threemammals.com/images/ocelot_logo.png">](http://threemammals.com/ocelot)
[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) [![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?branch=develop&svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) Windows (AppVeyor)
[![Build Status](https://travis-ci.org/ThreeMammals/Ocelot.svg?branch=develop)](https://travis-ci.org/ThreeMammals/Ocelot) Linux & OSX (Travis)
[![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop) [![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=develop)
# Ocelot # Ocelot
@ -38,8 +39,9 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Routing * Routing
* Request Aggregation * Request Aggregation
* Service Discovery with Consul * Service Discovery with Consul & Eureka
* Service Fabric * Service Fabric
* WebSockets
* Authentication * Authentication
* Authorisation * Authorisation
* Rate Limiting * Rate Limiting
@ -50,6 +52,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Headers / Query String / Claims Transformation * Headers / Query String / Claims Transformation
* Custom Middleware / Delegating Handlers * Custom Middleware / Delegating Handlers
* Configuration / Administration REST API * Configuration / Administration REST API
* Platform / Cloud agnostic
## How to install ## How to install

View File

@ -17,7 +17,7 @@ var artifactsDir = Directory("artifacts");
// unit testing // unit testing
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
var minCodeCoverage = 76.4d; var minCodeCoverage = 82d;
var coverallsRepoToken = "coveralls-repo-token-ocelot"; var coverallsRepoToken = "coveralls-repo-token-ocelot";
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
@ -189,6 +189,24 @@ Task("RunAcceptanceTests")
.IsDependentOn("Compile") .IsDependentOn("Compile")
.Does(() => .Does(() =>
{ {
if(TravisCI.IsRunningOnTravisCI)
{
Information(
@"Job:
JobId: {0}
JobNumber: {1}
OSName: {2}",
BuildSystem.TravisCI.Environment.Job.JobId,
BuildSystem.TravisCI.Environment.Job.JobNumber,
BuildSystem.TravisCI.Environment.Job.OSName
);
if(TravisCI.Environment.Job.OSName.ToLower() == "osx")
{
return;
}
}
var settings = new DotNetCoreTestSettings var settings = new DotNetCoreTestSettings
{ {
Configuration = compileConfig, Configuration = compileConfig,
@ -205,6 +223,24 @@ Task("RunIntegrationTests")
.IsDependentOn("Compile") .IsDependentOn("Compile")
.Does(() => .Does(() =>
{ {
if(TravisCI.IsRunningOnTravisCI)
{
Information(
@"Job:
JobId: {0}
JobNumber: {1}
OSName: {2}",
BuildSystem.TravisCI.Environment.Job.JobId,
BuildSystem.TravisCI.Environment.Job.JobNumber,
BuildSystem.TravisCI.Environment.Job.OSName
);
if(TravisCI.Environment.Job.OSName.ToLower() == "osx")
{
return;
}
}
var settings = new DotNetCoreTestSettings var settings = new DotNetCoreTestSettings
{ {
Configuration = compileConfig, Configuration = compileConfig,

View File

@ -1,7 +1,7 @@
Configuration Configuration
============ ============
An example configuration can be found `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/configuration.json>`_. An example configuration can be found `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/ocelot.json>`_.
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. 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 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 configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
@ -64,20 +64,12 @@ Here is an example ReRoute configuration, You don't need to set all of these thi
"UseCookieContainer": true, "UseCookieContainer": true,
"UseTracing": true "UseTracing": true
}, },
"UseServiceDiscovery": false "UseServiceDiscovery": false,
"DangerousAcceptAnyServerCertificateValidator": false
} }
More information on how to use these options is below.. More information on how to use these options is below..
Follow Redirects / Use CookieContainer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses.
Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true.
- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests.
The default value is true.
Multiple environments Multiple environments
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
@ -92,15 +84,40 @@ to you
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("configuration.json") .AddJsonFile("ocelot.json")
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
Ocelot should now use the environment specific configuration and fall back to configuration.json if there isnt one. Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isnt one.
You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_. You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_.
Merging configuration files
^^^^^^^^^^^^^^^^^^^^^^^^^^^
This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier.
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
.. code-block:: csharp
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddOcelot()
.AddEnvironmentVariables();
})
In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json.
The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running.
At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems.
Store configuration in consul Store configuration in consul
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -112,7 +129,7 @@ If you add the following when you register your services Ocelot will attempt to
.AddOcelot() .AddOcelot()
.AddStoreOcelotConfigurationInConsul(); .AddStoreOcelotConfigurationInConsul();
You also need to add the following to your configuration.json. This is how Ocelot You also need to add the following to your ocelot.json. This is how Ocelot
finds your Consul agent and interacts to load and store the configuration from Consul. finds your Consul agent and interacts to load and store the configuration from Consul.
.. code-block:: json .. code-block:: json
@ -128,3 +145,30 @@ I decided to create this feature after working on the raft consensus algorithm a
I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
This feature has a 3 second ttl cache before making a new request to your local consul agent. This feature has a 3 second ttl cache before making a new request to your local consul agent.
Follow Redirects / Use CookieContainer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user
noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients
that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight
requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting
UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request!
SSL Errors
^^^^^^^^^^
Id you want to ignore SSL warnings / errors set the following in your ReRoute config.
.. code-block:: json
"DangerousAcceptAnyServerCertificateValidator": false
I don't reccomend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can.

View File

@ -40,7 +40,7 @@ Or transient as below...
.AddTransientDelegatingHandler<FakeHandlerTwo>() .AddTransientDelegatingHandler<FakeHandlerTwo>()
Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of
the DelegatingHandler is to be applied to specific ReRoutes via configuration.json (more on that later). If it is set to true the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true
then it becomes a global handler and will be applied to all ReRoutes. then it becomes a global handler and will be applied to all ReRoutes.
e.g. e.g.
@ -58,7 +58,7 @@ Or transient as below...
.AddTransientDelegatingHandler<FakeHandler>(true) .AddTransientDelegatingHandler<FakeHandler>(true)
Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers
then you must add the following json to the specific ReRoute in configuration.json. The names in the array must match the class names of your then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your
DelegatingHandlers for Ocelot to match them together. DelegatingHandlers for Ocelot to match them together.
.. code-block:: json .. code-block:: json
@ -70,8 +70,8 @@ DelegatingHandlers for Ocelot to match them together.
You can have as many DelegatingHandlers as you want and they are run in the following order: You can have as many DelegatingHandlers as you want and they are run in the following order:
1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from configuration.json. 1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json.
2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from configuration.json ordered as they are in the DelegatingHandlers array. 2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array.
3. Tracing DelegatingHandler if enabled (see tracing docs). 3. Tracing DelegatingHandler if enabled (see tracing docs).
4. QoS DelegatingHandler if enabled (see QoS docs). 4. QoS DelegatingHandler if enabled (see QoS docs).
5. The HttpClient sends the HttpRequestMessage. 5. The HttpClient sends the HttpRequestMessage.

15
docs/features/graphql.rst Normal file
View File

@ -0,0 +1,15 @@
GraphQL
=======
OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate
the `graphql-dotnet <https://github.com/graphql-dotnet/graphql-dotnet>`_ library.
Please see the sample project `OcelotGraphQL <https://github.com/ThreeMammals/Ocelot/tree/develop/samples/OcelotGraphQL>`_.
Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do.
However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give
you enough instruction on how to do this!
Good luck and have fun :>

View File

@ -1,10 +1,50 @@
Headers Transformation Headers Transformation
===================== ======================
Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways. Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
Syntax Add to Request
^^^^^^ ^^^^^^^^^^^^^^
This feature was requestes in `GitHub #313 <https://github.com/ThreeMammals/Ocelot/issues/313>`_.
If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json:
.. code-block:: json
"UpstreamHeaderTransform": {
"Uncle": "Bob"
}
In the example above a header with the key Uncle and value Bob would be send to to the upstream service.
Placeholders are supported too (see below).
Add to Response
^^^^^^^^^^^^^^^
This feature was requested in `GitHub #280 <https://github.com/TomPallister/Ocelot/issues/280>`_.
If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json..
.. code-block:: json
"DownstreamHeaderTransform": {
"Uncle": "Bob"
},
In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute.
If you want to return the Butterfly APM trace id then do something like the following..
.. code-block:: json
"DownstreamHeaderTransform": {
"AnyKey": "{TraceId}"
},
Find and Replace
^^^^^^^^^^^^^^^^
In order to transform a header first we specify the header key and then the type of transform we want e.g. In order to transform a header first we specify the header key and then the type of transform we want e.g.
@ -17,7 +57,7 @@ The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/".
Pre Downstream Request Pre Downstream Request
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server.
.. code-block:: json .. code-block:: json
@ -26,9 +66,9 @@ Add the following to a ReRoute in configuration.json in order to replace http://
}, },
Post Downstream Request Post Downstream Request
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service.
.. code-block:: json .. code-block:: json
@ -43,6 +83,7 @@ Ocelot allows placeholders that can be used in header transformation.
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
Handling 302 Redirects Handling 302 Redirects
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^

View File

@ -16,7 +16,7 @@ You must choose in your configuration which load balancer to use.
Configuration Configuration
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
The following shows how to set up multiple downstream services for a ReRoute using configuration.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up.
.. code-block:: json .. code-block:: json

View File

@ -12,3 +12,9 @@ Finally if logging is set to trace level Ocelot will log starting, finishing and
The reason for not just using bog standard framework logging is that I could not 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 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. to true for logging settings. Nicely onto the next feature.
Warning
^^^^^^^
If you are logging to Console you will get terrible performance. I have had so many issues about performance issues with Ocelot
and it is always logging level Debug, logging to Console :) Make sure you are logging to something proper in production :)

View File

@ -9,7 +9,7 @@ and override middleware. This is done as follos.
.. code-block:: csharp .. code-block:: csharp
var configuration = new OcelotMiddlewareConfiguration var configuration = new OcelotPipelineConfiguration
{ {
PreErrorResponderMiddleware = async (ctx, next) => PreErrorResponderMiddleware = async (ctx, next) =>
{ {

View File

@ -17,6 +17,17 @@ Add the following section to a ReRoute configuration.
You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be
implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped. implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped.
TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out. TimeoutValue means if a request takes more than 5 seconds it will automatically be timed out.
If you do not add a QoS section QoS will not be used. You can set the TimeoutValue in isoldation of the ExceptionsAllowedBeforeBreaking and DurationOfBreak options.
.. code-block:: json
"QoSOptions": {
"TimeoutValue":5000
}
There is no point setting the other two in isolation as they affect each other :)
If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout
on all downstream requests. If someone needs this to be configurable open an issue.

View File

@ -23,7 +23,7 @@ Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on.
PeriodTimespan - This value specifies that we can retry after a certain number of seconds. PeriodTimespan - This value specifies that we can retry after a certain number of seconds.
Limit - This value specifies the maximum number of requests that a client can make in a defined period. Limit - This value specifies the maximum number of requests that a client can make in a defined period.
You can also set the following in the GlobalConfiguration part of configuration.json You can also set the following in the GlobalConfiguration part of ocelot.json
.. code-block:: json .. code-block:: json

View File

@ -5,12 +5,114 @@ Ocelot allow's you to specify Aggregate ReRoutes that compose multiple normal Re
a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type
architecture with Ocelot. architecture with Ocelot.
This feature was requested as part of `Issue 79 <https://github.com/TomPallister/Ocelot/pull/79>`_ . This feature was requested as part of `Issue 79 <https://github.com/TomPallister/Ocelot/pull/79>`_ and further improvements were made as part of `Issue 298 <https://github.com/TomPallister/Ocelot/issue/298>`_.
In order to set this up you must do something like the following in your configuration.json. Here we have specified two normal ReRoutes and each one has a Key property. In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property.
We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute.
Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below).
Advanced register your own Aggregators
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the
downstream services and then aggregate them into a response object.
The ocelot.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below.
.. code-block:: json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/",
"UpstreamPathTemplate": "/laura",
"UpstreamHttpMethod": [
"Get"
],
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 51881
}
],
"Key": "Laura"
},
{
"DownstreamPathTemplate": "/",
"UpstreamPathTemplate": "/tom",
"UpstreamHttpMethod": [
"Get"
],
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 51882
}
],
"Key": "Tom"
}
],
"Aggregates": [
{
"ReRouteKeys": [
"Tom",
"Laura"
],
"UpstreamPathTemplate": "/",
"Aggregator": "FakeDefinedAggregator"
}
]
}
Here we have added an aggregator called FakeDefinedAggregator. Ocelot is going to look for this aggregator when it tries to aggregate this ReRoute.
In order to make the aggregator available we must add the FakeDefinedAggregator to the OcelotBuilder like below.
.. code-block:: csharp
services
.AddOcelot()
.AddSingletonDefinedAggregator<FakeDefinedAggregator>();
Now when Ocelot tries to aggregate the ReRoute above it will find the FakeDefinedAggregator in the container and use it to aggregate the ReRoute.
Because the FakeDefinedAggregator is registered in the container you can add any dependencies it needs into the container like below.
.. code-block:: csharp
services.AddSingleton<FooDependency>();
services
.AddOcelot()
.AddSingletonDefinedAggregator<FooAggregator>();
In this example FooAggregator takes a dependency on FooDependency and it will be resolved by the container.
In addition to this Ocelot lets you add transient aggregators like below.
.. code-block:: csharp
services
.AddOcelot()
.AddTransientDefinedAggregator<FakeDefinedAggregator>();
In order to make an Aggregator you must implement this interface.
.. code-block:: csharp
public interface IDefinedAggregator
{
Task<DownstreamResponse> Aggregate(List<DownstreamResponse> responses);
}
With this feature you can pretty much do whatever you want because DownstreamResponse contains Content, Headers and Status Code. We can add extra things if needed
just raise an issue on GitHub. Please note if the HttpClient throws an exception when making a request to a ReRoute in the aggregate then you will not get a DownstreamResponse for
it but you would for any that succeed. If it does throw an exception this will be logged.
Basic expecting JSON from Downstream Services
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: json .. code-block:: json
{ {
@ -65,9 +167,6 @@ If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 2
{"Tom":{"Age": 19},"Laura":{"Age": 25}} {"Tom":{"Age": 19},"Laura":{"Age": 25}}
Gotcha's / Further info
^^^^^^^^^^^^^^^^^^^^^^^
At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary
as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just
JSON without any pretty spaces etc. JSON without any pretty spaces etc.
@ -76,20 +175,13 @@ All headers will be lost from the downstream services response.
Ocelot will always return content type application/json with an aggregate request. Ocelot will always return content type application/json with an aggregate request.
If you downstream services return a 404 the aggregate will just return nothing for that downstream service.
It will not change the aggregate response into a 404 even if all the downstreams return a 404.
Gotcha's / Further info
-----------------------
You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track. You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track.
Aggregation only supports the GET HTTP Verb. Aggregation only supports the GET HTTP Verb.
If you downstream services return a 404 the aggregate will just return nothing for that downstream service.
It will not change the aggregate response into a 404 even if all the downstreams return a 404.
Future
^^^^^^
There are loads of cool ways to enchance this such as..
What happens when downstream goes slow..should we timeout?
Can we do something like GraphQL where the user chooses what fields are returned?
Can we handle 404 better etc?
Can we make this not just support a JSON dictionary response?

View File

@ -12,7 +12,7 @@ In order to use the reques tid feature you have two options.
*Global* *Global*
In your configuration.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. In your ocelot.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot.
.. code-block:: json .. code-block:: json
@ -24,7 +24,7 @@ I reccomend using the GlobalConfiguration unless you really need it to be ReRout
*ReRoute* *ReRoute*
If you want to override this for a specific ReRoute add the following to configuration.json for the specific ReRoute. If you want to override this for a specific ReRoute add the following to ocelot.json for the specific ReRoute.
.. code-block:: json .. code-block:: json

View File

@ -140,3 +140,41 @@ If you do not set UpstreamHost on a ReRoue then any host header can match it. Th
preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set. preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set.
This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ . This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ .
Priority
^^^^^^^^
In `Issue 270 <https://github.com/TomPallister/Ocelot/pull/270>`_ I finally decided to expose the ReRoute priority in
ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest.
In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below.
.. code-block:: json
{
"Priority": 0
}
0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free
to set any priority you wish.
e.g. you could have
.. code-block:: json
{
"UpstreamPathTemplate": "/goods/{catchAll}"
"Priority": 0
}
and
.. code-block:: json
{
"UpstreamPathTemplate": "/goods/delete"
"Priority": 1
}
In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have
matched /goods/{catchAll} (because this is the first ReRoute in the list!).

View File

@ -38,3 +38,53 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel
} }
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services.
ACL Token
---------
If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below.
.. code-block:: json
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 9500,
"Token": "footoken"
}
Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request.
Eureka
^^^^^^
This feature was requested as part of `Issue 262 <https://github.com/TomPallister/Ocelot/issue/262>`_ . to add support for Netflix's
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
In order to get this working add the following to ocelot.json..
.. code-block:: json
"ServiceDiscoveryProvider": {
"Type": "Eureka"
}
And following the guide `Here <https://steeltoe.io/docs/steeltoe-discovery/>`_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it.
.. code-block:: json
"eureka": {
"client": {
"serviceUrl": "http://localhost:8761/eureka/",
"shouldRegisterWithEureka": false,
"shouldFetchRegistry": true
}
}
I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there.
Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with
Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory.
When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code
is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work.

View File

@ -20,7 +20,7 @@ In your ConfigureServices method
option.Service = "Ocelot"; option.Service = "Ocelot";
}); });
Then in your configuration.json add the following to the ReRoute you want to trace.. Then in your ocelot.json add the following to the ReRoute you want to trace..
.. code-block:: json .. code-block:: json

View File

@ -0,0 +1,68 @@
Websockets
==========
Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 <https://github.com/ThreeMammals/Ocelot/issues/212>`_.
In order to get websocket proxying working with Ocelot you need to do the following.
In your Configure method you need to tell your application to use WebSockets.
.. code-block:: csharp
Configure(app =>
{
app.UseWebSockets();
app.UseOcelot().Wait();
})
Then in your ocelot.json add the following to proxy a ReRoute using websockets.
.. code-block:: json
{
"DownstreamPathTemplate": "/ws",
"UpstreamPathTemplate": "/",
"DownstreamScheme": "ws",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
}
With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer
Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and
proxy these to the upstream client.
Supported
^^^^^^^^^
1. Load Balancer
2. Routing
3. Service Discovery
This means that you can set up your downstream services running websockets and either have multiple DownstreamHostAndPorts in your ReRoute
config or hook your ReRoute into a service discovery provider and then load balance requests...Which I think is pretty cool :)
Not Supported
^^^^^^^^^^^^^
Unfortunately a lot of Ocelot's features are non websocket specific such as header and http client stuff. I've listed what won't work below.
1. Tracing
2. RequestId
3. Request Aggregation
4. Rate Limiting
5. Quality of Service
6. Middleware Injection
7. Header Transformation
8. Delegating Handlers
9. Claims Transformation
10. Caching
11. Authentication - If anyone requests it we might be able to do something with basic authentication.
12. Authorisation
I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly!

View File

@ -21,10 +21,12 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
features/configuration features/configuration
features/routing features/routing
features/requestaggregation features/requestaggregation
features/graphql
features/servicediscovery features/servicediscovery
features/servicefabric features/servicefabric
features/authentication features/authentication
features/authorisation features/authorisation
features/websockets
features/administration features/administration
features/ratelimiting features/ratelimiting
features/caching features/caching

View File

@ -1,7 +1,7 @@
Big Picture Big Picture
=========== ===========
Ocleot is aimed at people using .NET running Ocelot is aimed at people using .NET running
a micro services / service orientated architecture a micro services / service orientated architecture
that need a unified point of entry into their system. that need a unified point of entry into their system.

View File

@ -9,7 +9,7 @@ built to netcoreapp2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/s
**Install NuGet package** **Install NuGet package**
Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp2.0 projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections Install Ocelot and it's dependencies using nuget. You will need to create a netcoreapp2.0 project and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
to get up and running. to get up and running.
``Install-Package Ocelot`` ``Install-Package Ocelot``
@ -18,7 +18,7 @@ All versions can be found `here <https://www.nuget.org/packages/Ocelot/>`_.
**Configuration** **Configuration**
The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. The following is a very basic ocelot.json. It won't do anything but should get Ocelot starting.
.. code-block:: json .. code-block:: json
@ -55,7 +55,7 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("configuration.json") .AddJsonFile("ocelot.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
.ConfigureServices(s => { .ConfigureServices(s => {
@ -87,7 +87,7 @@ All versions can be found `here <https://www.nuget.org/packages/Ocelot/>`_.
**Configuration** **Configuration**
The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. The following is a very basic ocelot.json. It won't do anything but should get Ocelot starting.
.. code-block:: json .. code-block:: json
@ -135,7 +135,7 @@ An example startup using a json file for configuration can be seen below.
.SetBasePath(env.ContentRootPath) .SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("configuration.json") .AddJsonFile("ocelot.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
Configuration = builder.Build(); Configuration = builder.Build();

View File

@ -7,7 +7,10 @@ Ocelot does not support...
* 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 :( * 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 :(
* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore * Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision
I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your
Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns
it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore
.. code-block:: csharp .. code-block:: csharp
@ -25,8 +28,16 @@ Ocelot does not support...
app.UseOcelot().Wait(); app.UseOcelot().Wait();
The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. The main reasons why I don't think Swagger makes sense is we already hand roll our definition in ocelot.json.
If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json
with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration.
In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. Unless I rolled my own Swagger implementation. In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service
and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has
no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return
multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle
package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot
information would not match. Unless I rolled my own Swagger implementation.
If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this. If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might
even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this.

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Update="ocelot.json;appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6"/>
<PackageReference Include="Ocelot" Version="5.5.1"/>
<PackageReference Include="GraphQL" Version="2.0.0-alpha-870"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Ocelot.Middleware;
using Ocelot.DependencyInjection;
using GraphQL.Types;
using GraphQL;
using Ocelot.Requester;
using Ocelot.Responses;
using System.Net.Http;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
namespace OcelotGraphQL
{
public class Hero
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Query
{
private List<Hero> _heroes = new List<Hero>
{
new Hero { Id = 1, Name = "R2-D2" },
new Hero { Id = 2, Name = "Batman" },
new Hero { Id = 3, Name = "Wonder Woman" },
new Hero { Id = 4, Name = "Tom Pallister" }
};
[GraphQLMetadata("hero")]
public Hero GetHero(int id)
{
return _heroes.FirstOrDefault(x => x.Id == id);
}
}
public class GraphQlDelegatingHandler : DelegatingHandler
{
private readonly ISchema _schema;
public GraphQlDelegatingHandler(ISchema schema)
{
_schema = schema;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//try get query from body, could check http method :)
var query = await request.Content.ReadAsStringAsync();
//if not body try query string, dont hack like this in real world..
if(query.Length == 0)
{
var decoded = WebUtility.UrlDecode(request.RequestUri.Query);
query = decoded.Replace("?query=", "");
}
var result = _schema.Execute(_ =>
{
_.Query = query;
});
//maybe check for errors and headers etc in real world?
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(result)
};
//ocelot will treat this like any other http request...
return response;
}
}
public class Program
{
public static void Main(string[] args)
{
var schema = Schema.For(@"
type Hero {
id: Int
name: String
}
type Query {
hero(id: Int): Hero
}
", _ => {
_.Types.Include<Query>();
});
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("ocelot.json")
.AddEnvironmentVariables();
})
.ConfigureServices(s => {
s.AddSingleton<ISchema>(schema);
s.AddOcelot()
.AddSingletonDelegatingHandler<GraphQlDelegatingHandler>();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.UseIISIntegration()
.Configure(app =>
{
app.UseOcelot().Wait();
})
.Build()
.Run();
}
}
}

View File

@ -0,0 +1,71 @@
# Ocelot using GraphQL example
Loads of people keep asking me if Ocelot will every support GraphQL, in my mind Ocelot and GraphQL are two different things that can work together.
I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorisation / authentication or I would
bring in the awesome [graphql-dotnet](https://github.com/graphql-dotnet/graphql-dotnet) library and use it in a [DelegatingHandler](http://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html). This way you could have Ocelot and GraphQL without the extra hop to GraphQL. This same is an example of how to do that.
## Example
If you run this project with
$ dotnet run
Use postman or something to make the following requests and you can see Ocelot and GraphQL in action together...
GET http://localhost:5000/graphql?query={ hero(id: 4) { id name } }
RESPONSE
```json
{
"data": {
"hero": {
"id": 4,
"name": "Tom Pallister"
}
}
}
```
POST http://localhost:5000/graphql
BODY
```json
{ hero(id: 4) { id name } }
```
RESPONSE
```json
{
"data": {
"hero": {
"id": 4,
"name": "Tom Pallister"
}
}
}
```
## Notes
Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in ocelot.json e.g.
```json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/graphql",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "yourgraphqlhost.com",
"Port": 80
}
],
"UpstreamPathTemplate": "/graphql",
"DelegatingHandlers": [
"GraphQlDelegatingHandler"
]
}
]
}
```

View File

@ -0,0 +1,19 @@
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "jsonplaceholder.typicode.com",
"Port": 80
}
],
"UpstreamPathTemplate": "/graphql",
"DelegatingHandlers": [
"GraphQlDelegatingHandler"
]
}
]
}

View File

@ -13,7 +13,7 @@
# Service fabric # Service fabric
OcelotApplicationApiGatewayPkg/Code OcelotApplicationApiGatewayPkg/Code
OcelotApplication/OcelotApplicationApiGatewayPkg/Code/appsettings.json OcelotApplication/OcelotApplicationApiGatewayPkg/Code/appsettings.json
OcelotApplication/OcelotApplicationApiGatewayPkg/Code/configuration.json OcelotApplication/OcelotApplicationApiGatewayPkg/Code/ocelot.json
OcelotApplication/OcelotApplicationApiGatewayPkg/Code/runtimes/ OcelotApplication/OcelotApplicationApiGatewayPkg/Code/runtimes/
OcelotApplicationServicePkg/Code OcelotApplicationServicePkg/Code
OcelotApplication/OcelotApplicationApiGatewayPkg/Code/web.config OcelotApplication/OcelotApplicationApiGatewayPkg/Code/web.config

View File

@ -8,7 +8,7 @@
<PackageId>OcelotApplicationApiGateway</PackageId> <PackageId>OcelotApplicationApiGateway</PackageId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Update="configuration.json;appsettings.json;"> <None Update="ocelot.json;appsettings.json;">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>

View File

@ -67,7 +67,7 @@ namespace OcelotApplicationApiGateway
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("configuration.json") .AddJsonFile("ocelot.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
.ConfigureLogging((hostingContext, logging) => .ConfigureLogging((hostingContext, logging) =>

View File

@ -12,20 +12,19 @@ namespace Ocelot.Authentication.Middleware
public class AuthenticationMiddleware : OcelotMiddleware public class AuthenticationMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IOcelotLogger _logger;
public AuthenticationMiddleware(OcelotRequestDelegate next, public AuthenticationMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory) IOcelotLoggerFactory loggerFactory)
: base(loggerFactory.CreateLogger<AuthenticationMiddleware>())
{ {
_next = next; _next = next;
_logger = loggerFactory.CreateLogger<AuthenticationMiddleware>();
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
if (IsAuthenticatedRoute(context.DownstreamReRoute)) if (IsAuthenticatedRoute(context.DownstreamReRoute))
{ {
_logger.LogDebug($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");
var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
@ -33,25 +32,22 @@ namespace Ocelot.Authentication.Middleware
if (context.HttpContext.User.Identity.IsAuthenticated) if (context.HttpContext.User.Identity.IsAuthenticated)
{ {
_logger.LogDebug($"Client has been authenticated for {context.HttpContext.Request.Path}"); Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}");
await _next.Invoke(context); await _next.Invoke(context);
} }
else else
{ {
var error = new List<Error> var error = new UnauthenticatedError(
{ $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated");
new UnauthenticatedError(
$"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated")
};
_logger.LogError($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error.ToErrorString()}"); Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}");
SetPipelineError(context, error); SetPipelineError(context, error);
} }
} }
else else
{ {
_logger.LogTrace($"No authentication needed for {context.HttpContext.Request.Path}"); Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}");
await _next.Invoke(context); await _next.Invoke(context);
} }

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Authorisation namespace Ocelot.Authorisation
@ -32,19 +31,13 @@ namespace Ocelot.Authorisation
var authorised = values.Data.Contains(required.Value); var authorised = values.Data.Contains(required.Value);
if (!authorised) if (!authorised)
{ {
return new ErrorResponse<bool>(new List<Error> return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
{ $"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}"));
new ClaimValueNotAuthorisedError(
$"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}")
});
} }
} }
else else
{ {
return new ErrorResponse<bool>(new List<Error> return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}"));
{
new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")
});
} }
} }

View File

@ -13,30 +13,29 @@
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IClaimsAuthoriser _claimsAuthoriser; private readonly IClaimsAuthoriser _claimsAuthoriser;
private readonly IScopesAuthoriser _scopesAuthoriser; private readonly IScopesAuthoriser _scopesAuthoriser;
private readonly IOcelotLogger _logger;
public AuthorisationMiddleware(OcelotRequestDelegate next, public AuthorisationMiddleware(OcelotRequestDelegate next,
IClaimsAuthoriser claimsAuthoriser, IClaimsAuthoriser claimsAuthoriser,
IScopesAuthoriser scopesAuthoriser, IScopesAuthoriser scopesAuthoriser,
IOcelotLoggerFactory loggerFactory) IOcelotLoggerFactory loggerFactory)
:base(loggerFactory.CreateLogger<AuthorisationMiddleware>())
{ {
_next = next; _next = next;
_claimsAuthoriser = claimsAuthoriser; _claimsAuthoriser = claimsAuthoriser;
_scopesAuthoriser = scopesAuthoriser; _scopesAuthoriser = scopesAuthoriser;
_logger = loggerFactory.CreateLogger<AuthorisationMiddleware>();
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
if (IsAuthenticatedRoute(context.DownstreamReRoute)) if (IsAuthenticatedRoute(context.DownstreamReRoute))
{ {
_logger.LogDebug("route is authenticated scopes must be checked"); Logger.LogInformation("route is authenticated scopes must be checked");
var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes); var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes);
if (authorised.IsError) if (authorised.IsError)
{ {
_logger.LogDebug("error authorising user scopes"); Logger.LogWarning("error authorising user scopes");
SetPipelineError(context, authorised.Errors); SetPipelineError(context, authorised.Errors);
return; return;
@ -44,29 +43,26 @@
if (IsAuthorised(authorised)) if (IsAuthorised(authorised))
{ {
_logger.LogDebug("user scopes is authorised calling next authorisation checks"); Logger.LogInformation("user scopes is authorised calling next authorisation checks");
} }
else else
{ {
_logger.LogDebug("user scopes is not authorised setting pipeline error"); Logger.LogWarning("user scopes is not authorised setting pipeline error");
SetPipelineError(context, new List<Error> SetPipelineError(context, new UnauthorisedError(
{ $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}"));
new UnauthorisedError(
$"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}")
});
} }
} }
if (IsAuthorisedRoute(context.DownstreamReRoute)) if (IsAuthorisedRoute(context.DownstreamReRoute))
{ {
_logger.LogDebug("route is authorised"); Logger.LogInformation("route is authorised");
var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement); var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement);
if (authorised.IsError) if (authorised.IsError)
{ {
_logger.LogDebug($"Error whilst authorising {context.HttpContext.User.Identity.Name} for {context.HttpContext.User.Identity.Name}. Setting pipeline error"); Logger.LogWarning($"Error whilst authorising {context.HttpContext.User.Identity.Name}. Setting pipeline error");
SetPipelineError(context, authorised.Errors); SetPipelineError(context, authorised.Errors);
return; return;
@ -74,22 +70,19 @@
if (IsAuthorised(authorised)) if (IsAuthorised(authorised))
{ {
_logger.LogDebug($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}.");
await _next.Invoke(context); await _next.Invoke(context);
} }
else else
{ {
_logger.LogDebug($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error");
SetPipelineError(context, new List<Error> SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}"));
{
new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}")
});
} }
} }
else else
{ {
_logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
await _next.Invoke(context); await _next.Invoke(context);
} }
} }

View File

@ -1,5 +1,4 @@
using IdentityModel; using IdentityModel;
using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
@ -38,11 +37,8 @@ namespace Ocelot.Authorisation
if (matchesScopes.Count == 0) if (matchesScopes.Count == 0)
{ {
return new ErrorResponse<bool>(new List<Error> return new ErrorResponse<bool>(
{ new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'"));
new ScopeNotAuthorisedError(
$"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")
});
} }
return new OkResponse<bool>(true); return new OkResponse<bool>(true);

View File

@ -2,19 +2,16 @@
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.IO; using System.IO;
using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Middleware.Multiplexer;
namespace Ocelot.Cache.Middleware namespace Ocelot.Cache.Middleware
{ {
public class OutputCacheMiddleware : OcelotMiddleware public class OutputCacheMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IOcelotLogger _logger;
private readonly IOcelotCache<CachedResponse> _outputCache; private readonly IOcelotCache<CachedResponse> _outputCache;
private readonly IRegionCreator _regionCreator; private readonly IRegionCreator _regionCreator;
@ -22,10 +19,10 @@ namespace Ocelot.Cache.Middleware
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IOcelotCache<CachedResponse> outputCache, IOcelotCache<CachedResponse> outputCache,
IRegionCreator regionCreator) IRegionCreator regionCreator)
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
{ {
_next = next; _next = next;
_outputCache = outputCache; _outputCache = outputCache;
_logger = loggerFactory.CreateLogger<OutputCacheMiddleware>();
_regionCreator = regionCreator; _regionCreator = regionCreator;
} }
@ -37,31 +34,31 @@ namespace Ocelot.Cache.Middleware
return; return;
} }
var downstreamUrlKey = $"{context.DownstreamRequest.Method.Method}-{context.DownstreamRequest.RequestUri.OriginalString}"; var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
_logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region); var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region);
if (cached != null) if (cached != null)
{ {
_logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
var response = CreateHttpResponseMessage(cached); var response = CreateHttpResponseMessage(cached);
SetHttpResponseMessageThisRequest(context, response); SetHttpResponseMessageThisRequest(context, response);
_logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
return; return;
} }
_logger.LogDebug("no resonse cached for {downstreamUrlKey}", downstreamUrlKey); Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
await _next.Invoke(context); await _next.Invoke(context);
if (context.IsError) if (context.IsError)
{ {
_logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey); Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
return; return;
} }
@ -70,41 +67,34 @@ namespace Ocelot.Cache.Middleware
_outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
_logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
} }
private void SetHttpResponseMessageThisRequest(DownstreamContext context, HttpResponseMessage response) private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response)
{ {
context.DownstreamResponse = response; context.DownstreamResponse = response;
} }
internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
{ {
if (cached == null) if (cached == null)
{ {
return null; return null;
} }
var response = new HttpResponseMessage(cached.StatusCode);
foreach (var header in cached.Headers)
{
response.Headers.Add(header.Key, header.Value);
}
var content = new MemoryStream(Convert.FromBase64String(cached.Body)); var content = new MemoryStream(Convert.FromBase64String(cached.Body));
response.Content = new StreamContent(content); var streamContent = new StreamContent(content);
foreach (var header in cached.ContentHeaders) foreach (var header in cached.ContentHeaders)
{ {
response.Content.Headers.Add(header.Key, header.Value); streamContent.Headers.Add(header.Key, header.Value);
} }
return response; return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList());
} }
internal async Task<CachedResponse> CreateCachedResponse(HttpResponseMessage response) internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
{ {
if (response == null) if (response == null)
{ {
@ -112,7 +102,7 @@ namespace Ocelot.Cache.Middleware
} }
var statusCode = response.StatusCode; var statusCode = response.StatusCode;
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value); var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
string body = null; string body = null;
if (response.Content != null) if (response.Content != null)

View File

@ -1,9 +1,5 @@
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ocelot.Cache;
using Ocelot.Configuration.Provider;
namespace Ocelot.Cache namespace Ocelot.Cache
{ {
@ -11,7 +7,7 @@ namespace Ocelot.Cache
[Route("outputcache")] [Route("outputcache")]
public class OutputCacheController : Controller public class OutputCacheController : Controller
{ {
private IOcelotCache<CachedResponse> _cache; private readonly IOcelotCache<CachedResponse> _cache;
public OutputCacheController(IOcelotCache<CachedResponse> cache) public OutputCacheController(IOcelotCache<CachedResponse> cache)
{ {

View File

@ -12,28 +12,27 @@ namespace Ocelot.Claims.Middleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IAddClaimsToRequest _addClaimsToRequest; private readonly IAddClaimsToRequest _addClaimsToRequest;
private readonly IOcelotLogger _logger;
public ClaimsBuilderMiddleware(OcelotRequestDelegate next, public ClaimsBuilderMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IAddClaimsToRequest addClaimsToRequest) IAddClaimsToRequest addClaimsToRequest)
:base(loggerFactory.CreateLogger<ClaimsBuilderMiddleware>())
{ {
_next = next; _next = next;
_addClaimsToRequest = addClaimsToRequest; _addClaimsToRequest = addClaimsToRequest;
_logger = loggerFactory.CreateLogger<ClaimsBuilderMiddleware>();
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
if (context.DownstreamReRoute.ClaimsToClaims.Any()) if (context.DownstreamReRoute.ClaimsToClaims.Any())
{ {
_logger.LogDebug("this route has instructions to convert claims to other claims"); Logger.LogDebug("this route has instructions to convert claims to other claims");
var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext); var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext);
if (result.IsError) if (result.IsError)
{ {
_logger.LogDebug("error converting claims to other claims, setting pipeline error"); Logger.LogDebug("error converting claims to other claims, setting pipeline error");
SetPipelineError(context, result.Errors); SetPipelineError(context, result.Errors);
return; return;

View File

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

View File

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

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Ocelot.Values; using Ocelot.Values;
using System.Linq; using System.Linq;
using Ocelot.Configuration.Creator;
namespace Ocelot.Configuration.Builder namespace Ocelot.Configuration.Builder
{ {
@ -37,11 +38,16 @@ namespace Ocelot.Configuration.Builder
private string _upstreamHost; private string _upstreamHost;
private string _key; private string _key;
private List<string> _delegatingHandlers; private List<string> _delegatingHandlers;
private List<AddHeader> _addHeadersToDownstream;
private List<AddHeader> _addHeadersToUpstream;
private bool _dangerousAcceptAnyServerCertificateValidator;
public DownstreamReRouteBuilder() public DownstreamReRouteBuilder()
{ {
_downstreamAddresses = new List<DownstreamHostAndPort>(); _downstreamAddresses = new List<DownstreamHostAndPort>();
_delegatingHandlers = new List<string>(); _delegatingHandlers = new List<string>();
_addHeadersToDownstream = new List<AddHeader>();
_addHeadersToUpstream = new List<AddHeader>();
} }
public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses) public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
@ -224,6 +230,24 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithAddHeadersToDownstream(List<AddHeader> addHeadersToDownstream)
{
_addHeadersToDownstream = addHeadersToDownstream;
return this;
}
public DownstreamReRouteBuilder WithAddHeadersToUpstream(List<AddHeader> addHeadersToUpstream)
{
_addHeadersToUpstream = addHeadersToUpstream;
return this;
}
public DownstreamReRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator)
{
_dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
return this;
}
public DownstreamReRoute Build() public DownstreamReRoute Build()
{ {
return new DownstreamReRoute( return new DownstreamReRoute(
@ -253,7 +277,10 @@ namespace Ocelot.Configuration.Builder
_authenticationOptions, _authenticationOptions,
new PathTemplate(_downstreamPathTemplate), new PathTemplate(_downstreamPathTemplate),
_reRouteKey, _reRouteKey,
_delegatingHandlers); _delegatingHandlers,
_addHeadersToDownstream,
_addHeadersToUpstream,
_dangerousAcceptAnyServerCertificateValidator);
} }
} }
} }

View File

@ -14,6 +14,7 @@ namespace Ocelot.Configuration.Builder
private List<HttpMethod> _upstreamHttpMethod; private List<HttpMethod> _upstreamHttpMethod;
private string _upstreamHost; private string _upstreamHost;
private List<DownstreamReRoute> _downstreamReRoutes; private List<DownstreamReRoute> _downstreamReRoutes;
private string _aggregator;
public ReRouteBuilder() public ReRouteBuilder()
{ {
@ -56,6 +57,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteBuilder WithAggregator(string aggregator)
{
_aggregator = aggregator;
return this;
}
public ReRoute Build() public ReRoute Build()
{ {
return new ReRoute( return new ReRoute(
@ -63,7 +70,8 @@ namespace Ocelot.Configuration.Builder
new PathTemplate(_upstreamTemplate), new PathTemplate(_upstreamTemplate),
_upstreamHttpMethod, _upstreamHttpMethod,
_upstreamTemplatePattern, _upstreamTemplatePattern,
_upstreamHost _upstreamHost,
_aggregator
); );
} }
} }

View File

@ -5,28 +5,35 @@ namespace Ocelot.Configuration.Builder
private string _serviceDiscoveryProviderHost; private string _serviceDiscoveryProviderHost;
private int _serviceDiscoveryProviderPort; private int _serviceDiscoveryProviderPort;
private string _type; private string _type;
private string _token;
public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
{ {
_serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost;
return this; return this;
} }
public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) public ServiceProviderConfigurationBuilder WithPort(int serviceDiscoveryProviderPort)
{ {
_serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort;
return this; return this;
} }
public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderType(string type) public ServiceProviderConfigurationBuilder WithType(string type)
{ {
_type = type; _type = type;
return this; return this;
} }
public ServiceProviderConfigurationBuilder WithToken(string token)
{
_token = token;
return this;
}
public ServiceProviderConfiguration Build() public ServiceProviderConfiguration Build()
{ {
return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort); return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token);
} }
} }
} }

View File

@ -0,0 +1,14 @@
namespace Ocelot.Configuration.Creator
{
public class AddHeader
{
public AddHeader(string key, string value)
{
Key = key;
Value = value;
}
public string Key { get; }
public string Value { get; }
}
}

View File

@ -26,8 +26,7 @@ namespace Ocelot.Configuration.Creator
if (claimToThing.IsError) if (claimToThing.IsError)
{ {
_logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest", _logger.LogDebug($"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect");
$"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect");
} }
else else
{ {

View File

@ -16,9 +16,8 @@ namespace Ocelot.Configuration.Creator
/// <summary> /// <summary>
/// Register as singleton /// Register as singleton
/// </summary> /// </summary>
public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator public class FileInternalConfigurationCreator : IInternalConfigurationCreator
{ {
private readonly IOptions<FileConfiguration> _options;
private readonly IConfigurationValidator _configurationValidator; private readonly IConfigurationValidator _configurationValidator;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IClaimsToThingCreator _claimsToThingCreator; private readonly IClaimsToThingCreator _claimsToThingCreator;
@ -35,8 +34,7 @@ namespace Ocelot.Configuration.Creator
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
public FileOcelotConfigurationCreator( public FileInternalConfigurationCreator(
IOptions<FileConfiguration> options,
IConfigurationValidator configurationValidator, IConfigurationValidator configurationValidator,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IClaimsToThingCreator claimsToThingCreator, IClaimsToThingCreator claimsToThingCreator,
@ -62,9 +60,8 @@ namespace Ocelot.Configuration.Creator
_requestIdKeyCreator = requestIdKeyCreator; _requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
_authOptionsCreator = authOptionsCreator; _authOptionsCreator = authOptionsCreator;
_options = options;
_configurationValidator = configurationValidator; _configurationValidator = configurationValidator;
_logger = loggerFactory.CreateLogger<FileOcelotConfigurationCreator>(); _logger = loggerFactory.CreateLogger<FileInternalConfigurationCreator>();
_claimsToThingCreator = claimsToThingCreator; _claimsToThingCreator = claimsToThingCreator;
_serviceProviderConfigCreator = serviceProviderConfigCreator; _serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator; _qosOptionsCreator = qosOptionsCreator;
@ -72,19 +69,19 @@ namespace Ocelot.Configuration.Creator
_httpHandlerOptionsCreator = httpHandlerOptionsCreator; _httpHandlerOptionsCreator = httpHandlerOptionsCreator;
} }
public async Task<Response<IOcelotConfiguration>> Create(FileConfiguration fileConfiguration) public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration)
{ {
var config = await SetUpConfiguration(fileConfiguration); var config = await SetUpConfiguration(fileConfiguration);
return config; return config;
} }
private async Task<Response<IOcelotConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration) private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration)
{ {
var response = await _configurationValidator.IsValid(fileConfiguration); var response = await _configurationValidator.IsValid(fileConfiguration);
if (response.Data.IsError) if (response.Data.IsError)
{ {
return new ErrorResponse<IOcelotConfiguration>(response.Data.Errors); return new ErrorResponse<IInternalConfiguration>(response.Data.Errors);
} }
var reRoutes = new List<ReRoute>(); var reRoutes = new List<ReRoute>();
@ -106,9 +103,9 @@ namespace Ocelot.Configuration.Creator
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
var config = new OcelotConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey);
return new OkResponse<IOcelotConfiguration>(config); return new OkResponse<IInternalConfiguration>(config);
} }
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
@ -132,6 +129,7 @@ namespace Ocelot.Configuration.Creator
.WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithDownstreamReRoutes(applicableReRoutes) .WithDownstreamReRoutes(applicableReRoutes)
.WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithUpstreamHost(aggregateReRoute.UpstreamHost)
.WithAggregator(aggregateReRoute.Aggregator)
.Build(); .Build();
return reRoute; return reRoute;
@ -213,6 +211,9 @@ namespace Ocelot.Configuration.Creator
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.WithUpstreamHost(fileReRoute.UpstreamHost) .WithUpstreamHost(fileReRoute.UpstreamHost)
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers) .WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
.Build(); .Build();
return reRoute; return reRoute;

View File

@ -1,44 +1,76 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Responses;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
{ {
private IBaseUrlFinder _finder; private readonly IPlaceholders _placeholders;
private readonly Dictionary<string, Func<string>> _placeholders; private readonly IOcelotLogger _logger;
public HeaderFindAndReplaceCreator(IBaseUrlFinder finder) public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory)
{ {
_finder = finder; _logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();
_placeholders = new Dictionary<string, Func<string>>(); _placeholders = placeholders;
_placeholders.Add("{BaseUrl}", () => _finder.Find());
} }
public HeaderTransformations Create(FileReRoute fileReRoute) public HeaderTransformations Create(FileReRoute fileReRoute)
{ {
var upstream = new List<HeaderFindAndReplace>(); var upstream = new List<HeaderFindAndReplace>();
var addHeadersToUpstream = new List<AddHeader>();
foreach(var input in fileReRoute.UpstreamHeaderTransform) foreach(var input in fileReRoute.UpstreamHeaderTransform)
{ {
var hAndr = Map(input); if (input.Value.Contains(","))
upstream.Add(hAndr); {
var hAndr = Map(input);
if (!hAndr.IsError)
{
upstream.Add(hAndr.Data);
}
else
{
_logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}");
}
}
else
{
addHeadersToUpstream.Add(new AddHeader(input.Key, input.Value));
}
} }
var downstream = new List<HeaderFindAndReplace>(); var downstream = new List<HeaderFindAndReplace>();
var addHeadersToDownstream = new List<AddHeader>();
foreach(var input in fileReRoute.DownstreamHeaderTransform) foreach(var input in fileReRoute.DownstreamHeaderTransform)
{ {
var hAndr = Map(input); if(input.Value.Contains(","))
downstream.Add(hAndr); {
var hAndr = Map(input);
if(!hAndr.IsError)
{
downstream.Add(hAndr.Data);
}
else
{
_logger.LogWarning($"Unable to add DownstreamHeaderTransform {input.Key}: {input.Value}");
}
}
else
{
addHeadersToDownstream.Add(new AddHeader(input.Key, input.Value));
}
} }
return new HeaderTransformations(upstream, downstream); return new HeaderTransformations(upstream, downstream, addHeadersToDownstream, addHeadersToUpstream);
} }
private HeaderFindAndReplace Map(KeyValuePair<string,string> input) private Response<HeaderFindAndReplace> Map(KeyValuePair<string,string> input)
{ {
var findAndReplace = input.Value.Split(","); var findAndReplace = input.Value.Split(",");
@ -51,16 +83,19 @@ namespace Ocelot.Configuration.Creator
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1)); var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
if(_placeholders.ContainsKey(placeholder)) var value = _placeholders.Get(placeholder);
if(value.IsError)
{ {
var value = _placeholders[placeholder].Invoke(); return new ErrorResponse<HeaderFindAndReplace>(value.Errors);
replace = replace.Replace(placeholder, value);
} }
replace = replace.Replace(placeholder, value.Data);
} }
var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0); var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0);
return hAndr; return new OkResponse<HeaderFindAndReplace>(hAndr);
} }
} }
} }

View File

@ -4,14 +4,23 @@ namespace Ocelot.Configuration.Creator
{ {
public class HeaderTransformations public class HeaderTransformations
{ {
public HeaderTransformations(List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> downstream) public HeaderTransformations(
List<HeaderFindAndReplace> upstream,
List<HeaderFindAndReplace> downstream,
List<AddHeader> addHeaderToDownstream,
List<AddHeader> addHeaderToUpstream)
{ {
AddHeadersToDownstream = addHeaderToDownstream;
AddHeadersToUpstream = addHeaderToUpstream;
Upstream = upstream; Upstream = upstream;
Downstream = downstream; Downstream = downstream;
} }
public List<HeaderFindAndReplace> Upstream {get;private set;} public List<HeaderFindAndReplace> Upstream { get; }
public List<HeaderFindAndReplace> Downstream {get;private set;} public List<HeaderFindAndReplace> Downstream { get; }
public List<AddHeader> AddHeadersToDownstream { get; }
public List<AddHeader> AddHeadersToUpstream { get; }
} }
} }

View File

@ -1,13 +1,24 @@
using Ocelot.Configuration.File; using Butterfly.Client.Tracing;
using Ocelot.Configuration.File;
using Ocelot.Requester;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
{ {
private IServiceTracer _tracer;
public HttpHandlerOptionsCreator(IServiceTracer tracer)
{
_tracer = tracer;
}
public HttpHandlerOptions Create(FileReRoute fileReRoute) public HttpHandlerOptions Create(FileReRoute fileReRoute)
{ {
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) ? fileReRoute.HttpHandlerOptions.UseTracing : false;
return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect, return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect,
fileReRoute.HttpHandlerOptions.UseCookieContainer, fileReRoute.HttpHandlerOptions.UseTracing); fileReRoute.HttpHandlerOptions.UseCookieContainer, useTracing);
} }
} }
} }

View File

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses;
namespace Ocelot.Configuration.Creator
{
public interface IInternalConfigurationCreator
{
Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration);
}
}

View File

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

View File

@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Ocelot.Configuration.Provider;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {

View File

@ -11,9 +11,10 @@ namespace Ocelot.Configuration.Creator
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
return new ServiceProviderConfigurationBuilder() return new ServiceProviderConfigurationBuilder()
.WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) .WithHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
.WithServiceDiscoveryProviderPort(serviceProviderPort) .WithPort(serviceProviderPort)
.WithServiceDiscoveryProviderType(globalConfiguration?.ServiceDiscoveryProvider?.Type) .WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type)
.WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token)
.Build(); .Build();
} }
} }

View File

@ -42,7 +42,7 @@ namespace Ocelot.Configuration.Creator
if (upstreamTemplate == "/") if (upstreamTemplate == "/")
{ {
return new UpstreamPathTemplate(RegExForwardSlashOnly, 1); return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority);
} }
if(upstreamTemplate.EndsWith("/")) if(upstreamTemplate.EndsWith("/"))
@ -54,7 +54,7 @@ namespace Ocelot.Configuration.Creator
? $"^{upstreamTemplate}{RegExMatchEndString}" ? $"^{upstreamTemplate}{RegExMatchEndString}"
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
return new UpstreamPathTemplate(route, 1); return new UpstreamPathTemplate(route, reRoute.Priority);
} }
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket) private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.Configuration namespace Ocelot.Configuration
@ -32,8 +33,13 @@ namespace Ocelot.Configuration
AuthenticationOptions authenticationOptions, AuthenticationOptions authenticationOptions,
PathTemplate downstreamPathTemplate, PathTemplate downstreamPathTemplate,
string reRouteKey, string reRouteKey,
List<string> delegatingHandlers) List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream,
bool dangerousAcceptAnyServerCertificateValidator)
{ {
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream;
DelegatingHandlers = delegatingHandlers; DelegatingHandlers = delegatingHandlers;
Key = key; Key = key;
UpstreamPathTemplate = upstreamPathTemplate; UpstreamPathTemplate = upstreamPathTemplate;
@ -61,6 +67,7 @@ namespace Ocelot.Configuration
AuthenticationOptions = authenticationOptions; AuthenticationOptions = authenticationOptions;
DownstreamPathTemplate = downstreamPathTemplate; DownstreamPathTemplate = downstreamPathTemplate;
ReRouteKey = reRouteKey; ReRouteKey = reRouteKey;
AddHeadersToUpstream = addHeadersToUpstream;
} }
public string Key { get; private set; } public string Key { get; private set; }
@ -90,5 +97,8 @@ namespace Ocelot.Configuration
public PathTemplate DownstreamPathTemplate { get; private set; } public PathTemplate DownstreamPathTemplate { get; private set; }
public string ReRouteKey { get; private set; } public string ReRouteKey { get; private set; }
public List<string> DelegatingHandlers {get;private set;} public List<string> DelegatingHandlers {get;private set;}
public List<AddHeader> AddHeadersToDownstream {get;private set;}
public List<AddHeader> AddHeadersToUpstream { get; private set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; private set; }
} }
} }

View File

@ -8,11 +8,14 @@ namespace Ocelot.Configuration.File
public string UpstreamPathTemplate { get;set; } public string UpstreamPathTemplate { get;set; }
public string UpstreamHost { get; set; } public string UpstreamHost { get; set; }
public bool ReRouteIsCaseSensitive { get; set; } public bool ReRouteIsCaseSensitive { get; set; }
public string Aggregator { get; set; }
// Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :)
public List<string> UpstreamHttpMethod public List<string> UpstreamHttpMethod
{ {
get { return new List<string> {"Get"}; } get { return new List<string> {"Get"}; }
} }
public int Priority {get;set;} = 1;
} }
} }

View File

@ -4,8 +4,8 @@
{ {
public FileHttpHandlerOptions() public FileHttpHandlerOptions()
{ {
AllowAutoRedirect = true; AllowAutoRedirect = false;
UseCookieContainer = true; UseCookieContainer = false;
} }
public bool AllowAutoRedirect { get; set; } public bool AllowAutoRedirect { get; set; }

View File

@ -20,6 +20,7 @@ namespace Ocelot.Configuration.File
UpstreamHeaderTransform = new Dictionary<string, string>(); UpstreamHeaderTransform = new Dictionary<string, string>();
DownstreamHostAndPorts = new List<FileHostAndPort>(); DownstreamHostAndPorts = new List<FileHostAndPort>();
DelegatingHandlers = new List<string>(); DelegatingHandlers = new List<string>();
Priority = 1;
} }
public string DownstreamPathTemplate { get; set; } public string DownstreamPathTemplate { get; set; }
@ -46,5 +47,8 @@ namespace Ocelot.Configuration.File
public string UpstreamHost { get; set; } public string UpstreamHost { get; set; }
public string Key { get;set; } public string Key { get;set; }
public List<string> DelegatingHandlers {get;set;} public List<string> DelegatingHandlers {get;set;}
public int Priority { get;set; }
public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
} }
} }

View File

@ -5,5 +5,6 @@ namespace Ocelot.Configuration.File
public string Host {get;set;} public string Host {get;set;}
public int Port { get; set; } public int Port { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string Token { get; set; }
} }
} }

View File

@ -4,5 +4,6 @@
{ {
string UpstreamPathTemplate { get; set; } string UpstreamPathTemplate { get; set; }
bool ReRouteIsCaseSensitive { get; set; } bool ReRouteIsCaseSensitive { get; set; }
int Priority {get;set;}
} }
} }

View File

@ -2,34 +2,34 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.Raft; using Ocelot.Raft;
using Rafty.Concensus; using Rafty.Concensus;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
using Repository;
[Authorize] [Authorize]
[Route("configuration")] [Route("configuration")]
public class FileConfigurationController : Controller public class FileConfigurationController : Controller
{ {
private readonly IFileConfigurationProvider _configGetter; private readonly IFileConfigurationRepository _repo;
private readonly IFileConfigurationSetter _configSetter; private readonly IFileConfigurationSetter _setter;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _provider;
public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider) public FileConfigurationController(IFileConfigurationRepository repo, IFileConfigurationSetter setter, IServiceProvider provider)
{ {
_configGetter = getFileConfig; _repo = repo;
_configSetter = configSetter; _setter = setter;
_serviceProvider = serviceProvider; _provider = provider;
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> Get() public async Task<IActionResult> Get()
{ {
var response = await _configGetter.Get(); var response = await _repo.Get();
if(response.IsError) if(response.IsError)
{ {
@ -43,7 +43,7 @@ namespace Ocelot.Configuration
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration) public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
{ {
//todo - this code is a bit shit sort it out.. //todo - this code is a bit shit sort it out..
var test = _serviceProvider.GetService(typeof(INode)); var test = _provider.GetService(typeof(INode));
if (test != null) if (test != null)
{ {
var node = (INode)test; var node = (INode)test;
@ -56,7 +56,7 @@ namespace Ocelot.Configuration
return new OkObjectResult(result.Command.Configuration); return new OkObjectResult(result.Command.Configuration);
} }
var response = await _configSetter.Set(fileConfiguration); var response = await _setter.Set(fileConfiguration);
if (response.IsError) if (response.IsError)
{ {

View File

@ -1,9 +1,7 @@
using System.Collections.Generic; namespace Ocelot.Configuration
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
namespace Ocelot.Configuration.Provider
{ {
using System.Collections.Generic;
public interface IIdentityServerConfiguration public interface IIdentityServerConfiguration
{ {
string ApiName { get; } string ApiName { get; }

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
public interface IOcelotConfiguration public interface IInternalConfiguration
{ {
List<ReRoute> ReRoutes { get; } List<ReRoute> ReRoutes { get; }
string AdministrationPath {get;} string AdministrationPath {get;}

View File

@ -1,9 +1,7 @@
using System.Collections.Generic; namespace Ocelot.Configuration
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
namespace Ocelot.Configuration.Provider
{ {
using System.Collections.Generic;
public class IdentityServerConfiguration : IIdentityServerConfiguration public class IdentityServerConfiguration : IIdentityServerConfiguration
{ {
public IdentityServerConfiguration( public IdentityServerConfiguration(
@ -22,11 +20,11 @@ namespace Ocelot.Configuration.Provider
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
} }
public string ApiName { get; private set; } public string ApiName { get; }
public bool RequireHttps { get; private set; } public bool RequireHttps { get; }
public List<string> AllowedScopes { get; private set; } public List<string> AllowedScopes { get; }
public string ApiSecret { get; private set; } public string ApiSecret { get; }
public string CredentialsSigningCertificateLocation { get; private set; } public string CredentialsSigningCertificateLocation { get; }
public string CredentialsSigningCertificatePassword { get; private set; } public string CredentialsSigningCertificatePassword { get; }
} }
} }

View File

@ -2,9 +2,9 @@ using System.Collections.Generic;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
public class OcelotConfiguration : IOcelotConfiguration public class InternalConfiguration : IInternalConfiguration
{ {
public OcelotConfiguration(List<ReRoute> reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) public InternalConfiguration(List<ReRoute> reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId)
{ {
ReRoutes = reRoutes; ReRoutes = reRoutes;
AdministrationPath = administrationPath; AdministrationPath = administrationPath;

View File

@ -20,22 +20,14 @@ namespace Ocelot.Configuration.Parser
if (instructions.Length <= 1) if (instructions.Length <= 1)
{ {
return new ErrorResponse<ClaimToThing>( return new ErrorResponse<ClaimToThing>(new NoInstructionsError(SplitToken));
new List<Error>
{
new NoInstructionsError(SplitToken)
});
} }
var claimMatch = _claimRegex.IsMatch(instructions[0]); var claimMatch = _claimRegex.IsMatch(instructions[0]);
if (!claimMatch) if (!claimMatch)
{ {
return new ErrorResponse<ClaimToThing>( return new ErrorResponse<ClaimToThing>(new InstructionNotForClaimsError());
new List<Error>
{
new InstructionNotForClaimsError()
});
} }
var newKey = GetIndexValue(instructions[0]); var newKey = GetIndexValue(instructions[0]);
@ -53,11 +45,7 @@ namespace Ocelot.Configuration.Parser
} }
catch (Exception exception) catch (Exception exception)
{ {
return new ErrorResponse<ClaimToThing>( return new ErrorResponse<ClaimToThing>(new ParsingConfigurationHeaderError(exception));
new List<Error>
{
new ParsingConfigurationHeaderError(exception)
});
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,32 +0,0 @@
using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Responses;
namespace Ocelot.Configuration.Provider
{
/// <summary>
/// Register as singleton
/// </summary>
public class OcelotConfigurationProvider : IOcelotConfigurationProvider
{
private readonly IOcelotConfigurationRepository _config;
public OcelotConfigurationProvider(IOcelotConfigurationRepository repo)
{
_config = repo;
}
public async Task<Response<IOcelotConfiguration>> Get()
{
var repoConfig = await _config.Get();
if (repoConfig.IsError)
{
return new ErrorResponse<IOcelotConfiguration>(repoConfig.Errors);
}
return new OkResponse<IOcelotConfiguration>(repoConfig.Data);
}
}
}

View File

@ -16,12 +16,12 @@ namespace Ocelot.Configuration
TimeoutStrategy = timeoutStrategy; TimeoutStrategy = timeoutStrategy;
} }
public int ExceptionsAllowedBeforeBreaking { get; private set; } public int ExceptionsAllowedBeforeBreaking { get; }
public int DurationOfBreak { get; private set; } public int DurationOfBreak { get; }
public int TimeoutValue { get; private set; } public int TimeoutValue { get; }
public TimeoutStrategy TimeoutStrategy { get; private set; } public TimeoutStrategy TimeoutStrategy { get; }
} }
} }

View File

@ -12,13 +12,15 @@ namespace Ocelot.Configuration
PathTemplate upstreamPathTemplate, PathTemplate upstreamPathTemplate,
List<HttpMethod> upstreamHttpMethod, List<HttpMethod> upstreamHttpMethod,
UpstreamPathTemplate upstreamTemplatePattern, UpstreamPathTemplate upstreamTemplatePattern,
string upstreamHost) string upstreamHost,
string aggregator)
{ {
UpstreamHost = upstreamHost; UpstreamHost = upstreamHost;
DownstreamReRoute = downstreamReRoute; DownstreamReRoute = downstreamReRoute;
UpstreamPathTemplate = upstreamPathTemplate; UpstreamPathTemplate = upstreamPathTemplate;
UpstreamHttpMethod = upstreamHttpMethod; UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern; UpstreamTemplatePattern = upstreamTemplatePattern;
Aggregator = aggregator;
} }
public PathTemplate UpstreamPathTemplate { get; private set; } public PathTemplate UpstreamPathTemplate { get; private set; }
@ -26,5 +28,6 @@ namespace Ocelot.Configuration
public List<HttpMethod> UpstreamHttpMethod { get; private set; } public List<HttpMethod> UpstreamHttpMethod { get; private set; }
public string UpstreamHost { get; private set; } public string UpstreamHost { get; private set; }
public List<DownstreamReRoute> DownstreamReRoute { get; private set; } public List<DownstreamReRoute> DownstreamReRoute { get; private set; }
public string Aggregator {get; private set;}
} }
} }

View File

@ -17,10 +17,16 @@ namespace Ocelot.Configuration.Repository
private string _previousAsJson; private string _previousAsJson;
private readonly Timer _timer; private readonly Timer _timer;
private bool _polling; private bool _polling;
private readonly IConsulPollerConfiguration _config;
public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) public ConsulFileConfigurationPoller(
IOcelotLoggerFactory factory,
IFileConfigurationRepository repo,
IFileConfigurationSetter setter,
IConsulPollerConfiguration config)
{ {
_setter = setter; _setter = setter;
_config = config;
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>(); _logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
_repo = repo; _repo = repo;
_previousAsJson = ""; _previousAsJson = "";
@ -34,18 +40,18 @@ namespace Ocelot.Configuration.Repository
_polling = true; _polling = true;
await Poll(); await Poll();
_polling = false; _polling = false;
}, null, 0, 1000); }, null, 0, _config.Delay);
} }
private async Task Poll() private async Task Poll()
{ {
_logger.LogDebug("Started polling consul"); _logger.LogInformation("Started polling consul");
var fileConfig = await _repo.Get(); var fileConfig = await _repo.Get();
if(fileConfig.IsError) if(fileConfig.IsError)
{ {
_logger.LogDebug($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
return; return;
} }
@ -57,14 +63,13 @@ namespace Ocelot.Configuration.Repository
_previousAsJson = asJson; _previousAsJson = asJson;
} }
_logger.LogDebug("Finished polling consul"); _logger.LogInformation("Finished polling consul");
} }
/// <summary> /// <summary>
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day! /// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
/// </summary> /// </summary>
/// <param name="config"></param> /// <returns>hash of the config</returns>
/// <returns></returns>
private string ToJson(FileConfiguration config) private string ToJson(FileConfiguration config)
{ {
var currentHash = JsonConvert.SerializeObject(config); var currentHash = JsonConvert.SerializeObject(config);

View File

@ -1,42 +1,60 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Consul;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Responses;
using Ocelot.ServiceDiscovery;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
using System;
using System.Text;
using System.Threading.Tasks;
using Consul;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Infrastructure.Consul;
using Ocelot.Logging;
using Ocelot.Responses;
using Ocelot.ServiceDiscovery.Configuration;
public class ConsulFileConfigurationRepository : IFileConfigurationRepository public class ConsulFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly ConsulClient _consul; private readonly IConsulClient _consul;
private string _ocelotConfiguration = "OcelotConfiguration"; private const string OcelotConfiguration = "InternalConfiguration";
private readonly Cache.IOcelotCache<FileConfiguration> _cache; private readonly Cache.IOcelotCache<FileConfiguration> _cache;
private readonly IOcelotLogger _logger;
public ConsulFileConfigurationRepository(Cache.IOcelotCache<FileConfiguration> cache, ServiceProviderConfiguration serviceProviderConfig) public ConsulFileConfigurationRepository(
Cache.IOcelotCache<FileConfiguration> cache,
IInternalConfigurationRepository repo,
IConsulClientFactory factory,
IOcelotLoggerFactory loggerFactory)
{ {
var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.Host) ? "localhost" : serviceProviderConfig?.Host; _logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
var consulPort = serviceProviderConfig?.Port ?? 8500;
var configuration = new ConsulRegistryConfiguration(consulHost, consulPort, _ocelotConfiguration);
_cache = cache; _cache = cache;
_consul = new ConsulClient(c =>
var internalConfig = repo.Get();
var consulHost = "localhost";
var consulPort = 8500;
string token = null;
if (!internalConfig.IsError)
{ {
c.Address = new Uri($"http://{configuration.HostName}:{configuration.Port}"); consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host;
}); consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort;
token = internalConfig.Data.ServiceProviderConfiguration?.Token;
}
var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, token);
_consul = factory.Get(config);
} }
public async Task<Response<FileConfiguration>> Get() public async Task<Response<FileConfiguration>> Get()
{ {
var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); var config = _cache.Get(OcelotConfiguration, OcelotConfiguration);
if (config != null) if (config != null)
{ {
return new OkResponse<FileConfiguration>(config); return new OkResponse<FileConfiguration>(config);
} }
var queryResult = await _consul.KV.Get(_ocelotConfiguration); var queryResult = await _consul.KV.Get(OcelotConfiguration);
if (queryResult.Response == null) if (queryResult.Response == null)
{ {
@ -54,11 +72,11 @@ namespace Ocelot.Configuration.Repository
public async Task<Response> Set(FileConfiguration ocelotConfiguration) public async Task<Response> Set(FileConfiguration ocelotConfiguration)
{ {
var json = JsonConvert.SerializeObject(ocelotConfiguration); var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
var bytes = Encoding.UTF8.GetBytes(json); var bytes = Encoding.UTF8.GetBytes(json);
var kvPair = new KVPair(_ocelotConfiguration) var kvPair = new KVPair(OcelotConfiguration)
{ {
Value = bytes Value = bytes
}; };
@ -67,7 +85,7 @@ namespace Ocelot.Configuration.Repository
if (result.Response) if (result.Response)
{ {
_cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration); _cache.AddAndDelete(OcelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), OcelotConfiguration);
return new OkResponse(); return new OkResponse();
} }

View File

@ -7,15 +7,17 @@ using Ocelot.Responses;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public class FileConfigurationRepository : IFileConfigurationRepository public class DiskFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly string _configFilePath; private readonly string _configFilePath;
private static readonly object _lock = new object(); private static readonly object _lock = new object();
public FileConfigurationRepository(IHostingEnvironment hostingEnvironment) private const string ConfigurationFileName = "ocelot";
public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment)
{ {
_configFilePath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; _configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
} }
public Task<Response<FileConfiguration>> Get() public Task<Response<FileConfiguration>> Get()
@ -34,7 +36,7 @@ namespace Ocelot.Configuration.Repository
public Task<Response> Set(FileConfiguration fileConfiguration) public Task<Response> Set(FileConfiguration fileConfiguration)
{ {
string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
lock(_lock) lock(_lock)
{ {

View File

@ -0,0 +1,7 @@
namespace Ocelot.Configuration.Repository
{
public interface IConsulPollerConfiguration
{
int Delay { get; }
}
}

View File

@ -0,0 +1,10 @@
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
public interface IInternalConfigurationRepository
{
Response<IInternalConfiguration> Get();
Response AddOrReplace(IInternalConfiguration internalConfiguration);
}
}

View File

@ -1,12 +0,0 @@
using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
public interface IOcelotConfigurationRepository
{
Task<Response<IOcelotConfiguration>> Get();
Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration);
}
}

View File

@ -0,0 +1,7 @@
namespace Ocelot.Configuration.Repository
{
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration
{
public int Delay => 1000;
}
}

View File

@ -0,0 +1,29 @@
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
/// <summary>
/// Register as singleton
/// </summary>
public class InMemoryInternalConfigurationRepository : IInternalConfigurationRepository
{
private static readonly object LockObject = new object();
private IInternalConfiguration _internalConfiguration;
public Response<IInternalConfiguration> Get()
{
return new OkResponse<IInternalConfiguration>(_internalConfiguration);
}
public Response AddOrReplace(IInternalConfiguration internalConfiguration)
{
lock (LockObject)
{
_internalConfiguration = internalConfiguration;
}
return new OkResponse();
}
}
}

View File

@ -1,30 +0,0 @@
using System.Threading.Tasks;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository
{
/// <summary>
/// Register as singleton
/// </summary>
public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository
{
private static readonly object LockObject = new object();
private IOcelotConfiguration _ocelotConfiguration;
public Task<Response<IOcelotConfiguration>> Get()
{
return Task.FromResult<Response<IOcelotConfiguration>>(new OkResponse<IOcelotConfiguration>(_ocelotConfiguration));
}
public Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration)
{
lock (LockObject)
{
_ocelotConfiguration = ocelotConfiguration;
}
return Task.FromResult<Response>(new OkResponse());
}
}
}

View File

@ -2,15 +2,17 @@
{ {
public class ServiceProviderConfiguration public class ServiceProviderConfiguration
{ {
public ServiceProviderConfiguration(string type, string host, int port) public ServiceProviderConfiguration(string type, string host, int port, string token)
{ {
Host = host; Host = host;
Port = port; Port = port;
Token = token;
Type = type; Type = type;
} }
public string Host { get; private set; } public string Host { get; }
public int Port { get; private set; } public int Port { get; }
public string Type { get; private set; } public string Type { get; }
public string Token { get; }
} }
} }

View File

@ -6,14 +6,16 @@ using Ocelot.Responses;
namespace Ocelot.Configuration.Setter namespace Ocelot.Configuration.Setter
{ {
public class FileConfigurationSetter : IFileConfigurationSetter public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
{ {
private readonly IOcelotConfigurationRepository _configRepo; private readonly IInternalConfigurationRepository _configRepo;
private readonly IOcelotConfigurationCreator _configCreator; private readonly IInternalConfigurationCreator _configCreator;
private readonly IFileConfigurationRepository _repo; private readonly IFileConfigurationRepository _repo;
public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, public FileAndInternalConfigurationSetter(
IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) IInternalConfigurationRepository configRepo,
IInternalConfigurationCreator configCreator,
IFileConfigurationRepository repo)
{ {
_configRepo = configRepo; _configRepo = configRepo;
_configCreator = configCreator; _configCreator = configCreator;
@ -33,7 +35,7 @@ namespace Ocelot.Configuration.Setter
if(!config.IsError) if(!config.IsError)
{ {
await _configRepo.AddOrReplace(config.Data); _configRepo.AddOrReplace(config.Data);
} }
return new ErrorResponse(config.Errors); return new ErrorResponse(config.Errors);

View File

@ -17,8 +17,8 @@ namespace Ocelot.Configuration.Validator
Errors = errors; Errors = errors;
} }
public bool IsError { get; private set; } public bool IsError { get; }
public List<Error> Errors { get; private set; } public List<Error> Errors { get; }
} }
} }

View File

@ -1,21 +1,73 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Configuration.File;
using Newtonsoft.Json;
public static class ConfigurationBuilderExtensions public static class ConfigurationBuilderExtensions
{ {
[Obsolete("Please set BaseUrl in configuration.json GlobalConfiguration.BaseUrl")] [Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")]
public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl) public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl)
{ {
var memorySource = new MemoryConfigurationSource(); var memorySource = new MemoryConfigurationSource
memorySource.InitialData = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>("BaseUrl", baseUrl) InitialData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("BaseUrl", baseUrl)
}
}; };
builder.Add(memorySource); builder.Add(memorySource);
return builder;
}
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder)
{
const string pattern = "(?i)ocelot.([a-zA-Z0-9]*).json";
var reg = new Regex(pattern);
var files = Directory.GetFiles(".")
.Where(path => reg.IsMatch(path))
.ToList();
var fileConfiguration = new FileConfiguration();
foreach (var file in files)
{
// windows and unix sigh...
if(files.Count > 1 && (file == "./ocelot.json" || file == ".\\ocelot.json"))
{
continue;
}
var lines = File.ReadAllText(file);
var config = JsonConvert.DeserializeObject<FileConfiguration>(lines);
// windows and unix sigh...
if (file == "./ocelot.global.json" || file == ".\\ocelot.global.json")
{
fileConfiguration.GlobalConfiguration = config.GlobalConfiguration;
}
fileConfiguration.Aggregates.AddRange(config.Aggregates);
fileConfiguration.ReRoutes.AddRange(config.ReRoutes);
}
var json = JsonConvert.SerializeObject(fileConfiguration);
File.WriteAllText("ocelot.json", json);
builder.AddJsonFile("ocelot.json");
return builder; return builder;
} }
} }

View File

@ -3,6 +3,7 @@ using CacheManager.Core;
using System; using System;
using System.Net.Http; using System.Net.Http;
using IdentityServer4.AccessTokenValidation; using IdentityServer4.AccessTokenValidation;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
@ -23,5 +24,10 @@ namespace Ocelot.DependencyInjection
IOcelotBuilder AddTransientDelegatingHandler<T>(bool global = false) IOcelotBuilder AddTransientDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler; where T : DelegatingHandler;
IOcelotBuilder AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator;
IOcelotBuilder AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator;
} }
} }

View File

@ -1,7 +1,3 @@
using Butterfly.Client.Tracing;
using Microsoft.Extensions.Options;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
using CacheManager.Core; using CacheManager.Core;
@ -12,17 +8,14 @@ namespace Ocelot.DependencyInjection
using Ocelot.Authorisation; using Ocelot.Authorisation;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Claims; using Ocelot.Claims;
using Ocelot.Configuration.Authentication;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.DownstreamUrlCreator;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
@ -44,14 +37,16 @@ namespace Ocelot.DependencyInjection
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using IdentityServer4.AccessTokenValidation; using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using Butterfly.Client.AspNetCore; using Butterfly.Client.AspNetCore;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Consul;
using Butterfly.Client.Tracing;
using Ocelot.Middleware.Multiplexer;
using Pivotal.Discovery.Client;
using ServiceDiscovery.Providers;
public class OcelotBuilder : IOcelotBuilder public class OcelotBuilder : IOcelotBuilder
{ {
@ -76,8 +71,8 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>(); _services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>(); _services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>(); _services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>(); _services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>(); _services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>(); _services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>(); _services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>(); _services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
@ -89,9 +84,8 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>(); _services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
_services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>(); _services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
_services.TryAddSingleton<IRegionCreator, RegionCreator>(); _services.TryAddSingleton<IRegionCreator, RegionCreator>();
_services.TryAddSingleton<IFileConfigurationRepository, FileConfigurationRepository>(); _services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
_services.TryAddSingleton<IFileConfigurationSetter, FileConfigurationSetter>(); _services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
_services.TryAddSingleton<IFileConfigurationProvider, FileConfigurationProvider>();
_services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>(); _services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
_services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>(); _services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
_services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); _services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
@ -99,7 +93,6 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); _services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
_services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); _services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
_services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>(); _services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
_services.TryAddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
_services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>(); _services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
_services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>(); _services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
_services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>(); _services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
@ -121,6 +114,17 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>(); _services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
_services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>(); _services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
if (UsingEurekaServiceDiscoveryProvider(configurationRoot))
{
_services.AddDiscoveryClient(configurationRoot);
}
else
{
_services.TryAddSingleton<IDiscoveryClient, FakeEurekaDiscoveryClient>();
}
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
// could maybe use a scoped data repository // could maybe use a scoped data repository
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); _services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -148,6 +152,12 @@ namespace Ocelot.DependencyInjection
// We add this here so that we can always inject something into the factory for IoC.. // We add this here so that we can always inject something into the factory for IoC..
_services.AddSingleton<IServiceTracer, FakeServiceTracer>(); _services.AddSingleton<IServiceTracer, FakeServiceTracer>();
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
_services.TryAddSingleton<IPlaceholders, Placeholders>();
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
} }
public IOcelotAdministrationBuilder AddAdministration(string path, string secret) public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
@ -182,6 +192,20 @@ namespace Ocelot.DependencyInjection
return new OcelotAdministrationBuilder(_services, _configurationRoot); return new OcelotAdministrationBuilder(_services, _configurationRoot);
} }
public IOcelotBuilder AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
_services.AddSingleton<IDefinedAggregator, T>();
return this;
}
public IOcelotBuilder AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
_services.AddTransient<IDefinedAggregator, T>();
return this;
}
public IOcelotBuilder AddSingletonDelegatingHandler<THandler>(bool global = false) public IOcelotBuilder AddSingletonDelegatingHandler<THandler>(bool global = false)
where THandler : DelegatingHandler where THandler : DelegatingHandler
{ {
@ -230,15 +254,6 @@ namespace Ocelot.DependencyInjection
public IOcelotBuilder AddStoreOcelotConfigurationInConsul() public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
{ {
var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0);
var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty);
var config = new ServiceProviderConfigurationBuilder()
.WithServiceDiscoveryProviderPort(serviceDiscoveryPort)
.WithServiceDiscoveryProviderHost(serviceDiscoveryHost)
.Build();
_services.AddSingleton<ServiceProviderConfiguration>(config);
_services.AddSingleton<ConsulFileConfigurationPoller>(); _services.AddSingleton<ConsulFileConfigurationPoller>();
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>(); _services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
return this; return this;
@ -254,12 +269,12 @@ namespace Ocelot.DependencyInjection
_services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache); _services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager); _services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings); var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache); var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
_services.RemoveAll(typeof(ICacheManager<IOcelotConfiguration>)); _services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
_services.RemoveAll(typeof(IOcelotCache<IOcelotConfiguration>)); _services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
_services.AddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache); _services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager); _services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings); var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache); var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
@ -280,7 +295,6 @@ namespace Ocelot.DependencyInjection
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
{ {
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration); _services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
_services.TryAddSingleton<IHashMatcher, HashMatcher>();
var identityServerBuilder = _services var identityServerBuilder = _services
.AddIdentityServer(o => { .AddIdentityServer(o => {
o.IssuerUri = "Ocelot"; o.IssuerUri = "Ocelot";
@ -345,5 +359,13 @@ namespace Ocelot.DependencyInjection
} }
}; };
} }
private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot)
{
var type = configurationRoot.GetValue<string>("GlobalConfiguration:ServiceDiscoveryProvider:Type",
string.Empty);
return type.ToLower() == "eureka";
}
} }
} }

View File

@ -18,7 +18,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
_placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
} }
public Response<DownstreamRoute> FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration, string upstreamHost) public Response<DownstreamRoute> FindDownstreamRoute(string path, string httpMethod, IInternalConfiguration configuration, string upstreamHost)
{ {
var downstreamRoutes = new List<DownstreamRoute>(); var downstreamRoutes = new List<DownstreamRoute>();
@ -44,10 +44,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
return notNullOption != null ? new OkResponse<DownstreamRoute>(notNullOption) : new OkResponse<DownstreamRoute>(nullOption); return notNullOption != null ? new OkResponse<DownstreamRoute>(notNullOption) : new OkResponse<DownstreamRoute>(nullOption);
} }
return new ErrorResponse<DownstreamRoute>(new List<Error> return new ErrorResponse<DownstreamRoute>(new UnableToFindDownstreamRouteError(path, httpMethod));
{
new UnableToFindDownstreamRouteError()
});
} }
private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost) private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost)

View File

@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder
{ {
public interface IDownstreamRouteFinder public interface IDownstreamRouteFinder
{ {
Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration, string upstreamHost); Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost);
} }
} }

View File

@ -4,7 +4,8 @@ namespace Ocelot.DownstreamRouteFinder.Finder
{ {
public class UnableToFindDownstreamRouteError : Error public class UnableToFindDownstreamRouteError : Error
{ {
public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError", OcelotErrorCode.UnableToFindDownstreamRouteError) public UnableToFindDownstreamRouteError(string path, string httpVerb)
: base($"Unable to find downstream route for path: {path}, verb: {httpVerb}", OcelotErrorCode.UnableToFindDownstreamRouteError)
{ {
} }
} }

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration; using System.Linq;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository;
using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging; using Ocelot.Logging;
@ -13,21 +13,20 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IDownstreamRouteFinder _downstreamRouteFinder; private readonly IDownstreamRouteFinder _downstreamRouteFinder;
private readonly IOcelotLogger _logger; private readonly IInternalConfigurationRepository _repo;
private readonly IOcelotConfigurationProvider _configProvider;
private readonly IMultiplexer _multiplexer; private readonly IMultiplexer _multiplexer;
public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IDownstreamRouteFinder downstreamRouteFinder, IDownstreamRouteFinder downstreamRouteFinder,
IOcelotConfigurationProvider configProvider, IInternalConfigurationRepository repo,
IMultiplexer multiplexer) IMultiplexer multiplexer)
:base(loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>())
{ {
_configProvider = configProvider; _repo = repo;
_multiplexer = multiplexer; _multiplexer = multiplexer;
_next = next; _next = next;
_downstreamRouteFinder = downstreamRouteFinder; _downstreamRouteFinder = downstreamRouteFinder;
_logger = loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>();
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
@ -36,31 +35,31 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
var upstreamHost = context.HttpContext.Request.Headers["Host"]; var upstreamHost = context.HttpContext.Request.Headers["Host"];
var configuration = await _configProvider.Get(); var configuration = _repo.Get();
if (configuration.IsError) if (configuration.IsError)
{ {
_logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
SetPipelineError(context, configuration.Errors); SetPipelineError(context, configuration.Errors);
return; return;
} }
context.ServiceProviderConfiguration = configuration.Data.ServiceProviderConfiguration; context.ServiceProviderConfiguration = configuration.Data.ServiceProviderConfiguration;
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); Logger.LogDebug($"Upstream url path is {upstreamUrlPath}");
var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.HttpContext.Request.Method, configuration.Data, upstreamHost); var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.HttpContext.Request.Method, configuration.Data, upstreamHost);
if (downstreamRoute.IsError) if (downstreamRoute.IsError)
{ {
_logger.LogError($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}");
SetPipelineError(context, downstreamRoute.Errors); SetPipelineError(context, downstreamRoute.Errors);
return; return;
} }
// todo - put this back in var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
//// _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamReRoute.DownstreamPathTemplate); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamUrlCreator
{
public class DownstreamHostNullOrEmptyError : Error
{
public DownstreamHostNullOrEmptyError()
: base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError)
{
}
}
}

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamUrlCreator
{
public class DownstreamPathNullOrEmptyError : Error
{
public DownstreamPathNullOrEmptyError()
: base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError)
{
}
}
}

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamUrlCreator
{
public class DownstreamSchemeNullOrEmptyError : Error
{
public DownstreamSchemeNullOrEmptyError()
: base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError)
{
}
}
}

View File

@ -1,12 +0,0 @@
/*
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.DownstreamUrlCreator
{
public interface IUrlBuilder
{
Response<DownstreamUrl> Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort downstreamHostAndPort);
}
}
*/

View File

@ -16,15 +16,14 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IDownstreamPathPlaceholderReplacer _replacer; private readonly IDownstreamPathPlaceholderReplacer _replacer;
private readonly IOcelotLogger _logger;
public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IDownstreamPathPlaceholderReplacer replacer) IDownstreamPathPlaceholderReplacer replacer)
:base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
{ {
_next = next; _next = next;
_replacer = replacer; _replacer = replacer;
_logger = loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>();
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
@ -34,55 +33,42 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
if (dsPath.IsError) if (dsPath.IsError)
{ {
_logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
SetPipelineError(context, dsPath.Errors); SetPipelineError(context, dsPath.Errors);
return; return;
} }
UriBuilder uriBuilder; context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
if (ServiceFabricRequest(context)) if (ServiceFabricRequest(context))
{ {
uriBuilder = CreateServiceFabricUri(context, dsPath); var pathAndQuery = CreateServiceFabricUri(context, dsPath);
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
context.DownstreamRequest.Query = pathAndQuery.query;
} }
else else
{ {
uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri) context.DownstreamRequest.AbsolutePath = dsPath.Data.Value;
{
Path = dsPath.Data.Value,
Scheme = context.DownstreamReRoute.DownstreamScheme
};
} }
context.DownstreamRequest.RequestUri = uriBuilder.Uri; Logger.LogDebug($"Downstream url is {context.DownstreamRequest}");
_logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", context.DownstreamRequest.RequestUri);
await _next.Invoke(context); await _next.Invoke(context);
} }
private UriBuilder CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath) private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
{ {
var query = context.DownstreamRequest.RequestUri.Query; var query = context.DownstreamRequest.Query;
var scheme = context.DownstreamReRoute.DownstreamScheme;
var host = context.DownstreamRequest.RequestUri.Host;
var port = context.DownstreamRequest.RequestUri.Port;
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}"; var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
Uri uri;
if (RequestForStatefullService(query)) if (RequestForStatefullService(query))
{ {
uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}"); return (serviceFabricPath, query);
}
else
{
var split = string.IsNullOrEmpty(query) ? "?" : "&";
uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}{split}cmd=instance");
} }
return new UriBuilder(uri); var split = string.IsNullOrEmpty(query) ? "?" : "&";
return (serviceFabricPath, $"{query}{split}cmd=instance");
} }
private static bool ServiceFabricRequest(DownstreamContext context) private static bool ServiceFabricRequest(DownstreamContext context)

View File

@ -1,47 +0,0 @@
/*
using System;
using System.Collections.Generic;
using Ocelot.Errors;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.DownstreamUrlCreator
{
public class UrlBuilder : IUrlBuilder
{
public Response<DownstreamUrl> Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort downstreamHostAndPort)
{
if (string.IsNullOrEmpty(downstreamPath))
{
return new ErrorResponse<DownstreamUrl>(new List<Error> {new DownstreamPathNullOrEmptyError()});
}
if (string.IsNullOrEmpty(downstreamScheme))
{
return new ErrorResponse<DownstreamUrl>(new List<Error> { new DownstreamSchemeNullOrEmptyError() });
}
if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost))
{
return new ErrorResponse<DownstreamUrl>(new List<Error> { new DownstreamHostNullOrEmptyError() });
}
var builder = new UriBuilder
{
Host = downstreamHostAndPort.DownstreamHost,
Path = downstreamPath,
Scheme = downstreamScheme,
};
if (downstreamHostAndPort.DownstreamPort > 0)
{
builder.Port = downstreamHostAndPort.DownstreamPort;
}
var url = builder.Uri.ToString();
return new OkResponse<DownstreamUrl>(new DownstreamUrl(url));
}
}
}
*/

View File

@ -1,10 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Ocelot.Configuration.Repository;
using Microsoft.Extensions.Primitives;
using Ocelot.Configuration.Provider;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
@ -18,68 +15,64 @@ namespace Ocelot.Errors.Middleware
public class ExceptionHandlerMiddleware : OcelotMiddleware public class ExceptionHandlerMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IOcelotLogger _logger; private readonly IInternalConfigurationRepository _configRepo;
private readonly IOcelotConfigurationProvider _provider;
private readonly IRequestScopedDataRepository _repo; private readonly IRequestScopedDataRepository _repo;
public ExceptionHandlerMiddleware(OcelotRequestDelegate next, public ExceptionHandlerMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IOcelotConfigurationProvider provider, IInternalConfigurationRepository configRepo,
IRequestScopedDataRepository repo) IRequestScopedDataRepository repo)
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
{ {
_provider = provider; _configRepo = configRepo;
_repo = repo; _repo = repo;
_next = next; _next = next;
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
try try
{ {
await TrySetGlobalRequestId(context); TrySetGlobalRequestId(context);
_logger.LogDebug("ocelot pipeline started"); Logger.LogDebug("ocelot pipeline started");
await _next.Invoke(context); await _next.Invoke(context);
} }
catch (Exception e) catch (Exception e)
{ {
_logger.LogDebug("error calling middleware"); Logger.LogDebug("error calling middleware");
var message = CreateMessage(context, e); var message = CreateMessage(context, e);
_logger.LogError(message, e); Logger.LogError(message, e);
SetInternalServerErrorOnResponse(context); SetInternalServerErrorOnResponse(context);
} }
_logger.LogDebug("ocelot pipeline finished"); Logger.LogDebug("ocelot pipeline finished");
} }
private async Task TrySetGlobalRequestId(DownstreamContext context) private void TrySetGlobalRequestId(DownstreamContext context)
{ {
//try and get the global request id and set it for logs... //try and get the global request id and set it for logs...
//should this basically be immutable per request...i guess it should! //should this basically be immutable per request...i guess it should!
//first thing is get config //first thing is get config
var configuration = await _provider.Get(); var configuration = _configRepo.Get();
//if error throw to catch below.. if(configuration.IsError)
if(configuration.IsError) {
{ throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
}
//else set the request id?
var key = configuration.Data.RequestId;
StringValues upstreamRequestIds;
if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out upstreamRequestIds))
{
//todo fix looking in both places
context.HttpContext.TraceIdentifier = upstreamRequestIds.First();
_repo.Add<string>("RequestId", context.HttpContext.TraceIdentifier);
} }
var key = configuration.Data.RequestId;
if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
{
context.HttpContext.TraceIdentifier = upstreamRequestIds.First();
}
_repo.Add("RequestId", context.HttpContext.TraceIdentifier);
} }
private void SetInternalServerErrorOnResponse(DownstreamContext context) private void SetInternalServerErrorOnResponse(DownstreamContext context)

View File

@ -34,6 +34,8 @@
RateLimitOptionsError, RateLimitOptionsError,
PathTemplateDoesntStartWithForwardSlash, PathTemplateDoesntStartWithForwardSlash,
FileValidationFailedError, FileValidationFailedError,
UnableToFindDelegatingHandlerProviderError UnableToFindDelegatingHandlerProviderError,
CouldNotFindPlaceholderError,
CouldNotFindAggregatorError
} }
} }

View File

@ -4,6 +4,9 @@ using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.Creator;
using Ocelot.Request.Middleware;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
@ -16,7 +19,7 @@ namespace Ocelot.Headers
_claimsParser = claimsParser; _claimsParser = claimsParser;
} }
public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, HttpRequestMessage downstreamRequest) public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest)
{ {
foreach (var config in claimsToThings) foreach (var config in claimsToThings)
{ {
@ -39,5 +42,19 @@ namespace Ocelot.Headers
return new OkResponse(); return new OkResponse();
} }
public void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context)
{
var requestHeader = context.Request.Headers;
foreach (var header in headers)
{
if (requestHeader.ContainsKey(header.Key))
{
requestHeader.Remove(header.Key);
}
requestHeader.Add(header.Key, header.Value);
}
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More