mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-19 20:12:51 +08:00
Remove Ocelot specific Middleware to make Ocelot more compatible with kestrel middleware and get ready for YARP
This commit is contained in:
parent
99a15d8668
commit
fe3e8bd23a
@ -4,6 +4,7 @@ root = true
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
end_of_line = lf
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 2010
|
||||||
|
*.txt -crlf
|
||||||
|
|
||||||
|
# 2020
|
||||||
|
*.txt text eol=lf
|
@ -8,6 +8,7 @@ EndProject
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.dockerignore = .dockerignore
|
.dockerignore = .dockerignore
|
||||||
|
.editorconfig = .editorconfig
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
build.cake = build.cake
|
build.cake = build.cake
|
||||||
build.ps1 = build.ps1
|
build.ps1 = build.ps1
|
||||||
|
26
README.md
26
README.md
@ -8,28 +8,16 @@
|
|||||||
|
|
||||||
# Ocelot
|
# Ocelot
|
||||||
|
|
||||||
Ocelot is a .NET API Gateway. This project is aimed at people using .NET running
|
Ocelot is a .NET API Gateway. This project is aimed at people using .NET running a micro services / service oriented architecture
|
||||||
a micro services / service oriented architecture
|
|
||||||
that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports.
|
that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports.
|
||||||
|
|
||||||
In particular I want easy integration with
|
In particular I want easy integration with IdentityServer reference and bearer tokens.
|
||||||
IdentityServer reference and bearer tokens.
|
|
||||||
|
|
||||||
We have been unable to find this in my current workplace
|
We have been unable to find this in my current workplacewithout having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already existsto do this.
|
||||||
without having to write our own Javascript middlewares
|
|
||||||
to handle the IdentityServer reference tokens. We would
|
|
||||||
rather use the IdentityServer code that already exists
|
|
||||||
to do this.
|
|
||||||
|
|
||||||
Ocelot is a bunch of middlewares in a specific order.
|
Ocelot is a bunch of middlewares in a specific order.
|
||||||
|
|
||||||
Ocelot manipulates the HttpRequest object into a state specified by its configuration until
|
Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features!
|
||||||
it reaches a request builder middleware where it creates a HttpRequestMessage object which is
|
|
||||||
used to make a request to a downstream service. The middleware that makes the request is
|
|
||||||
the last thing in the Ocelot pipeline. It does not call the next middleware.
|
|
||||||
The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline.
|
|
||||||
There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that
|
|
||||||
is returned to the client. That is basically it with a bunch of other features!
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -81,15 +69,13 @@ We love to receive contributions from the community so please keep them coming :
|
|||||||
|
|
||||||
Pull requests, issues and commentary welcome!
|
Pull requests, issues and commentary welcome!
|
||||||
|
|
||||||
Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes
|
Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes before doing any work incase this is something we are already doing or it might not make sense. We can also give advice on the easiest way to do things :)
|
||||||
before doing any work incase this is something we are already doing or it might not make sense. We can also give
|
|
||||||
advice on the easiest way to do things :)
|
|
||||||
|
|
||||||
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :)
|
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :)
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
If you think this project is worth supporting financially please make a contribution using the button below!
|
If you think this project is worth supporting financially please make a contribution using the button below! We use the money to run the https://threemammals.com website.
|
||||||
|
|
||||||
[](https://www.paypal.me/ThreeMammals/)
|
[](https://www.paypal.me/ThreeMammals/)
|
||||||
|
|
||||||
|
@ -1,140 +1,128 @@
|
|||||||
Administration
|
Administration
|
||||||
==============
|
==============
|
||||||
|
|
||||||
Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's
|
Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own IdentityServer.
|
||||||
internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own
|
|
||||||
IdentityServer.
|
The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package..
|
||||||
|
|
||||||
The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package..
|
``Install-Package Ocelot.Administration``
|
||||||
|
|
||||||
``Install-Package Ocelot.Administration``
|
This will bring down everything needed by the admin API.
|
||||||
|
|
||||||
This will bring down everything needed by the admin API.
|
Providing your own IdentityServer
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Providing your own IdentityServer
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method.
|
All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
public virtual void ConfigureServices(IServiceCollection services)
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
Action<IdentityServerAuthenticationOptions> options = o => {
|
Action<IdentityServerAuthenticationOptions> options = o => {
|
||||||
// o.Authority = ;
|
// o.Authority = ;
|
||||||
// o.ApiName = ;
|
// o.ApiName = ;
|
||||||
// etc....
|
// etc....
|
||||||
};
|
};
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddAdministration("/administration", options);
|
.AddAdministration("/administration", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API.
|
You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API.
|
||||||
|
|
||||||
This feature was implemented for `issue 228 <https://github.com/ThreeMammals/Ocelot/issues/228>`_. It is useful because the IdentityServer authentication
|
This feature was implemented for `issue 228 <https://github.com/ThreeMammals/Ocelot/issues/228>`_. It is useful because the IdentityServer authentication middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL.
|
||||||
middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL.
|
|
||||||
|
Internal IdentityServer
|
||||||
Internal IdentityServer
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing `Identity Server <https://github.com/IdentityServer/IdentityServer4>`_ project that I have been using for a few years now. Check them out.
|
||||||
The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing
|
|
||||||
`Identity Server <https://github.com/IdentityServer/IdentityServer4>`_ project that I have been using for a few years now. Check them out.
|
In order to enable the administration section you need to do a few things. First of all add this to yourinitial Startup.cs.
|
||||||
|
|
||||||
In order to enable the administration section you need to do a few things. First of all add this to your
|
The path can be anything you want and it is obviously reccomended don't usea url you would like to route through with Ocelot as this will not work. The administration uses theMapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not to the Ocelot middleware.
|
||||||
initial Startup.cs.
|
|
||||||
|
The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be!
|
||||||
The path can be anything you want and it is obviously reccomended don't use
|
|
||||||
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
.. code-block:: csharp
|
||||||
MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not
|
|
||||||
to the Ocelot middleware.
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be!
|
services
|
||||||
|
.AddOcelot()
|
||||||
.. code-block:: csharp
|
.AddAdministration("/administration", "secret");
|
||||||
|
}
|
||||||
public virtual void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. This means that you need to add the base url of Ocelot to global configuration if it is not default (http://localhost:5000). Please note if you are using something like docker to host Ocelot it might not be able to call back to localhost etc and you need to know what you are doing with docker networking in this scenario. Anyway this can be done as follows..
|
||||||
services
|
|
||||||
.AddOcelot()
|
If you want to run on a different host and port locally..
|
||||||
.AddAdministration("/administration", "secret");
|
|
||||||
}
|
.. code-block:: json
|
||||||
|
|
||||||
In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. This means that you need to add the base url of Ocelot
|
"GlobalConfiguration": {
|
||||||
to global configuration if it is not default (http://localhost:5000). Please note if you are using something like docker to host Ocelot it might not be able to
|
"BaseUrl": "http://localhost:55580"
|
||||||
call back to localhost etc and you need to know what you are doing with docker networking in this scenario. Anyway this can be done as follows..
|
}
|
||||||
|
|
||||||
If you want to run on a different host and port locally..
|
or if Ocelot is exposed via dns
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
"BaseUrl": "http://localhost:55580"
|
"BaseUrl": "http://mydns.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
or if Ocelot is exposed via dns
|
Now if you went with the configuration options above and want to access the API you can use the postman scriptscalled ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these will need to be changed if you are running Ocelot on a different url to http://localhost:5000.
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST a configuration.
|
||||||
"GlobalConfiguration": {
|
|
||||||
"BaseUrl": "http://mydns.com"
|
If you are running multiple Ocelot instances in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API.
|
||||||
}
|
|
||||||
|
In order to do this you need to add two more environmental variables for each Ocelot in the cluster.
|
||||||
Now if you went with the configuration options above and want to access the API you can use the postman scripts
|
|
||||||
called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these
|
``OCELOT_CERTIFICATE``
|
||||||
will need to be changed if you are running Ocelot on a different url to http://localhost:5000.
|
The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it.
|
||||||
|
``OCELOT_CERTIFICATE_PASSWORD``
|
||||||
|
The password for the certificate.
|
||||||
The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST
|
|
||||||
a configuration.
|
Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelot instances in thecluster have the same certificate then you are good!
|
||||||
|
|
||||||
If you are running multiple Ocelot instances in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API.
|
|
||||||
|
Administration API
|
||||||
In order to do this you need to add two more environmental variables for each Ocelot in the cluster.
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
``OCELOT_CERTIFICATE``
|
**POST {adminPath}/connect/token**
|
||||||
The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it.
|
|
||||||
``OCELOT_CERTIFICATE_PASSWORD``
|
This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot.
|
||||||
The password for the certificate.
|
|
||||||
|
The body of the request is form-data as follows
|
||||||
Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelot instances in the cluster have the same certificate then you are good!
|
|
||||||
|
``client_id`` set as admin
|
||||||
|
|
||||||
Administration API
|
``client_secret`` set as whatever you used when setting up the administration services.
|
||||||
^^^^^^^^^^^^^^^^^^
|
|
||||||
|
``scope`` set as admin
|
||||||
**POST {adminPath}/connect/token**
|
|
||||||
|
``grant_type`` set as client_credentials
|
||||||
This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot.
|
|
||||||
|
**GET {adminPath}/configuration**
|
||||||
The body of the request is form-data as follows
|
|
||||||
|
|
||||||
``client_id`` set as admin
|
This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place.
|
||||||
|
|
||||||
``client_secret`` set as whatever you used when setting up the administration services.
|
**POST {adminPath}/configuration**
|
||||||
|
|
||||||
``scope`` set as admin
|
This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples.
|
||||||
|
|
||||||
``grant_type`` set as client_credentials
|
The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up
|
||||||
|
Ocelot on a file system.
|
||||||
**GET {adminPath}/configuration**
|
|
||||||
|
Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save.
|
||||||
|
|
||||||
This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place.
|
**DELETE {adminPath}/outputcache/{region}**
|
||||||
|
|
||||||
**POST {adminPath}/configuration**
|
This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache.
|
||||||
|
|
||||||
This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples.
|
The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration.
|
||||||
|
|
||||||
The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up
|
|
||||||
Ocelot on a file system.
|
|
||||||
|
|
||||||
Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk
|
|
||||||
where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save.
|
|
||||||
|
|
||||||
**DELETE {adminPath}/outputcache/{region}**
|
|
||||||
|
|
||||||
This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache.
|
|
||||||
|
|
||||||
The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration.
|
|
||||||
|
@ -1,182 +1,179 @@
|
|||||||
Authentication
|
Authentication
|
||||||
==============
|
==============
|
||||||
|
|
||||||
In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g.
|
In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
var authenticationProviderKey = "TestKey";
|
var authenticationProviderKey = "TestKey";
|
||||||
|
|
||||||
services.AddAuthentication()
|
services.AddAuthentication()
|
||||||
.AddJwtBearer(authenticationProviderKey, x =>
|
.AddJwtBearer(authenticationProviderKey, x =>
|
||||||
{
|
{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
In this example TestKey is the scheme that this provider has been registered with.
|
In this example TestKey is the scheme that this provider has been registered with. We then map this to a ReRoute in the configuration e.g.
|
||||||
We then map this to a ReRoute in the configuration e.g.
|
|
||||||
|
.. code-block:: json
|
||||||
.. code-block:: json
|
|
||||||
|
"ReRoutes": [{
|
||||||
"ReRoutes": [{
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamHostAndPorts": [
|
{
|
||||||
{
|
"Host": "localhost",
|
||||||
"Host": "localhost",
|
"Port": 51876,
|
||||||
"Port": 51876,
|
}
|
||||||
}
|
],
|
||||||
],
|
"DownstreamPathTemplate": "/",
|
||||||
"DownstreamPathTemplate": "/",
|
"UpstreamPathTemplate": "/",
|
||||||
"UpstreamPathTemplate": "/",
|
"UpstreamHttpMethod": ["Post"],
|
||||||
"UpstreamHttpMethod": ["Post"],
|
"ReRouteIsCaseSensitive": false,
|
||||||
"ReRouteIsCaseSensitive": false,
|
"DownstreamScheme": "http",
|
||||||
"DownstreamScheme": "http",
|
"AuthenticationOptions": {
|
||||||
"AuthenticationOptions": {
|
"AuthenticationProviderKey": "TestKey",
|
||||||
"AuthenticationProviderKey": "TestKey",
|
"AllowedScopes": []
|
||||||
"AllowedScopes": []
|
}
|
||||||
}
|
}]
|
||||||
}]
|
|
||||||
|
When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot will not start up, if there is then the ReRoute will use that provider when it executes.
|
||||||
When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey
|
|
||||||
and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot
|
If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401.
|
||||||
will not start up, if there is then the ReRoute will use that provider when it executes.
|
|
||||||
|
JWT Tokens
|
||||||
If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401.
|
^^^^^^^^^^
|
||||||
|
|
||||||
JWT Tokens
|
If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g.
|
||||||
^^^^^^^^^^
|
|
||||||
|
.. code-block:: csharp
|
||||||
If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g.
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
.. code-block:: csharp
|
{
|
||||||
|
var authenticationProviderKey = "TestKey";
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
services.AddAuthentication()
|
||||||
var authenticationProviderKey = "TestKey";
|
.AddJwtBearer(authenticationProviderKey, x =>
|
||||||
|
{
|
||||||
services.AddAuthentication()
|
x.Authority = "test";
|
||||||
.AddJwtBearer(authenticationProviderKey, x =>
|
x.Audience = "test";
|
||||||
{
|
});
|
||||||
x.Authority = "test";
|
|
||||||
x.Audience = "test";
|
services.AddOcelot();
|
||||||
});
|
}
|
||||||
|
|
||||||
services.AddOcelot();
|
Then map the authentication provider key to a ReRoute in your configuration e.g.
|
||||||
}
|
|
||||||
|
.. code-block:: json
|
||||||
Then map the authentication provider key to a ReRoute in your configuration e.g.
|
|
||||||
|
"ReRoutes": [{
|
||||||
.. code-block:: json
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
"ReRoutes": [{
|
"Host": "localhost",
|
||||||
"DownstreamHostAndPorts": [
|
"Port": 51876,
|
||||||
{
|
}
|
||||||
"Host": "localhost",
|
],
|
||||||
"Port": 51876,
|
"DownstreamPathTemplate": "/",
|
||||||
}
|
"UpstreamPathTemplate": "/",
|
||||||
],
|
"UpstreamHttpMethod": ["Post"],
|
||||||
"DownstreamPathTemplate": "/",
|
"ReRouteIsCaseSensitive": false,
|
||||||
"UpstreamPathTemplate": "/",
|
"DownstreamScheme": "http",
|
||||||
"UpstreamHttpMethod": ["Post"],
|
"AuthenticationOptions": {
|
||||||
"ReRouteIsCaseSensitive": false,
|
"AuthenticationProviderKey": "TestKey",
|
||||||
"DownstreamScheme": "http",
|
"AllowedScopes": []
|
||||||
"AuthenticationOptions": {
|
}
|
||||||
"AuthenticationProviderKey": "TestKey",
|
}]
|
||||||
"AllowedScopes": []
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
Identity Server Bearer Tokens
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Identity Server Bearer Tokens
|
In order to use IdentityServer bearer tokens, register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
.. code-block:: csharp
|
||||||
In order to use IdentityServer bearer tokens, register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation.
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
.. code-block:: csharp
|
{
|
||||||
|
var authenticationProviderKey = "TestKey";
|
||||||
public void ConfigureServices(IServiceCollection services)
|
Action<IdentityServerAuthenticationOptions> options = o =>
|
||||||
{
|
{
|
||||||
var authenticationProviderKey = "TestKey";
|
o.Authority = "https://whereyouridentityserverlives.com";
|
||||||
Action<IdentityServerAuthenticationOptions> options = o =>
|
o.ApiName = "api";
|
||||||
{
|
o.SupportedTokens = SupportedTokens.Both;
|
||||||
o.Authority = "https://whereyouridentityserverlives.com";
|
o.ApiSecret = "secret";
|
||||||
o.ApiName = "api";
|
};
|
||||||
o.SupportedTokens = SupportedTokens.Both;
|
|
||||||
o.ApiSecret = "secret";
|
services.AddAuthentication()
|
||||||
};
|
.AddIdentityServerAuthentication(authenticationProviderKey, options);
|
||||||
|
|
||||||
services.AddAuthentication()
|
services.AddOcelot();
|
||||||
.AddIdentityServerAuthentication(authenticationProviderKey, options);
|
}
|
||||||
|
|
||||||
services.AddOcelot();
|
Then map the authentication provider key to a ReRoute in your configuration e.g.
|
||||||
}
|
|
||||||
|
.. code-block:: json
|
||||||
Then map the authentication provider key to a ReRoute in your configuration e.g.
|
|
||||||
|
"ReRoutes": [{
|
||||||
.. code-block:: json
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
"ReRoutes": [{
|
"Host": "localhost",
|
||||||
"DownstreamHostAndPorts": [
|
"Port": 51876,
|
||||||
{
|
}
|
||||||
"Host": "localhost",
|
],
|
||||||
"Port": 51876,
|
"DownstreamPathTemplate": "/",
|
||||||
}
|
"UpstreamPathTemplate": "/",
|
||||||
],
|
"UpstreamHttpMethod": ["Post"],
|
||||||
"DownstreamPathTemplate": "/",
|
"ReRouteIsCaseSensitive": false,
|
||||||
"UpstreamPathTemplate": "/",
|
"DownstreamScheme": "http",
|
||||||
"UpstreamHttpMethod": ["Post"],
|
"AuthenticationOptions": {
|
||||||
"ReRouteIsCaseSensitive": false,
|
"AuthenticationProviderKey": "TestKey",
|
||||||
"DownstreamScheme": "http",
|
"AllowedScopes": []
|
||||||
"AuthenticationOptions": {
|
}
|
||||||
"AuthenticationProviderKey": "TestKey",
|
}]
|
||||||
"AllowedScopes": []
|
|
||||||
}
|
Okta
|
||||||
}]
|
^^^^
|
||||||
|
Add the following to your startup Configure method:
|
||||||
Okta
|
|
||||||
^^^^
|
.. code-block:: csharp
|
||||||
Add the following to your startup Configure method:
|
|
||||||
|
app
|
||||||
.. code-block:: csharp
|
.UseAuthentication()
|
||||||
|
.UseOcelot()
|
||||||
app
|
.Wait();
|
||||||
.UseAuthentication()
|
|
||||||
.UseOcelot()
|
|
||||||
.Wait();
|
Add the following, at minimum, to your startup ConfigureServices method:
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
Add the following, at minimum, to your startup ConfigureServices method:
|
|
||||||
|
services
|
||||||
.. code-block:: csharp
|
.AddAuthentication()
|
||||||
|
.AddJwtBearer(oktaProviderKey, options =>
|
||||||
services
|
{
|
||||||
.AddAuthentication()
|
options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience
|
||||||
.AddJwtBearer(oktaProviderKey, options =>
|
options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier}
|
||||||
{
|
});
|
||||||
options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience
|
services.AddOcelot(configuration);
|
||||||
options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier}
|
|
||||||
});
|
|
||||||
services.AddOcelot(configuration);
|
NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope"
|
||||||
|
|
||||||
|
|
||||||
NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope"
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
// Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them
|
||||||
.. code-block:: csharp
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp");
|
||||||
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope");
|
||||||
// Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them
|
|
||||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp");
|
|
||||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope");
|
`Issue 446 <https://github.com/ThreeMammals/Ocelot/issues/446>`_ that contains some code and examples that might help with Okta integration.
|
||||||
|
|
||||||
|
Allowed Scopes
|
||||||
`Issue 446 <https://github.com/ThreeMammals/Ocelot/issues/446>`_ that contains some code and examples that might help with Okta integration.
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
Allowed Scopes
|
If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list.
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
This is a way to restrict access to a ReRoute on a per scope basis.
|
||||||
If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list.
|
|
||||||
|
|
||||||
This is a way to restrict access to a ReRoute on a per scope basis.
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
Authorisation
|
Authorisation
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Ocelot supports claims based authorisation which is run post authentication. This means if
|
Ocelot supports claims based authorisation which is run post authentication. This means if you have a route you want to authorise you can add the following to you ReRoute configuration.
|
||||||
you have a route you want to authorise you can add the following to you ReRoute configuration.
|
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
@ -10,9 +9,7 @@ you have a route you want to authorise you can add the following to you ReRoute
|
|||||||
"UserType": "registered"
|
"UserType": "registered"
|
||||||
}
|
}
|
||||||
|
|
||||||
In this example when the authorisation middleware is called Ocelot will check to see
|
In this example when the authorisation middleware is called Ocelot will check to seeif the user has the claim type UserType and if the value of that claim is registered. If it isn't then the user will not be authorised and the response will be 403 forbidden.
|
||||||
if the user has the claim type UserType and if the value of that claim is registered.
|
|
||||||
If it isn't then the user will not be authorised and the response will be 403 forbidden.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
Caching
|
Caching
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Ocelot supports some very rudimentary caching at the moment provider by
|
Ocelot supports some very rudimentary caching at the moment provider by the `CacheManager <https://github.com/MichaCo/CacheManager>`_ project. This is an amazing project that is solving a lot of caching problems. I would reccomend using this package to cache with Ocelot.
|
||||||
the `CacheManager <https://github.com/MichaCo/CacheManager>`_ project. This is an amazing project
|
|
||||||
that is solving a lot of caching problems. I would reccomend using this package to
|
|
||||||
cache with Ocelot.
|
|
||||||
|
|
||||||
The following example shows how to add CacheManager to Ocelot so that you can do output caching.
|
The following example shows how to add CacheManager to Ocelot so that you can do output caching.
|
||||||
|
|
||||||
@ -32,19 +29,14 @@ Finally in order to use caching on a route in your ReRoute configuration add thi
|
|||||||
|
|
||||||
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
||||||
|
|
||||||
If you look at the example `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager
|
If you look at the example `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager is setup and then passed into the Ocelot AddCacheManager configuration method. You can use any settings supported by the CacheManager package and just pass them in.
|
||||||
is setup and then passed into the Ocelot AddCacheManager configuration method. You can use any settings supported by
|
|
||||||
the CacheManager package and just pass them in.
|
|
||||||
|
|
||||||
Anyway Ocelot currently supports caching on the URL of the downstream service
|
Anyway Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache. You can also clear the cache for a region by calling Ocelot's administration API.
|
||||||
and setting a TTL in seconds to expire the cache. You can also clear the cache for a region
|
|
||||||
by calling Ocelot's administration API.
|
|
||||||
|
|
||||||
Your own caching
|
Your own caching
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If you want to add your own caching method implement the following interfaces and register them in DI
|
If you want to add your own caching method implement the following interfaces and register them in DI e.g. ``services.AddSingleton<IOcelotCache<CachedResponse>, MyCache>()``
|
||||||
e.g. ``services.AddSingleton<IOcelotCache<CachedResponse>, MyCache>()``
|
|
||||||
|
|
||||||
``IOcelotCache<CachedResponse>`` this is for output caching.
|
``IOcelotCache<CachedResponse>`` this is for output caching.
|
||||||
|
|
||||||
|
@ -1,35 +1,17 @@
|
|||||||
Claims Transformation
|
Claims Transformation
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Ocelot allows the user to access claims and transform them into headers, query string
|
Ocelot allows the user to access claims and transform them into headers, query string parameters, other claims and change downstream paths. This is only available once a user has been authenticated.
|
||||||
parameters, other claims and change downstream paths. This is only available once a user
|
|
||||||
has been authenticated.
|
|
||||||
|
|
||||||
After the user is authenticated we run the claims to claims transformation middleware.
|
After the user is authenticated we run the claims to claims transformation middleware. This allows the user to transform claims before the authorisation middleware is called. After the user is authorised first we call the claims to headers middleware, thenthe claims to query string parameters middleware, and Finally the claims to downstream pathmiddleware.
|
||||||
This allows the user to transform claims before the authorisation middleware is called.
|
|
||||||
After the user is authorised first we call the claims to headers middleware, then
|
|
||||||
the claims to query string parameters middleware, and Finally the claims to downstream path
|
|
||||||
middleware.
|
|
||||||
|
|
||||||
The syntax for performing the transforms is the same for each process. In the ReRoute
|
The syntax for performing the transforms is the same for each process. In the ReRoute configuration a json dictionary is added with a specific name either AddClaimsToRequest, AddHeadersToRequest, AddQueriesToRequest, or ChangeDownstreamPathTemplate.
|
||||||
configuration a json dictionary is added with a specific name either AddClaimsToRequest,
|
|
||||||
AddHeadersToRequest, AddQueriesToRequest, or ChangeDownstreamPathTemplate.
|
|
||||||
|
|
||||||
Note: I'm not a hotshot programmer so have no idea if this syntax is good...
|
Note: I'm not a hotshot programmer so have no idea if this syntax is good...
|
||||||
|
|
||||||
Within this dictionary the entries specify how Ocelot should transform things!
|
Within this dictionary the entries specify how Ocelot should transform things! The key to the dictionary is going to become the key of either a claim, header or query parameter. In the case of ChangeDownstreamPathTemplate, the key must be also specified in the DownstreamPathTemplate, in order to do the transformation.
|
||||||
The key to the dictionary is going to become the key of either a claim, header
|
|
||||||
or query parameter. In the case of ChangeDownstreamPathTemplate, the key must be
|
|
||||||
also specified in the DownstreamPathTemplate, in order to do the transformation.
|
|
||||||
|
|
||||||
The value of the entry is parsed to logic that will perform the transform. First of
|
The value of the entry is parsed to logic that will perform the transform. First ofall a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want to access the claims and get the CustomerId claim type. Next is a greater than (>)symbol which is just used to split the string. The next entry is either value or value with an indexer. If value is specified Ocelot will just take the value and add it to the transform. If the value has an indexer Ocelot will look for a delimiter which is provided after another greater than symbol. Ocelot will then split the value on the delimiter and add whatever was at the index requested to the transform.
|
||||||
all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want
|
|
||||||
to access the claims and get the CustomerId claim type. Next is a greater than (>)
|
|
||||||
symbol which is just used to split the string. The next entry is either value or value with
|
|
||||||
and indexer. If value is specified Ocelot will just take the value and add it to the
|
|
||||||
transform. If the value has an indexer Ocelot will look for a delimiter which is provided
|
|
||||||
after another greater than symbol. Ocelot will then split the value on the delimiter
|
|
||||||
and add whatever was at the index requested to the transform.
|
|
||||||
|
|
||||||
Claims to Claims Transformation
|
Claims to Claims Transformation
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -43,8 +25,7 @@ Below is an example configuration that will transforms claims to claims
|
|||||||
"UserId": "Claims[sub] > value[1] > |"
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
}
|
}
|
||||||
|
|
||||||
This shows a transforms where Ocelot looks at the users sub claim and transforms it into
|
This shows a transforms where Ocelot looks at the users sub claim and transforms it into UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue".
|
||||||
UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue".
|
|
||||||
|
|
||||||
Claims to Headers Tranformation
|
Claims to Headers Tranformation
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -57,8 +38,7 @@ Below is an example configuration that will transforms claims to headers
|
|||||||
"CustomerId": "Claims[sub] > value[1] > |"
|
"CustomerId": "Claims[sub] > value[1] > |"
|
||||||
}
|
}
|
||||||
|
|
||||||
This shows a transform where Ocelot looks at the users sub claim and transforms it into a
|
This shows a transform where Ocelot looks at the users sub claim and transforms it into a CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue".
|
||||||
CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue".
|
|
||||||
|
|
||||||
Claims to Query String Parameters Transformation
|
Claims to Query String Parameters Transformation
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -71,8 +51,7 @@ Below is an example configuration that will transforms claims to query string pa
|
|||||||
"LocationId": "Claims[LocationId] > value",
|
"LocationId": "Claims[LocationId] > value",
|
||||||
}
|
}
|
||||||
|
|
||||||
This shows a transform where Ocelot looks at the users LocationId claim and add it as
|
This shows a transform where Ocelot looks at the users LocationId claim and add it as a query string parameter to be forwarded onto the downstream service.
|
||||||
a query string parameter to be forwarded onto the downstream service.
|
|
||||||
|
|
||||||
Claims to Downstream Path Transformation
|
Claims to Downstream Path Transformation
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -87,10 +66,7 @@ Below is an example configuration that will transform claims to downstream path
|
|||||||
"userId": "Claims[sub] > value[1] > |",
|
"userId": "Claims[sub] > value[1] > |",
|
||||||
}
|
}
|
||||||
|
|
||||||
This shows a transform where Ocelot looks at the users userId claim and substitutes the value
|
This shows a transform where Ocelot looks at the users userId claim and substitutes the value to the "{userId}" placeholder specified in the DownstreamPathTemplate. Take into account that the key specified in the ChangeDownstreamPathTemplate must be the same than the placeholder specified in
|
||||||
to the "{userId}" placeholder specified in the DownstreamPathTemplate. Take into account that the
|
|
||||||
key specified in the ChangeDownstreamPathTemplate must be the same than the placeholder specified in
|
|
||||||
the DownstreamPathTemplate.
|
the DownstreamPathTemplate.
|
||||||
|
|
||||||
Note: if a key specified in the ChangeDownstreamPathTemplate does not exist as a placeholder in DownstreamPathTemplate
|
Note: if a key specified in the ChangeDownstreamPathTemplate does not exist as a placeholder in DownstreamPathTemplate it will fail at runtime returning an error in the response.
|
||||||
it will fail at runtime returning an error in the response.
|
|
@ -1,287 +1,273 @@
|
|||||||
Configuration
|
Configuration
|
||||||
============
|
============
|
||||||
|
|
||||||
An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/ocelot.json>`_.
|
An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/ocelot.json>`_. There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful if you don't want to manage lots of ReRoute specific settings.
|
||||||
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
|
.. code-block:: json
|
||||||
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
|
|
||||||
if you don't want to manage lots of ReRoute specific settings.
|
{
|
||||||
|
"ReRoutes": [],
|
||||||
.. code-block:: json
|
"GlobalConfiguration": {}
|
||||||
|
}
|
||||||
{
|
|
||||||
"ReRoutes": [],
|
Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment:
|
||||||
"GlobalConfiguration": {}
|
|
||||||
}
|
.. code-block:: json
|
||||||
|
|
||||||
Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment:
|
{
|
||||||
|
"DownstreamPathTemplate": "/",
|
||||||
.. code-block:: json
|
"UpstreamPathTemplate": "/",
|
||||||
|
"UpstreamHttpMethod": [
|
||||||
{
|
"Get"
|
||||||
"DownstreamPathTemplate": "/",
|
],
|
||||||
"UpstreamPathTemplate": "/",
|
"DownstreamHttpMethod": "",
|
||||||
"UpstreamHttpMethod": [
|
"DownstreamHttpVersion": "",
|
||||||
"Get"
|
"AddHeadersToRequest": {},
|
||||||
],
|
"AddClaimsToRequest": {},
|
||||||
"DownstreamHttpMethod": "",
|
"RouteClaimsRequirement": {},
|
||||||
"DownstreamHttpVersion": "",
|
"AddQueriesToRequest": {},
|
||||||
"AddHeadersToRequest": {},
|
"RequestIdKey": "",
|
||||||
"AddClaimsToRequest": {},
|
"FileCacheOptions": {
|
||||||
"RouteClaimsRequirement": {},
|
"TtlSeconds": 0,
|
||||||
"AddQueriesToRequest": {},
|
"Region": ""
|
||||||
"RequestIdKey": "",
|
},
|
||||||
"FileCacheOptions": {
|
"ReRouteIsCaseSensitive": false,
|
||||||
"TtlSeconds": 0,
|
"ServiceName": "",
|
||||||
"Region": ""
|
"DownstreamScheme": "http",
|
||||||
},
|
"DownstreamHostAndPorts": [
|
||||||
"ReRouteIsCaseSensitive": false,
|
{
|
||||||
"ServiceName": "",
|
"Host": "localhost",
|
||||||
"DownstreamScheme": "http",
|
"Port": 51876,
|
||||||
"DownstreamHostAndPorts": [
|
}
|
||||||
{
|
],
|
||||||
"Host": "localhost",
|
"QoSOptions": {
|
||||||
"Port": 51876,
|
"ExceptionsAllowedBeforeBreaking": 0,
|
||||||
}
|
"DurationOfBreak": 0,
|
||||||
],
|
"TimeoutValue": 0
|
||||||
"QoSOptions": {
|
},
|
||||||
"ExceptionsAllowedBeforeBreaking": 0,
|
"LoadBalancer": "",
|
||||||
"DurationOfBreak": 0,
|
"RateLimitOptions": {
|
||||||
"TimeoutValue": 0
|
"ClientWhitelist": [],
|
||||||
},
|
"EnableRateLimiting": false,
|
||||||
"LoadBalancer": "",
|
"Period": "",
|
||||||
"RateLimitOptions": {
|
"PeriodTimespan": 0,
|
||||||
"ClientWhitelist": [],
|
"Limit": 0
|
||||||
"EnableRateLimiting": false,
|
},
|
||||||
"Period": "",
|
"AuthenticationOptions": {
|
||||||
"PeriodTimespan": 0,
|
"AuthenticationProviderKey": "",
|
||||||
"Limit": 0
|
"AllowedScopes": []
|
||||||
},
|
},
|
||||||
"AuthenticationOptions": {
|
"HttpHandlerOptions": {
|
||||||
"AuthenticationProviderKey": "",
|
"AllowAutoRedirect": true,
|
||||||
"AllowedScopes": []
|
"UseCookieContainer": true,
|
||||||
},
|
"UseTracing": true,
|
||||||
"HttpHandlerOptions": {
|
"MaxConnectionsPerServer": 100
|
||||||
"AllowAutoRedirect": true,
|
},
|
||||||
"UseCookieContainer": true,
|
"DangerousAcceptAnyServerCertificateValidator": false
|
||||||
"UseTracing": true,
|
}
|
||||||
"MaxConnectionsPerServer": 100
|
|
||||||
},
|
More information on how to use these options is below..
|
||||||
"DangerousAcceptAnyServerCertificateValidator": false
|
|
||||||
}
|
Multiple environments
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
More information on how to use these options is below..
|
|
||||||
|
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following
|
||||||
Multiple environments
|
to you
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
.. code-block:: csharp
|
||||||
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following
|
|
||||||
to you
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
.. code-block:: csharp
|
config
|
||||||
|
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
.AddJsonFile("appsettings.json", true, true)
|
||||||
{
|
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||||
config
|
.AddJsonFile("ocelot.json")
|
||||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
|
||||||
.AddJsonFile("appsettings.json", true, true)
|
.AddEnvironmentVariables();
|
||||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
})
|
||||||
.AddJsonFile("ocelot.json")
|
|
||||||
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
|
Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one.
|
||||||
.AddEnvironmentVariables();
|
|
||||||
})
|
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>`_.
|
||||||
|
|
||||||
Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one.
|
Merging configuration files
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
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>`_.
|
|
||||||
|
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.
|
||||||
Merging configuration files
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
|
||||||
|
|
||||||
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.
|
.. code-block:: csharp
|
||||||
|
|
||||||
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
.. code-block:: csharp
|
config
|
||||||
|
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
.AddJsonFile("appsettings.json", true, true)
|
||||||
{
|
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||||
config
|
.AddOcelot(hostingContext.HostingEnvironment)
|
||||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
.AddEnvironmentVariables();
|
||||||
.AddJsonFile("appsettings.json", true, true)
|
})
|
||||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
|
||||||
.AddOcelot(hostingContext.HostingEnvironment)
|
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.
|
||||||
.AddEnvironmentVariables();
|
|
||||||
})
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
You can also give Ocelot a specific path to look in for the configuration files like below.
|
||||||
|
|
||||||
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.
|
.. code-block:: csharp
|
||||||
|
|
||||||
You can also give Ocelot a specific path to look in for the configuration files like below.
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
.. code-block:: csharp
|
config
|
||||||
|
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
.AddJsonFile("appsettings.json", true, true)
|
||||||
{
|
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||||
config
|
.AddOcelot("/foo/bar", hostingContext.HostingEnvironment)
|
||||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
.AddEnvironmentVariables();
|
||||||
.AddJsonFile("appsettings.json", true, true)
|
})
|
||||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
|
||||||
.AddOcelot("/foo/bar", hostingContext.HostingEnvironment)
|
Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm.
|
||||||
.AddEnvironmentVariables();
|
|
||||||
})
|
Store configuration in consul
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm.
|
|
||||||
|
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
|
||||||
Store configuration in consul
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
``Install-Package Ocelot.Provider.Consul``
|
||||||
|
|
||||||
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
|
Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
|
||||||
|
|
||||||
``Install-Package Ocelot.Provider.Consul``
|
.. code-block:: csharp
|
||||||
|
|
||||||
Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
|
services
|
||||||
|
.AddOcelot()
|
||||||
.. code-block:: csharp
|
.AddConsul()
|
||||||
|
.AddConfigStoredInConsul();
|
||||||
services
|
|
||||||
.AddOcelot()
|
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.
|
||||||
.AddConsul()
|
|
||||||
.AddConfigStoredInConsul();
|
.. code-block:: json
|
||||||
|
|
||||||
You also need to add the following to your ocelot.json. This is how Ocelot
|
"GlobalConfiguration": {
|
||||||
finds your Consul agent and interacts to load and store the configuration from Consul.
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Host": "localhost",
|
||||||
.. code-block:: json
|
"Port": 9500
|
||||||
|
}
|
||||||
"GlobalConfiguration": {
|
}
|
||||||
"ServiceDiscoveryProvider": {
|
|
||||||
"Host": "localhost",
|
I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
|
||||||
"Port": 9500
|
|
||||||
}
|
This feature has a 3 second ttl cache before making a new request to your local consul agent.
|
||||||
}
|
|
||||||
|
Reload JSON config on change
|
||||||
I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this!
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
|
|
||||||
|
Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated
|
||||||
This feature has a 3 second ttl cache before making a new request to your local consul agent.
|
manually.
|
||||||
|
|
||||||
Reload JSON config on change
|
.. code-block:: json
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
|
||||||
Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated
|
|
||||||
manually.
|
Configuration Key
|
||||||
|
-----------------
|
||||||
.. code-block:: json
|
|
||||||
|
If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g.
|
||||||
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
|
|
||||||
|
.. code-block:: json
|
||||||
Configuration Key
|
|
||||||
-----------------
|
"GlobalConfiguration": {
|
||||||
|
"ServiceDiscoveryProvider": {
|
||||||
If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g.
|
"Host": "localhost",
|
||||||
|
"Port": 9500,
|
||||||
.. code-block:: json
|
"ConfigurationKey": "Oceolot_A"
|
||||||
|
}
|
||||||
"GlobalConfiguration": {
|
}
|
||||||
"ServiceDiscoveryProvider": {
|
|
||||||
"Host": "localhost",
|
In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul.
|
||||||
"Port": 9500,
|
|
||||||
"ConfigurationKey": "Oceolot_A"
|
If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key.
|
||||||
}
|
|
||||||
}
|
Follow Redirects / Use CookieContainer
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul.
|
|
||||||
|
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
||||||
If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key.
|
|
||||||
|
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.
|
||||||
Follow Redirects / Use CookieContainer
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
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 don't 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!
|
||||||
|
|
||||||
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
SSL Errors
|
||||||
|
^^^^^^^^^^
|
||||||
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.
|
If you want to ignore SSL warnings / errors set the following in your ReRoute config.
|
||||||
|
|
||||||
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
|
.. code-block:: json
|
||||||
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
|
"DangerousAcceptAnyServerCertificateValidator": true
|
||||||
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
|
I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can.
|
||||||
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 don't use the cookies from the previous response! All in all not a great situation. I would avoid setting
|
MaxConnectionsPerServer
|
||||||
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
|
This controls how many connections the internal HttpClient will open. This can be set at ReRoute or global level.
|
||||||
^^^^^^^^^^
|
|
||||||
|
React to Configuration Changes
|
||||||
If you want to ignore SSL warnings / errors set the following in your ReRoute config.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. code-block:: json
|
Resolve IOcelotConfigurationChangeTokenSource from the DI container if you wish to react to changes to the Ocelot configuration via the Ocelot.Administration API or ocelot.json being reloaded from the disk. You may either poll the change token's HasChanged property, or register a callback with the RegisterChangeCallback method.
|
||||||
|
|
||||||
"DangerousAcceptAnyServerCertificateValidator": true
|
Polling the HasChanged property
|
||||||
|
-------------------------------
|
||||||
I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can.
|
|
||||||
|
.. code-block:: csharp
|
||||||
MaxConnectionsPerServer
|
public class ConfigurationNotifyingService : BackgroundService
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
{
|
||||||
|
private readonly IOcelotConfigurationChangeTokenSource _tokenSource;
|
||||||
This controls how many connections the internal HttpClient will open. This can be set at ReRoute or global level.
|
private readonly ILogger _logger;
|
||||||
|
public ConfigurationNotifyingService(IOcelotConfigurationChangeTokenSource tokenSource, ILogger logger)
|
||||||
React to Configuration Changes
|
{
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
_tokenSource = tokenSource;
|
||||||
|
_logger = logger;
|
||||||
Resolve IOcelotConfigurationChangeTokenSource from the DI container if you wish to react to changes to the Ocelot configuration via the Ocelot.Administration API or ocelot.json being reloaded from the disk. You may either poll the change token's HasChanged property, or register a callback with the RegisterChangeCallback method.
|
}
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
Polling the HasChanged property
|
{
|
||||||
-------------------------------
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
.. code-block:: csharp
|
if (_tokenSource.ChangeToken.HasChanged)
|
||||||
public class ConfigurationNotifyingService : BackgroundService
|
{
|
||||||
{
|
_logger.LogInformation("Configuration updated");
|
||||||
private readonly IOcelotConfigurationChangeTokenSource _tokenSource;
|
}
|
||||||
private readonly ILogger _logger;
|
await Task.Delay(1000, stoppingToken);
|
||||||
public ConfigurationNotifyingService(IOcelotConfigurationChangeTokenSource tokenSource, ILogger logger)
|
}
|
||||||
{
|
}
|
||||||
_tokenSource = tokenSource;
|
}
|
||||||
_logger = logger;
|
|
||||||
}
|
Registering a callback
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
----------------------
|
||||||
{
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
.. code-block:: csharp
|
||||||
{
|
public class MyDependencyInjectedClass : IDisposable
|
||||||
if (_tokenSource.ChangeToken.HasChanged)
|
{
|
||||||
{
|
private readonly IOcelotConfigurationChangeTokenSource _tokenSource;
|
||||||
_logger.LogInformation("Configuration updated");
|
private readonly IDisposable _callbackHolder;
|
||||||
}
|
public MyClass(IOcelotConfigurationChangeTokenSource tokenSource)
|
||||||
await Task.Delay(1000, stoppingToken);
|
{
|
||||||
}
|
_tokenSource = tokenSource;
|
||||||
}
|
_callbackHolder = tokenSource.ChangeToken.RegisterChangeCallback(_ => Console.WriteLine("Configuration changed"), null);
|
||||||
}
|
}
|
||||||
|
public void Dispose()
|
||||||
Registering a callback
|
{
|
||||||
----------------------
|
_callbackHolder.Dispose();
|
||||||
|
}
|
||||||
.. code-block:: csharp
|
}
|
||||||
public class MyDependencyInjectedClass : IDisposable
|
|
||||||
{
|
DownstreamHttpVersion
|
||||||
private readonly IOcelotConfigurationChangeTokenSource _tokenSource;
|
---------------------
|
||||||
private readonly IDisposable _callbackHolder;
|
|
||||||
public MyClass(IOcelotConfigurationChangeTokenSource tokenSource)
|
|
||||||
{
|
|
||||||
_tokenSource = tokenSource;
|
|
||||||
_callbackHolder = tokenSource.ChangeToken.RegisterChangeCallback(_ => Console.WriteLine("Configuration changed"), null);
|
|
||||||
}
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_callbackHolder.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DownstreamHttpVersion
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as "1.0", "1.1" or "2.0".
|
Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as "1.0", "1.1" or "2.0".
|
@ -1,66 +1,64 @@
|
|||||||
Delegating Handlers
|
Delegating Handlers
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/ThreeMammals/Ocelot/issues/208>`_
|
Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/ThreeMammals/Ocelot/issues/208>`_
|
||||||
and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 <https://github.com/ThreeMammals/Ocelot/issues/264>`_.
|
and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 <https://github.com/ThreeMammals/Ocelot/issues/264>`_.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
In order to add delegating handlers to the HttpClient transport you need to do two main things.
|
In order to add delegating handlers to the HttpClient transport you need to do two main things.
|
||||||
|
|
||||||
First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the
|
First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the
|
||||||
asp.net core container so you can inject any other services you have registered into the constructor of your handler.
|
asp.net core container so you can inject any other services you have registered into the constructor of your handler.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
public class FakeHandler : DelegatingHandler
|
public class FakeHandler : DelegatingHandler
|
||||||
{
|
{
|
||||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
//do stuff and optionally call the base handler..
|
//do stuff and optionally call the base handler..
|
||||||
return await base.SendAsync(request, cancellationToken);
|
return await base.SendAsync(request, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Next you must add the handlers to Ocelot's container like below...
|
Next you must add the handlers to Ocelot's container like below...
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services.AddOcelot()
|
services.AddOcelot()
|
||||||
.AddDelegatingHandler<FakeHandler>()
|
.AddDelegatingHandler<FakeHandler>()
|
||||||
.AddDelegatingHandler<FakeHandlerTwo>()
|
.AddDelegatingHandler<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 ocelot.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.
|
|
||||||
|
As below...
|
||||||
As below...
|
|
||||||
|
.. code-block:: csharp
|
||||||
.. code-block:: csharp
|
|
||||||
|
services.AddOcelot()
|
||||||
services.AddOcelot()
|
.AddDelegatingHandler<FakeHandler>(true)
|
||||||
.AddDelegatingHandler<FakeHandler>(true)
|
|
||||||
|
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 ocelot.json. The names in the array must match the class names of your
|
||||||
Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers
|
DelegatingHandlers for Ocelot to match them together.
|
||||||
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.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
"DelegatingHandlers": [
|
||||||
|
"FakeHandlerTwo",
|
||||||
"DelegatingHandlers": [
|
"FakeHandler"
|
||||||
"FakeHandlerTwo",
|
]
|
||||||
"FakeHandler"
|
|
||||||
]
|
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 ocelot.json.
|
||||||
|
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.
|
||||||
1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json.
|
3. Tracing DelegatingHandler if enabled (see tracing docs).
|
||||||
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.
|
4. QoS DelegatingHandler if enabled (see QoS docs).
|
||||||
3. Tracing DelegatingHandler if enabled (see tracing docs).
|
5. The HttpClient sends the HttpRequestMessage.
|
||||||
4. QoS DelegatingHandler if enabled (see QoS docs).
|
|
||||||
5. The HttpClient sends the HttpRequestMessage.
|
Hopefully other people will find this feature useful!
|
||||||
|
|
||||||
Hopefully other people will find this feature useful!
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
Http Error Status Codes
|
Http Error Status Codes
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
Ocelot will return HTTP status error codes based on internal logic in certain siturations:
|
Ocelot will return HTTP status error codes based on internal logic in certain siturations:
|
||||||
- 401 if the authentication middleware runs and the user is not authenticated.
|
- 401 if the authentication middleware runs and the user is not authenticated.
|
||||||
- 403 if the authorisation middleware runs and the user is unauthenticated, claim value not authroised, scope not authorised, user doesnt have required claim or cannot find claim.
|
- 403 if the authorisation middleware runs and the user is unauthenticated, claim value not authroised, scope not authorised, user doesnt have required claim or cannot find claim.
|
||||||
- 503 if the downstream request times out.
|
- 503 if the downstream request times out.
|
||||||
- 499 if the request is cancelled by the client.
|
- 499 if the request is cancelled by the client.
|
||||||
- 404 if unable to find a downstream route.
|
- 404 if unable to find a downstream route.
|
||||||
- 502 if unable to connect to downstream service.
|
- 502 if unable to connect to downstream service.
|
||||||
- 500 if unable to complete the HTTP request downstream and the exception is not OperationCanceledException or HttpRequestException.
|
- 500 if unable to complete the HTTP request downstream and the exception is not OperationCanceledException or HttpRequestException.
|
||||||
- 404 if Ocelot is unable to map an internal error code to a HTTP status code.
|
- 404 if Ocelot is unable to map an internal error code to a HTTP status code.
|
||||||
|
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
GraphQL
|
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
|
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.
|
||||||
the `graphql-dotnet <https://github.com/graphql-dotnet/graphql-dotnet>`_ library.
|
|
||||||
|
|
||||||
|
|
||||||
Please see the sample project `OcelotGraphQL <https://github.com/ThreeMammals/Ocelot/tree/master/samples/OcelotGraphQL>`_.
|
Please see the sample project `OcelotGraphQL <https://github.com/ThreeMammals/Ocelot/tree/master/samples/OcelotGraphQL>`_. Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do.
|
||||||
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!
|
||||||
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 :>
|
Good luck and have fun :>
|
||||||
|
|
||||||
|
@ -1,151 +1,150 @@
|
|||||||
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/ThreeMammals/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/ThreeMammals/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
|
||||||
|
|
||||||
Add to Request
|
Add to Request
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requestes in `GitHub #313 <https://github.com/ThreeMammals/Ocelot/issues/313>`_.
|
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:
|
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
|
.. code-block:: json
|
||||||
|
|
||||||
"UpstreamHeaderTransform": {
|
"UpstreamHeaderTransform": {
|
||||||
"Uncle": "Bob"
|
"Uncle": "Bob"
|
||||||
}
|
}
|
||||||
|
|
||||||
In the example above a header with the key Uncle and value Bob would be send to to the upstream service.
|
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).
|
Placeholders are supported too (see below).
|
||||||
|
|
||||||
Add to Response
|
Add to Response
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `GitHub #280 <https://github.com/ThreeMammals/Ocelot/issues/280>`_.
|
This feature was requested in `GitHub #280 <https://github.com/ThreeMammals/Ocelot/issues/280>`_.
|
||||||
|
|
||||||
If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json..
|
If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json..
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"DownstreamHeaderTransform": {
|
"DownstreamHeaderTransform": {
|
||||||
"Uncle": "Bob"
|
"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.
|
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..
|
If you want to return the Butterfly APM trace id then do something like the following..
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"DownstreamHeaderTransform": {
|
"DownstreamHeaderTransform": {
|
||||||
"AnyKey": "{TraceId}"
|
"AnyKey": "{TraceId}"
|
||||||
},
|
},
|
||||||
|
|
||||||
Find and Replace
|
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.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
|
|
||||||
The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more.
|
The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more.
|
||||||
|
|
||||||
Pre Downstream Request
|
Pre Downstream Request
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
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.
|
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
|
||||||
|
|
||||||
"UpstreamHeaderTransform": {
|
"UpstreamHeaderTransform": {
|
||||||
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
},
|
},
|
||||||
|
|
||||||
Post Downstream Request
|
Post Downstream Request
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
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.
|
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
|
||||||
|
|
||||||
"DownstreamHeaderTransform": {
|
"DownstreamHeaderTransform": {
|
||||||
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
},
|
},
|
||||||
|
|
||||||
Placeholders
|
Placeholders
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
Ocelot allows placeholders that can be used in header transformation.
|
Ocelot allows placeholders that can be used in header transformation.
|
||||||
|
|
||||||
{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP.
|
{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP.
|
||||||
{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.
|
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
|
||||||
{UpstreamHost} - This will look for the incoming Host header.
|
{UpstreamHost} - This will look for the incoming Host header.
|
||||||
|
|
||||||
Handling 302 Redirects
|
Handling 302 Redirects
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration.
|
Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"DownstreamHeaderTransform": {
|
"DownstreamHeaderTransform": {
|
||||||
"Location": "http://www.bbc.co.uk/, http://ocelot.com/"
|
"Location": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
},
|
},
|
||||||
"HttpHandlerOptions": {
|
"HttpHandlerOptions": {
|
||||||
"AllowAutoRedirect": false,
|
"AllowAutoRedirect": false,
|
||||||
},
|
},
|
||||||
|
|
||||||
or you could use the BaseUrl placeholder.
|
or you could use the BaseUrl placeholder.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"DownstreamHeaderTransform": {
|
"DownstreamHeaderTransform": {
|
||||||
"Location": "http://localhost:6773, {BaseUrl}"
|
"Location": "http://localhost:6773, {BaseUrl}"
|
||||||
},
|
},
|
||||||
"HttpHandlerOptions": {
|
"HttpHandlerOptions": {
|
||||||
"AllowAutoRedirect": false,
|
"AllowAutoRedirect": false,
|
||||||
},
|
},
|
||||||
|
|
||||||
finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following.
|
finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"DownstreamHeaderTransform": {
|
"DownstreamHeaderTransform": {
|
||||||
"Location": "{DownstreamBaseUrl}, {BaseUrl}"
|
"Location": "{DownstreamBaseUrl}, {BaseUrl}"
|
||||||
},
|
},
|
||||||
"HttpHandlerOptions": {
|
"HttpHandlerOptions": {
|
||||||
"AllowAutoRedirect": false,
|
"AllowAutoRedirect": false,
|
||||||
},
|
},
|
||||||
|
|
||||||
X-Forwarded-For
|
X-Forwarded-For
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
An example of using {RemoteIpAddress} placeholder...
|
An example of using {RemoteIpAddress} placeholder...
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"UpstreamHeaderTransform": {
|
"UpstreamHeaderTransform": {
|
||||||
"X-Forwarded-For": "{RemoteIpAddress}"
|
"X-Forwarded-For": "{RemoteIpAddress}"
|
||||||
}
|
}
|
||||||
|
|
||||||
Future
|
Future
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one.
|
Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. It would also be nice if it could multi find and replace e.g.
|
||||||
It would also be nice if it could multi find and replace e.g.
|
|
||||||
|
.. code-block:: json
|
||||||
.. code-block:: json
|
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
"DownstreamHeaderTransform": {
|
"Location": "[{one,one},{two,two}"
|
||||||
"Location": "[{one,one},{two,two}"
|
},
|
||||||
},
|
"HttpHandlerOptions": {
|
||||||
"HttpHandlerOptions": {
|
"AllowAutoRedirect": false,
|
||||||
"AllowAutoRedirect": false,
|
},
|
||||||
},
|
|
||||||
|
If anyone wants to have a go at this please help yourself!!
|
||||||
If anyone wants to have a go at this please help yourself!!
|
|
||||||
|
@ -1,97 +1,95 @@
|
|||||||
Kubernetes
|
Kubernetes
|
||||||
==============
|
==============
|
||||||
|
|
||||||
This feature was requested as part of `Issue 345 <https://github.com/ThreeMammals/Ocelot/issues/345>`_ . to add support for kubernetes's provider.
|
This feature was requested as part of `Issue 345 <https://github.com/ThreeMammals/Ocelot/issues/345>`_ . to add support for kubernetes's provider.
|
||||||
|
|
||||||
Ocelot will call the k8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them. Ocelot used to use the services api to send requests to the k8s service but this was changed in `PR 1134 <https://github.com/ThreeMammals/Ocelot/pull/1134>`_ because the service did not load balance as expected.
|
Ocelot will call the k8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them. Ocelot used to use the services api to send requests to the k8s service but this was changed in `PR 1134 <https://github.com/ThreeMammals/Ocelot/pull/1134>`_ because the service did not load balance as expected.
|
||||||
|
|
||||||
The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot.
|
The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot.
|
||||||
|
|
||||||
``Install-Package Ocelot.Provider.Kubernetes``
|
``Install-Package Ocelot.Provider.Kubernetes``
|
||||||
|
|
||||||
Then add the following to your ConfigureServices method.
|
Then add the following to your ConfigureServices method.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
s.AddOcelot()
|
s.AddOcelot()
|
||||||
.AddKubernetes();
|
.AddKubernetes();
|
||||||
|
|
||||||
If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization
|
If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization
|
||||||
|
|
||||||
.. code-block::csharp
|
.. code-block::csharp
|
||||||
public static class OcelotBuilderExtensions
|
public static class OcelotBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true);
|
public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
You can replicate a Permissive. Using RBAC role bindings.
|
You can replicate a Permissive. Using RBAC role bindings.
|
||||||
`Permissive RBAC Permissions <https://kubernetes.io/docs/reference/access-authn-authz/rbac/#permissive-rbac-permissions>`_, k8s api server and token will read from pod.
|
`Permissive RBAC Permissions <https://kubernetes.io/docs/reference/access-authn-authz/rbac/#permissive-rbac-permissions>`_, k8s api server and token will read from pod.
|
||||||
|
|
||||||
.. code-block::bash
|
.. code-block::bash
|
||||||
kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts
|
kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts
|
||||||
|
|
||||||
The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the
|
The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration.
|
||||||
kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration.
|
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
.. code-block:: json
|
|
||||||
|
{
|
||||||
{
|
"ReRoutes": [
|
||||||
"ReRoutes": [
|
{
|
||||||
{
|
"DownstreamPathTemplate": "/api/values",
|
||||||
"DownstreamPathTemplate": "/api/values",
|
"DownstreamScheme": "http",
|
||||||
"DownstreamScheme": "http",
|
"UpstreamPathTemplate": "/values",
|
||||||
"UpstreamPathTemplate": "/values",
|
"ServiceName": "downstreamservice",
|
||||||
"ServiceName": "downstreamservice",
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
}
|
||||||
}
|
],
|
||||||
],
|
"GlobalConfiguration": {
|
||||||
"GlobalConfiguration": {
|
"ServiceDiscoveryProvider": {
|
||||||
"ServiceDiscoveryProvider": {
|
"Host": "192.168.0.13",
|
||||||
"Host": "192.168.0.13",
|
"Port": 443,
|
||||||
"Port": 443,
|
"Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
|
||||||
"Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
|
"Namespace": "dev",
|
||||||
"Namespace": "dev",
|
"Type": "kube"
|
||||||
"Type": "kube"
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type.
|
||||||
Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type.
|
Note: Host、 Port and Token are no longer in use。
|
||||||
Note: Host、 Port and Token are no longer in use。
|
|
||||||
|
You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration.
|
||||||
You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration.
|
|
||||||
|
.. code-block:: json
|
||||||
.. code-block:: json
|
|
||||||
|
"ServiceDiscoveryProvider": {
|
||||||
"ServiceDiscoveryProvider": {
|
"Host": "192.168.0.13",
|
||||||
"Host": "192.168.0.13",
|
"Port": 443,
|
||||||
"Port": 443,
|
"Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
|
||||||
"Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
|
"Namespace": "dev",
|
||||||
"Namespace": "dev",
|
"Type": "pollkube",
|
||||||
"Type": "pollkube",
|
"PollingInterval": 100
|
||||||
"PollingInterval": 100
|
}
|
||||||
}
|
|
||||||
|
The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration.
|
||||||
The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration.
|
|
||||||
|
Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. There is no way for Ocelot to work these out for you.
|
||||||
Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request.
|
|
||||||
There is no way for Ocelot to work these out for you.
|
If your downstream service resides in a different namespace you can override the global setting at the ReRoute level by specifying a ServiceNamespace.
|
||||||
|
|
||||||
If your downstream service resides in a different namespace you can override the global setting at the ReRoute level by specifying a ServiceNamespace.
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
"ReRoutes": [
|
"DownstreamPathTemplate": "/api/values",
|
||||||
{
|
"DownstreamScheme": "http",
|
||||||
"DownstreamPathTemplate": "/api/values",
|
"UpstreamPathTemplate": "/values",
|
||||||
"DownstreamScheme": "http",
|
"ServiceName": "downstreamservice",
|
||||||
"UpstreamPathTemplate": "/values",
|
"ServiceNamespace": "downstream-namespace",
|
||||||
"ServiceName": "downstreamservice",
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
"ServiceNamespace": "downstream-namespace",
|
}
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -1,219 +1,210 @@
|
|||||||
Load Balancer
|
Load Balancer
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively.
|
Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively.
|
||||||
|
|
||||||
The type of load balancer available are:
|
The type of load balancer available are:
|
||||||
|
|
||||||
LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
||||||
|
|
||||||
RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
||||||
|
|
||||||
NoLoadBalancer - takes the first available service from config or service discovery.
|
NoLoadBalancer - takes the first available service from config or service discovery.
|
||||||
|
|
||||||
CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below.
|
CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below.
|
||||||
|
|
||||||
You must choose in your configuration which load balancer to use.
|
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 ocelot.json and then select the LeastConnection 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 LeastConnection load balancer. This is the simplest way to get load balancing set up.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "10.0.1.10",
|
"Host": "10.0.1.10",
|
||||||
"Port": 5000,
|
"Port": 5000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Host": "10.0.1.11",
|
"Host": "10.0.1.11",
|
||||||
"Port": 5000,
|
"Port": 5000,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"LoadBalancerOptions": {
|
"LoadBalancerOptions": {
|
||||||
"Type": "LeastConnection"
|
"Type": "LeastConnection"
|
||||||
},
|
},
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Service Discovery
|
Service Discovery
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer.
|
The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
"ServiceName": "product",
|
"ServiceName": "product",
|
||||||
"LoadBalancerOptions": {
|
"LoadBalancerOptions": {
|
||||||
"Type": "LeastConnection"
|
"Type": "LeastConnection"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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. If you add and remove services from the
|
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. If you add and remove services from the
|
||||||
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
||||||
|
|
||||||
CookieStickySessions
|
CookieStickySessions
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream
|
I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 <https://github.com/ThreeMammals/Ocelot/issues/322>`_ though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have!
|
||||||
servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each
|
|
||||||
time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 <https://github.com/ThreeMammals/Ocelot/issues/322>`_
|
In order to set up CookieStickySessions load balancer you need to do something like the following.
|
||||||
though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have!
|
|
||||||
|
.. code-block:: json
|
||||||
In order to set up CookieStickySessions load balancer you need to do something like the following.
|
|
||||||
|
{
|
||||||
.. code-block:: json
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
{
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
{
|
||||||
"DownstreamScheme": "https",
|
"Host": "10.0.1.10",
|
||||||
"DownstreamHostAndPorts": [
|
"Port": 5000,
|
||||||
{
|
},
|
||||||
"Host": "10.0.1.10",
|
{
|
||||||
"Port": 5000,
|
"Host": "10.0.1.11",
|
||||||
},
|
"Port": 5000,
|
||||||
{
|
}
|
||||||
"Host": "10.0.1.11",
|
],
|
||||||
"Port": 5000,
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
}
|
"LoadBalancerOptions": {
|
||||||
],
|
"Type": "CookieStickySessions",
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"Key": "ASP.NET_SessionId",
|
||||||
"LoadBalancerOptions": {
|
"Expiry": 1800000
|
||||||
"Type": "CookieStickySessions",
|
},
|
||||||
"Key": "ASP.NET_SessionId",
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
"Expiry": 1800000
|
}
|
||||||
},
|
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this refreshes on every request which is meant to mimick how sessions work usually.
|
||||||
}
|
|
||||||
|
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there subsequent requests. This means the sessions will be stuck across ReRoutes.
|
||||||
The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you
|
|
||||||
wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this
|
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the moment but could be changed.
|
||||||
refreshes on every request which is meant to mimick how sessions work usually.
|
|
||||||
|
Custom Load Balancers
|
||||||
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
|
||||||
|
`DavidLievrouw <https://github.com/DavidLievrouw`_ implemented a way to provide Ocelot with custom load balancer in `PR 1155 <https://github.com/ThreeMammals/Ocelot/pull/1155`_.
|
||||||
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
|
||||||
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
In order to create and use a custom load balancer you can do the following. Below we setup a basic load balancing config and not the Type is CustomLoadBalancer this is the name of a class we will setup to do load balancing.
|
||||||
moment but could be changed.
|
|
||||||
|
.. code-block:: json
|
||||||
Custom Load Balancers
|
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
`DavidLievrouw <https://github.com/DavidLievrouw`_ implemented a way to provide Ocelot with custom load balancer in `PR 1155 <https://github.com/ThreeMammals/Ocelot/pull/1155`_.
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
In order to create and use a custom load balancer you can do the following. Below we setup a basic load balancing config and not the Type is CustomLoadBalancer this is the name of a class we will
|
{
|
||||||
setup to do load balancing.
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
.. code-block:: json
|
},
|
||||||
|
{
|
||||||
{
|
"Host": "10.0.1.11",
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"Port": 5000,
|
||||||
"DownstreamScheme": "https",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"Host": "10.0.1.10",
|
"LoadBalancerOptions": {
|
||||||
"Port": 5000,
|
"Type": "CustomLoadBalancer"
|
||||||
},
|
},
|
||||||
{
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
"Host": "10.0.1.11",
|
}
|
||||||
"Port": 5000,
|
|
||||||
}
|
|
||||||
],
|
Then you need to create a class that implements the ILoadBalancer interface. Below is a simple round robin example.
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"LoadBalancerOptions": {
|
.. code-block:: csharp
|
||||||
"Type": "CustomLoadBalancer"
|
|
||||||
},
|
private class CustomLoadBalancer : ILoadBalancer
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
{
|
||||||
}
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
Then you need to create a class that implements the ILoadBalancer interface. Below is a simple round robin example.
|
private int _last;
|
||||||
|
|
||||||
.. code-block:: csharp
|
public CustomLoadBalancer(Func<Task<List<Service>>> services)
|
||||||
|
{
|
||||||
private class CustomLoadBalancer : ILoadBalancer
|
_services = services;
|
||||||
{
|
}
|
||||||
private readonly Func<Task<List<Service>>> _services;
|
|
||||||
private readonly object _lock = new object();
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext, HttpContext httpContext)
|
||||||
|
{
|
||||||
private int _last;
|
var services = await _services();
|
||||||
|
lock (_lock)
|
||||||
public CustomLoadBalancer(Func<Task<List<Service>>> services)
|
{
|
||||||
{
|
if (_last >= services.Count)
|
||||||
_services = services;
|
{
|
||||||
}
|
_last = 0;
|
||||||
|
}
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
|
||||||
{
|
var next = services[_last];
|
||||||
var services = await _services();
|
_last++;
|
||||||
lock (_lock)
|
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
|
||||||
{
|
}
|
||||||
if (_last >= services.Count)
|
}
|
||||||
{
|
|
||||||
_last = 0;
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
}
|
{
|
||||||
|
}
|
||||||
var next = services[_last];
|
}
|
||||||
_last++;
|
|
||||||
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
|
Finally you need to register this class with Ocelot. I have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers.
|
||||||
}
|
|
||||||
}
|
.. code-block:: csharp
|
||||||
|
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, CustomLoadBalancer> loadBalancerFactoryFunc = (serviceProvider, reRoute, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get);
|
||||||
{
|
|
||||||
}
|
s.AddOcelot()
|
||||||
}
|
.AddCustomLoadBalancer(loadBalancerFactoryFunc);
|
||||||
|
|
||||||
Finally you need to register this class with Ocelot. I have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers.
|
However there is a much simpler example that will work the same.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, CustomLoadBalancer> loadBalancerFactoryFunc = (serviceProvider, reRoute, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get);
|
s.AddOcelot()
|
||||||
|
.AddCustomLoadBalancer<CustomLoadBalancer>();
|
||||||
s.AddOcelot()
|
|
||||||
.AddCustomLoadBalancer(loadBalancerFactoryFunc);
|
There are numerous extension methods to add a custom load balancer and the interface is as follows.
|
||||||
|
|
||||||
However there is a much simpler example that will work the same.
|
.. code-block:: csharp
|
||||||
|
|
||||||
.. code-block:: csharp
|
IOcelotBuilder AddCustomLoadBalancer<T>()
|
||||||
|
where T : ILoadBalancer, new();
|
||||||
s.AddOcelot()
|
|
||||||
.AddCustomLoadBalancer<CustomLoadBalancer>();
|
IOcelotBuilder AddCustomLoadBalancer<T>(Func<T> loadBalancerFactoryFunc)
|
||||||
|
where T : ILoadBalancer;
|
||||||
There are numerous extension methods to add a custom load balancer and the interface is as follows.
|
|
||||||
|
IOcelotBuilder AddCustomLoadBalancer<T>(Func<IServiceProvider, T> loadBalancerFactoryFunc)
|
||||||
.. code-block:: csharp
|
where T : ILoadBalancer;
|
||||||
|
|
||||||
IOcelotBuilder AddCustomLoadBalancer<T>()
|
IOcelotBuilder AddCustomLoadBalancer<T>(
|
||||||
where T : ILoadBalancer, new();
|
Func<DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
|
||||||
|
where T : ILoadBalancer;
|
||||||
IOcelotBuilder AddCustomLoadBalancer<T>(Func<T> loadBalancerFactoryFunc)
|
|
||||||
where T : ILoadBalancer;
|
IOcelotBuilder AddCustomLoadBalancer<T>(
|
||||||
|
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
|
||||||
IOcelotBuilder AddCustomLoadBalancer<T>(Func<IServiceProvider, T> loadBalancerFactoryFunc)
|
where T : ILoadBalancer;
|
||||||
where T : ILoadBalancer;
|
|
||||||
|
When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. If it finds a match it will use your load balaner to load balance. If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class then you will receive a HTTP 500 internal server error. If your load balancer factory throw an exception when Ocelot calls it you will receive a HTTP 500 internal server error.
|
||||||
IOcelotBuilder AddCustomLoadBalancer<T>(
|
|
||||||
Func<DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
|
|
||||||
where T : ILoadBalancer;
|
|
||||||
|
|
||||||
IOcelotBuilder AddCustomLoadBalancer<T>(
|
|
||||||
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
|
|
||||||
where T : ILoadBalancer;
|
|
||||||
|
|
||||||
When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. If it finds a match it will use your load balaner to load balance. If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class then you will receive a HTTP 500 internal server error. If your load balancer factory throw an exception when Ocelot calls it you will receive a HTTP 500 internal server error.
|
|
||||||
|
|
||||||
Remember if you specify no load balancer in your config Ocelot will not try and load balance.
|
Remember if you specify no load balancer in your config Ocelot will not try and load balance.
|
@ -1,20 +1,17 @@
|
|||||||
Logging
|
Logging
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
|
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment. This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
|
||||||
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
|
|
||||||
for the standard asp.net core logging stuff at the moment. This is because Ocelot add's some extra info to the logs such as request id if it is configured.
|
for the standard asp.net core logging stuff at the moment. This is because Ocelot add's some extra info to the logs such as request id if it is configured.
|
||||||
|
|
||||||
There is a global error handler that should catch any exceptions thrown and log them as errors.
|
There is a global error handler that should catch any exceptions thrown and log them as errors.
|
||||||
|
|
||||||
Finally if logging is set to trace level Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful.
|
Finally if logging is set to trace level Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful.
|
||||||
|
|
||||||
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
|
Warning
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
If you are logging to Console you will get terrible performance. I have had so many issues about performance issues with Ocelot
|
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 :)
|
||||||
and it is always logging level Debug, logging to Console :) Make sure you are logging to something proper in production :)
|
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
HTTP Method Transformation
|
HTTP Method Transformation
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service.
|
Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service.
|
||||||
|
|
||||||
This achieved by setting the following ReRoute configuration:
|
This achieved by setting the following ReRoute configuration:
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/{url}",
|
"DownstreamPathTemplate": "/{url}",
|
||||||
"UpstreamPathTemplate": "/{url}",
|
"UpstreamPathTemplate": "/{url}",
|
||||||
"UpstreamHttpMethod": [
|
"UpstreamHttpMethod": [
|
||||||
"Get"
|
"Get"
|
||||||
],
|
],
|
||||||
"DownstreamHttpMethod": "POST",
|
"DownstreamHttpMethod": "POST",
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 53271
|
"Port": 53271
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
The key property here is DownstreamHttpMethod which is set as POST and the ReRoute will only match on GET as set by UpstreamHttpMethod.
|
The key property here is DownstreamHttpMethod which is set as POST and the ReRoute will only match on GET as set by UpstreamHttpMethod.
|
||||||
|
|
||||||
This feature can be useful when interacting with downstream apis that only support POST and you want to present some kind of RESTful interface.
|
This feature can be useful when interacting with downstream apis that only support POST and you want to present some kind of RESTful interface.
|
@ -1,8 +1,7 @@
|
|||||||
Quality of Service
|
Quality of Service
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
|
Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you want to use a circuit breaker when making requests to a downstream service. This uses an awesome
|
||||||
want to use a circuit breaker when making requests to a downstream service. This uses an awesome
|
|
||||||
.NET library called Polly check them out `here <https://github.com/App-vNext/Polly>`_.
|
.NET library called Polly check them out `here <https://github.com/App-vNext/Polly>`_.
|
||||||
|
|
||||||
The first thing you need to do if you want to use the administration API is bring in the relevant NuGet package..
|
The first thing you need to do if you want to use the administration API is bring in the relevant NuGet package..
|
||||||
@ -30,8 +29,7 @@ Then add the following section to a ReRoute configuration.
|
|||||||
"TimeoutValue":5000
|
"TimeoutValue":5000
|
||||||
}
|
}
|
||||||
|
|
||||||
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 means the circuit breaker will stay open for 1 second after it is tripped.
|
||||||
implemented. Duration of break means the circuit breaker will stay open for 1 second after it is tripped.
|
|
||||||
TimeoutValue means if 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.
|
||||||
|
|
||||||
You can set the TimeoutValue in isolation of the ExceptionsAllowedBeforeBreaking and DurationOfBreak options.
|
You can set the TimeoutValue in isolation of the ExceptionsAllowedBeforeBreaking and DurationOfBreak options.
|
||||||
@ -44,5 +42,4 @@ You can set the TimeoutValue in isolation of the ExceptionsAllowedBeforeBreaking
|
|||||||
|
|
||||||
There is no point setting the other two in isolation as they affect each other :)
|
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
|
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.
|
||||||
on all downstream requests. If someone needs this to be configurable open an issue.
|
|
||||||
|
@ -1,49 +1,49 @@
|
|||||||
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
Ocelot has recently integrated `Rafty <https://github.com/ThreeMammals/Rafty>`_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK.
|
Ocelot has recently integrated `Rafty <https://github.com/ThreeMammals/Rafty>`_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK.
|
||||||
|
|
||||||
Raft is a distributed concensus algorithm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server).
|
Raft is a distributed concensus algorithm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server).
|
||||||
|
|
||||||
To get Raft support you must first install the Ocelot Rafty package.
|
To get Raft support you must first install the Ocelot Rafty package.
|
||||||
|
|
||||||
``Install-Package Ocelot.Provider.Rafty``
|
``Install-Package Ocelot.Provider.Rafty``
|
||||||
|
|
||||||
Then you must make the following changes to your Startup.cs / Program.cs.
|
Then you must make the following changes to your Startup.cs / Program.cs.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
public virtual void ConfigureServices(IServiceCollection services)
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddAdministration("/administration", "secret")
|
.AddAdministration("/administration", "secret")
|
||||||
.AddRafty();
|
.AddRafty();
|
||||||
}
|
}
|
||||||
|
|
||||||
In addition to this you must add a file called peers.json to your main project and it will look as follows
|
In addition to this you must add a file called peers.json to your main project and it will look as follows
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"Peers": [{
|
"Peers": [{
|
||||||
"HostAndPort": "http://localhost:5000"
|
"HostAndPort": "http://localhost:5000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"HostAndPort": "http://localhost:5002"
|
"HostAndPort": "http://localhost:5002"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"HostAndPort": "http://localhost:5003"
|
"HostAndPort": "http://localhost:5003"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"HostAndPort": "http://localhost:5004"
|
"HostAndPort": "http://localhost:5004"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"HostAndPort": "http://localhost:5001"
|
"HostAndPort": "http://localhost:5001"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty.
|
Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty.
|
||||||
|
|
||||||
Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration.
|
Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration.
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
Rate Limiting
|
Rate Limiting
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
|
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
|
||||||
|
|
||||||
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
|
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
|
||||||
|
|
||||||
OK so to get rate limiting working for a ReRoute you need to add the following json to it.
|
OK so to get rate limiting working for a ReRoute you need to add the following json to it.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RateLimitOptions": {
|
"RateLimitOptions": {
|
||||||
"ClientWhitelist": [],
|
"ClientWhitelist": [],
|
||||||
"EnableRateLimiting": true,
|
"EnableRateLimiting": true,
|
||||||
"Period": "1s",
|
"Period": "1s",
|
||||||
"PeriodTimespan": 1,
|
"PeriodTimespan": 1,
|
||||||
"Limit": 1
|
"Limit": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
|
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
|
||||||
|
|
||||||
EnableRateLimiting - This value specifies enable endpoint rate limiting.
|
EnableRateLimiting - This value specifies enable endpoint rate limiting.
|
||||||
|
|
||||||
Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request.
|
Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request.
|
||||||
|
|
||||||
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 ocelot.json
|
You can also set the following in the GlobalConfiguration part of ocelot.json
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RateLimitOptions": {
|
"RateLimitOptions": {
|
||||||
"DisableRateLimitHeaders": false,
|
"DisableRateLimitHeaders": false,
|
||||||
"QuotaExceededMessage": "Customize Tips!",
|
"QuotaExceededMessage": "Customize Tips!",
|
||||||
"HttpStatusCode": 999,
|
"HttpStatusCode": 999,
|
||||||
"ClientIdHeader" : "Test"
|
"ClientIdHeader" : "Test"
|
||||||
}
|
}
|
||||||
|
|
||||||
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Retry-After headers are disabled.
|
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Retry-After headers are disabled.
|
||||||
|
|
||||||
QuotaExceededMessage - This value specifies the exceeded message.
|
QuotaExceededMessage - This value specifies the exceeded message.
|
||||||
|
|
||||||
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
|
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
|
||||||
|
|
||||||
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"
|
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"
|
||||||
|
@ -1,187 +1,185 @@
|
|||||||
Request Aggregation
|
Request Aggregation
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usually where you have
|
Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usually where you have
|
||||||
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/ThreeMammals/Ocelot/pull/79>`_ and further improvements were made as part of `Issue 298 <https://github.com/ThreeMammals/Ocelot/issue/298>`_.
|
This feature was requested as part of `Issue 79 <https://github.com/ThreeMammals/Ocelot/pull/79>`_ and further improvements were made as part of `Issue 298 <https://github.com/ThreeMammals/Ocelot/issue/298>`_.
|
||||||
|
|
||||||
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.
|
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
|
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
|
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.
|
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.
|
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
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"ReRoutes": [
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/",
|
"DownstreamPathTemplate": "/",
|
||||||
"UpstreamPathTemplate": "/laura",
|
"UpstreamPathTemplate": "/laura",
|
||||||
"UpstreamHttpMethod": [
|
"UpstreamHttpMethod": [
|
||||||
"Get"
|
"Get"
|
||||||
],
|
],
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 51881
|
"Port": 51881
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Key": "Laura"
|
"Key": "Laura"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/",
|
"DownstreamPathTemplate": "/",
|
||||||
"UpstreamPathTemplate": "/tom",
|
"UpstreamPathTemplate": "/tom",
|
||||||
"UpstreamHttpMethod": [
|
"UpstreamHttpMethod": [
|
||||||
"Get"
|
"Get"
|
||||||
],
|
],
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 51882
|
"Port": 51882
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Key": "Tom"
|
"Key": "Tom"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Aggregates": [
|
"Aggregates": [
|
||||||
{
|
{
|
||||||
"ReRouteKeys": [
|
"ReRouteKeys": [
|
||||||
"Tom",
|
"Tom",
|
||||||
"Laura"
|
"Laura"
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/",
|
"UpstreamPathTemplate": "/",
|
||||||
"Aggregator": "FakeDefinedAggregator"
|
"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.
|
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.
|
In order to make the aggregator available we must add the FakeDefinedAggregator to the OcelotBuilder like below.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddSingletonDefinedAggregator<FakeDefinedAggregator>();
|
.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.
|
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.
|
Because the FakeDefinedAggregator is registered in the container you can add any dependencies it needs into the container like below.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services.AddSingleton<FooDependency>();
|
services.AddSingleton<FooDependency>();
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddSingletonDefinedAggregator<FooAggregator>();
|
.AddSingletonDefinedAggregator<FooAggregator>();
|
||||||
|
|
||||||
In this example FooAggregator takes a dependency on FooDependency and it will be resolved by the container.
|
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.
|
In addition to this Ocelot lets you add transient aggregators like below.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddTransientDefinedAggregator<FakeDefinedAggregator>();
|
.AddTransientDefinedAggregator<FakeDefinedAggregator>();
|
||||||
|
|
||||||
In order to make an Aggregator you must implement this interface.
|
In order to make an Aggregator you must implement this interface.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
public interface IDefinedAggregator
|
public interface IDefinedAggregator
|
||||||
{
|
{
|
||||||
Task<DownstreamResponse> Aggregate(List<DownstreamResponse> responses);
|
Task<DownstreamResponse> Aggregate(List<HttpContext> 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
|
With this feature you can pretty much do whatever you want because the HttpContext objects contain the results of all the aggregate requests. Please note if the HttpClient throws an exception when making a request to a ReRoute in the aggregate then you will not get a HttpContext for it but you would for any that succeed. If it does throw an exception this will be logged.
|
||||||
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
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Basic expecting JSON from Downstream Services
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
"ReRoutes": [
|
"DownstreamPathTemplate": "/",
|
||||||
{
|
"UpstreamPathTemplate": "/laura",
|
||||||
"DownstreamPathTemplate": "/",
|
"UpstreamHttpMethod": [
|
||||||
"UpstreamPathTemplate": "/laura",
|
"Get"
|
||||||
"UpstreamHttpMethod": [
|
],
|
||||||
"Get"
|
"DownstreamScheme": "http",
|
||||||
],
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamScheme": "http",
|
{
|
||||||
"DownstreamHostAndPorts": [
|
"Host": "localhost",
|
||||||
{
|
"Port": 51881
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 51881
|
],
|
||||||
}
|
"Key": "Laura"
|
||||||
],
|
},
|
||||||
"Key": "Laura"
|
{
|
||||||
},
|
"DownstreamPathTemplate": "/",
|
||||||
{
|
"UpstreamPathTemplate": "/tom",
|
||||||
"DownstreamPathTemplate": "/",
|
"UpstreamHttpMethod": [
|
||||||
"UpstreamPathTemplate": "/tom",
|
"Get"
|
||||||
"UpstreamHttpMethod": [
|
],
|
||||||
"Get"
|
"DownstreamScheme": "http",
|
||||||
],
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamScheme": "http",
|
{
|
||||||
"DownstreamHostAndPorts": [
|
"Host": "localhost",
|
||||||
{
|
"Port": 51882
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 51882
|
],
|
||||||
}
|
"Key": "Tom"
|
||||||
],
|
}
|
||||||
"Key": "Tom"
|
],
|
||||||
}
|
"Aggregates": [
|
||||||
],
|
{
|
||||||
"Aggregates": [
|
"ReRouteKeys": [
|
||||||
{
|
"Tom",
|
||||||
"ReRouteKeys": [
|
"Laura"
|
||||||
"Tom",
|
],
|
||||||
"Laura"
|
"UpstreamPathTemplate": "/"
|
||||||
],
|
}
|
||||||
"UpstreamPathTemplate": "/"
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
You can also set UpstreamHost and ReRouteIsCaseSensitive in the Aggregate configuration. These behave the same as any other ReRoutes.
|
||||||
|
|
||||||
You can also set UpstreamHost and ReRouteIsCaseSensitive in the Aggregate configuration. These behave the same as any other ReRoutes.
|
If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 25} the the response after aggregation would be as follows.
|
||||||
|
|
||||||
If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 25} the the response after aggregation would be as follows.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{"Tom":{"Age": 19},"Laura":{"Age": 25}}
|
||||||
|
|
||||||
{"Tom":{"Age": 19},"Laura":{"Age": 25}}
|
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
|
||||||
At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary
|
JSON without any pretty spaces etc.
|
||||||
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.
|
All headers will be lost from the downstream services response.
|
||||||
|
|
||||||
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.
|
||||||
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
|
||||||
|
-----------------------
|
||||||
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.
|
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
Request Id / Correlation Id
|
Request Id / Correlation Id
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
Ocelot supports a client sending a request id in the form of a header. If set Ocelot will
|
Ocelot supports a client sending a request id in the form of a header. If set Ocelot willuse the requestid for logging as soon as it becomes available in the middleware pipeline.
|
||||||
use the requestid for logging as soon as it becomes available in the middleware pipeline.
|
|
||||||
Ocelot will also forward the request id with the specified header to the downstream service.
|
Ocelot will also forward the request id with the specified header to the downstream service.
|
||||||
|
|
||||||
You can still get the asp.net core request id in the logs if you set
|
You can still get the asp.net core request id in the logs if you set IncludeScopes true in your logging config.
|
||||||
IncludeScopes true in your logging config.
|
|
||||||
|
|
||||||
In order to use the request id feature you have two options.
|
In order to use the request id feature you have two options.
|
||||||
|
|
||||||
|
@ -1,245 +1,238 @@
|
|||||||
Routing
|
Routing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Ocelot's primary functionality is to take incoming http requests and forward them on
|
Ocelot's primary functionality is to take incoming http requests and forward them on to a downstream service. Ocelot currently only supports this in the form of another http request (in the future
|
||||||
to a downstream service. Ocelot currently only supports this in the form of another http request (in the future
|
this could be any transport mechanism).
|
||||||
this could be any transport mechanism).
|
|
||||||
|
Ocelot's describes the routing of one request to another as a ReRoute. In order to get anything working in Ocelot you need to set up a ReRoute in the configuration.
|
||||||
Ocelot's describes the routing of one request to another as a ReRoute. In order to get
|
|
||||||
anything working in Ocelot you need to set up a ReRoute in the configuration.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"ReRoutes": [
|
||||||
{
|
]
|
||||||
"ReRoutes": [
|
}
|
||||||
]
|
|
||||||
}
|
To configure a ReRoute you need to add one to the ReRoutes json array.
|
||||||
|
|
||||||
To configure a ReRoute you need to add one to the ReRoutes json array.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
{
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamScheme": "https",
|
{
|
||||||
"DownstreamHostAndPorts": [
|
"Host": "localhost",
|
||||||
{
|
"Port": 80,
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 80,
|
],
|
||||||
}
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
],
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
}
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
|
||||||
}
|
The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to.
|
||||||
|
|
||||||
The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to.
|
DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to. Usually this will just contain a single entry but sometimes you might want to load balance requests to your downstream services and Ocelot allows you add more than one entry and then select a load balancer.
|
||||||
|
|
||||||
DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to.
|
The UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request. The UpstreamHttpMethod is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. You can set a specific list of HTTP Methods or set an empty list to allow any of them.
|
||||||
Usually this will just contain a single entry but sometimes you might want to load balance requests to your downstream services and Ocelot allows you add more than one entry and then select a load balancer.
|
|
||||||
|
In Ocelot you can add placeholders for variables to your Templates in the form of {something}. The placeholder variable needs to be present in both the DownstreamPathTemplate and UpstreamPathTemplate properties. When it is Ocelot will attempt to substitute the value in the UpstreamPathTemplate placeholder into the DownstreamPathTemplate for each request Ocelot processes.
|
||||||
The UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request.
|
|
||||||
The UpstreamHttpMethod is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. You can set a specific list of HTTP Methods or set an empty list to allow any of them.
|
You can also do a catch all type of ReRoute e.g.
|
||||||
|
|
||||||
In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
|
.. code-block:: json
|
||||||
The placeholder variable needs to be present in both the DownstreamPathTemplate and UpstreamPathTemplate properties. When it is Ocelot will attempt to substitute the value in the UpstreamPathTemplate placeholder into the DownstreamPathTemplate for each request Ocelot processes.
|
|
||||||
|
{
|
||||||
You can also do a catch all type of ReRoute e.g.
|
"DownstreamPathTemplate": "/api/{everything}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
.. code-block:: json
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
{
|
"Host": "localhost",
|
||||||
"DownstreamPathTemplate": "/api/{everything}",
|
"Port": 80,
|
||||||
"DownstreamScheme": "https",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"UpstreamPathTemplate": "/{everything}",
|
||||||
"Host": "localhost",
|
"UpstreamHttpMethod": [ "Get", "Post" ]
|
||||||
"Port": 80,
|
}
|
||||||
}
|
|
||||||
],
|
This will forward any path + query string combinations to the downstream service after the path /api.
|
||||||
"UpstreamPathTemplate": "/{everything}",
|
|
||||||
"UpstreamHttpMethod": [ "Get", "Post" ]
|
|
||||||
}
|
The default ReRouting configuration is case insensitive!
|
||||||
|
|
||||||
This will forward any path + query string combinations to the downstream service after the path /api.
|
In order to change this you can specify on a per ReRoute basis the following setting.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
The default ReRouting configuration is case insensitive!
|
|
||||||
|
"ReRouteIsCaseSensitive": true
|
||||||
In order to change this you can specify on a per ReRoute basis the following setting.
|
|
||||||
|
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
||||||
.. code-block:: json
|
evaluation will be case sensitive.
|
||||||
|
|
||||||
"ReRouteIsCaseSensitive": true
|
Catch All
|
||||||
|
^^^^^^^^^
|
||||||
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
|
||||||
evaluation will be case sensitive.
|
Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic.
|
||||||
|
|
||||||
Catch All
|
If you set up your config like below, all requests will be proxied straight through. The placeholder {url} name is not significant, any name will work.
|
||||||
^^^^^^^^^
|
|
||||||
|
.. code-block:: json
|
||||||
Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic.
|
|
||||||
|
{
|
||||||
If you set up your config like below, all requests will be proxied straight through. The placeholder {url} name is not significant, any name will work.
|
"DownstreamPathTemplate": "/{url}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
.. code-block:: json
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
{
|
"Host": "localhost",
|
||||||
"DownstreamPathTemplate": "/{url}",
|
"Port": 80,
|
||||||
"DownstreamScheme": "https",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"UpstreamPathTemplate": "/{url}",
|
||||||
"Host": "localhost",
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
"Port": 80,
|
}
|
||||||
}
|
|
||||||
],
|
The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.
|
||||||
"UpstreamPathTemplate": "/{url}",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
.. code-block:: json
|
||||||
}
|
|
||||||
|
{
|
||||||
The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.
|
"DownstreamPathTemplate": "/",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
.. code-block:: json
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
{
|
"Host": "10.0.10.1",
|
||||||
"DownstreamPathTemplate": "/",
|
"Port": 80,
|
||||||
"DownstreamScheme": "https",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"UpstreamPathTemplate": "/",
|
||||||
"Host": "10.0.10.1",
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
"Port": 80,
|
}
|
||||||
}
|
|
||||||
],
|
Upstream Host
|
||||||
"UpstreamPathTemplate": "/",
|
^^^^^^^^^^^^^
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
|
||||||
}
|
This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute.
|
||||||
|
|
||||||
Upstream Host
|
In order to use this feature please add the following to your config.
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
.. code-block:: json
|
||||||
This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute.
|
|
||||||
|
{
|
||||||
In order to use this feature please add the following to your config.
|
"DownstreamPathTemplate": "/",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
.. code-block:: json
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
{
|
"Host": "10.0.10.1",
|
||||||
"DownstreamPathTemplate": "/",
|
"Port": 80,
|
||||||
"DownstreamScheme": "https",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"UpstreamPathTemplate": "/",
|
||||||
"Host": "10.0.10.1",
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
"Port": 80,
|
"UpstreamHost": "somedomain.com"
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/",
|
The ReRoute above will only be matched when the host header value is somedomain.com.
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"UpstreamHost": "somedomain.com"
|
If you do not set UpstreamHost on a ReRoute then any host header will match it. 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/ThreeMammals/Ocelot/pull/216>`_ .
|
||||||
The ReRoute above will only be matched when the host header value is somedomain.com.
|
|
||||||
|
Priority
|
||||||
If you do not set UpstreamHost on a ReRoute then any host header will match it. 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/ThreeMammals/Ocelot/pull/216>`_ .
|
You can define the order you want your ReRoutes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json
|
||||||
|
See `Issue 270 <https://github.com/ThreeMammals/Ocelot/pull/270>`_ for reference
|
||||||
Priority
|
|
||||||
^^^^^^^^
|
.. code-block:: json
|
||||||
|
|
||||||
You can define the order you want your ReRoutes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json
|
{
|
||||||
See `Issue 270 <https://github.com/ThreeMammals/Ocelot/pull/270>`_ for reference
|
"Priority": 0
|
||||||
|
}
|
||||||
.. code-block:: json
|
|
||||||
|
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.
|
||||||
{
|
|
||||||
"Priority": 0
|
e.g. you could have
|
||||||
}
|
|
||||||
|
.. code-block:: json
|
||||||
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.
|
{
|
||||||
|
"UpstreamPathTemplate": "/goods/{catchAll}"
|
||||||
e.g. you could have
|
"Priority": 0
|
||||||
|
}
|
||||||
.. code-block:: json
|
|
||||||
|
and
|
||||||
{
|
|
||||||
"UpstreamPathTemplate": "/goods/{catchAll}"
|
.. code-block:: json
|
||||||
"Priority": 0
|
|
||||||
}
|
{
|
||||||
|
"UpstreamPathTemplate": "/goods/delete"
|
||||||
and
|
"Priority": 1
|
||||||
|
}
|
||||||
.. code-block:: json
|
|
||||||
|
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!).
|
||||||
{
|
|
||||||
"UpstreamPathTemplate": "/goods/delete"
|
Dynamic Routing
|
||||||
"Priority": 1
|
^^^^^^^^^^^^^^^
|
||||||
}
|
|
||||||
|
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_.
|
||||||
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!).
|
The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
|
||||||
|
this sounds interesting to you.
|
||||||
Dynamic Routing
|
|
||||||
^^^^^^^^^^^^^^^
|
Query Strings
|
||||||
|
^^^^^^^^^^^^^
|
||||||
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_.
|
|
||||||
|
Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below.
|
||||||
The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
|
|
||||||
this sounds interesting to you.
|
.. code-block:: json
|
||||||
|
|
||||||
Query Strings
|
{
|
||||||
^^^^^^^^^^^^^
|
"ReRoutes": [
|
||||||
|
{
|
||||||
Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below.
|
"DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
|
||||||
|
"UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
|
||||||
.. code-block:: json
|
"UpstreamHttpMethod": [
|
||||||
|
"Get"
|
||||||
{
|
],
|
||||||
"ReRoutes": [
|
"DownstreamScheme": "http",
|
||||||
{
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
|
{
|
||||||
"UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
|
"Host": "localhost",
|
||||||
"UpstreamHttpMethod": [
|
"Port": 50110
|
||||||
"Get"
|
}
|
||||||
],
|
]
|
||||||
"DownstreamScheme": "http",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"GlobalConfiguration": {
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 50110
|
}
|
||||||
}
|
|
||||||
]
|
In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId!
|
||||||
}
|
|
||||||
],
|
Ocelot will also allow you to put query string parameters in the UpstreamPathTemplate so you can match certain queries to certain services.
|
||||||
"GlobalConfiguration": {
|
|
||||||
}
|
.. code-block:: json
|
||||||
}
|
|
||||||
|
{
|
||||||
In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId!
|
"ReRoutes": [
|
||||||
|
{
|
||||||
Ocelot will also allow you to put query string parameters in the UpstreamPathTemplate so you can match certain queries to certain services.
|
"DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
|
||||||
|
"UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
|
||||||
.. code-block:: json
|
"UpstreamHttpMethod": [
|
||||||
|
"Get"
|
||||||
{
|
],
|
||||||
"ReRoutes": [
|
"DownstreamScheme": "http",
|
||||||
{
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
|
{
|
||||||
"UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
|
"Host": "localhost",
|
||||||
"UpstreamHttpMethod": [
|
"Port": 50110
|
||||||
"Get"
|
}
|
||||||
],
|
]
|
||||||
"DownstreamScheme": "http",
|
}
|
||||||
"DownstreamHostAndPorts": [
|
],
|
||||||
{
|
"GlobalConfiguration": {
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 50110
|
}
|
||||||
}
|
|
||||||
]
|
In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this
|
||||||
}
|
but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path.
|
||||||
],
|
|
||||||
"GlobalConfiguration": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this
|
|
||||||
but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path.
|
|
||||||
|
@ -1,255 +1,244 @@
|
|||||||
.. service-discovery:
|
.. service-discovery:
|
||||||
|
|
||||||
Service Discovery
|
Service Discovery
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Ocelot allows you to specify a service discovery provider and will use this to find the host and port
|
Ocelot allows you to specify a service discovery provider and will use this to find the host and port for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the
|
||||||
for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the
|
GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes you specify a ServiceName for at ReRoute level.
|
||||||
GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes
|
|
||||||
you specify a ServiceName for at ReRoute level.
|
Consul
|
||||||
|
^^^^^^
|
||||||
Consul
|
|
||||||
^^^^^^
|
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
|
||||||
|
|
||||||
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
|
``Install-Package Ocelot.Provider.Consul``
|
||||||
|
|
||||||
``Install-Package Ocelot.Provider.Consul``
|
Then add the following to your ConfigureServices method.
|
||||||
|
|
||||||
Then add the following to your ConfigureServices method.
|
.. code-block:: csharp
|
||||||
|
|
||||||
.. code-block:: csharp
|
s.AddOcelot()
|
||||||
|
.AddConsul();
|
||||||
s.AddOcelot()
|
|
||||||
.AddConsul();
|
The following is required in the GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default
|
||||||
|
will be used.
|
||||||
The following is required in the GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default
|
|
||||||
will be used.
|
Please note the Scheme option defauls to HTTP. It was added in this `PR <https://github.com/ThreeMammals/Ocelot/pull/1154>`_. It defaults to HTTP to not introduce a breaking change.
|
||||||
|
|
||||||
Please note the Scheme option defauls to HTTP. It was added in this `PR <https://github.com/ThreeMammals/Ocelot/pull/1154>`_. It defaults to HTTP to not introduce a breaking change.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Scheme": "https",
|
||||||
"ServiceDiscoveryProvider": {
|
"Host": "localhost",
|
||||||
"Scheme": "https",
|
"Port": 8500,
|
||||||
"Host": "localhost",
|
"Type": "Consul"
|
||||||
"Port": 8500,
|
}
|
||||||
"Type": "Consul"
|
|
||||||
}
|
In the future we can add a feature that allows ReRoute specfic configuration.
|
||||||
|
|
||||||
In the future we can add a feature that allows ReRoute specfic configuration.
|
In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests.
|
||||||
|
|
||||||
In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the
|
.. code-block:: json
|
||||||
ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin
|
|
||||||
and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests.
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
.. code-block:: json
|
"DownstreamScheme": "https",
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
{
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"ServiceName": "product",
|
||||||
"DownstreamScheme": "https",
|
"LoadBalancerOptions": {
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"Type": "LeastConnection"
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
},
|
||||||
"ServiceName": "product",
|
}
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "LeastConnection"
|
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.
|
||||||
},
|
|
||||||
}
|
A lot of people have asked me to implement a feature where Ocelot polls Consul for latest service information rather than per request. If you want to poll Consul for the latest services rather than per request (default behaviour) then you need to set the following configuration.
|
||||||
|
|
||||||
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.
|
.. code-block:: json
|
||||||
|
|
||||||
A lot of people have asked me to implement a feature where Ocelot polls Consul for latest service information rather than per request. If you want to poll Consul for the latest services rather than per request (default behaviour) then you need to set the following configuration.
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Host": "localhost",
|
||||||
.. code-block:: json
|
"Port": 8500,
|
||||||
|
"Type": "PollConsul",
|
||||||
"ServiceDiscoveryProvider": {
|
"PollingInterval": 100
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 8500,
|
|
||||||
"Type": "PollConsul",
|
The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration.
|
||||||
"PollingInterval": 100
|
|
||||||
}
|
Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling Consul per request (as sidecar agent). If you are calling a remote Consul agent then polling will be a good performance improvement.
|
||||||
|
|
||||||
The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration.
|
Your services need to be added to Consul something like below (C# style but hopefully this make sense)...The only important thing to note is not to add http or https to the Address field. I have been contacted before about not accepting scheme in Address and accepting scheme in address. After reading `this <https://www.consul.io/docs/agent/services.html>`_ I don't think the scheme should be in there.
|
||||||
|
|
||||||
Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling Consul per request (as sidecar agent). If you are calling a remote Consul agent then polling will be a good performance improvement.
|
.. code-block: csharp
|
||||||
|
|
||||||
Your services need to be added to Consul something like below (C# style but hopefully this make sense)...The only important thing to note
|
new AgentService()
|
||||||
is not to add http or https to the Address field. I have been contacted before about not accepting scheme in Address and accepting scheme
|
{
|
||||||
in address. After reading `this <https://www.consul.io/docs/agent/services.html>`_ I don't think the scheme should be in there.
|
Service = "some-service-name",
|
||||||
|
Address = "localhost",
|
||||||
.. code-block: csharp
|
Port = 8080,
|
||||||
|
ID = "some-id",
|
||||||
new AgentService()
|
}
|
||||||
{
|
|
||||||
Service = "some-service-name",
|
Or
|
||||||
Address = "localhost",
|
|
||||||
Port = 8080,
|
.. code-block:: json
|
||||||
ID = "some-id",
|
|
||||||
}
|
"Service": {
|
||||||
|
"ID": "some-id",
|
||||||
Or
|
"Service": "some-service-name",
|
||||||
|
"Address": "localhost",
|
||||||
.. code-block:: json
|
"Port": 8080
|
||||||
|
}
|
||||||
"Service": {
|
|
||||||
"ID": "some-id",
|
ACL Token
|
||||||
"Service": "some-service-name",
|
---------
|
||||||
"Address": "localhost",
|
|
||||||
"Port": 8080
|
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
|
||||||
ACL Token
|
|
||||||
---------
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Host": "localhost",
|
||||||
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.
|
"Port": 8500,
|
||||||
|
"Token": "footoken",
|
||||||
.. code-block:: json
|
"Type": "Consul"
|
||||||
|
}
|
||||||
"ServiceDiscoveryProvider": {
|
|
||||||
"Host": "localhost",
|
Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request.
|
||||||
"Port": 8500,
|
|
||||||
"Token": "footoken",
|
Eureka
|
||||||
"Type": "Consul"
|
^^^^^^
|
||||||
}
|
|
||||||
|
This feature was requested as part of `Issue 262 <https://github.com/ThreeMammals/Ocelot/issues/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.
|
||||||
Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request.
|
|
||||||
|
The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot.
|
||||||
Eureka
|
|
||||||
^^^^^^
|
``Install-Package Ocelot.Provider.Eureka``
|
||||||
|
|
||||||
This feature was requested as part of `Issue 262 <https://github.com/ThreeMammals/Ocelot/issues/262>`_ . to add support for Netflix's
|
Then add the following to your ConfigureServices method.
|
||||||
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.
|
.. code-block:: csharp
|
||||||
|
|
||||||
The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot.
|
s.AddOcelot()
|
||||||
|
.AddEureka();
|
||||||
``Install-Package Ocelot.Provider.Eureka``
|
|
||||||
|
Then in order to get this working add the following to ocelot.json..
|
||||||
Then add the following to your ConfigureServices method.
|
|
||||||
|
.. code-block:: json
|
||||||
.. code-block:: csharp
|
|
||||||
|
"ServiceDiscoveryProvider": {
|
||||||
s.AddOcelot()
|
"Type": "Eureka"
|
||||||
.AddEureka();
|
}
|
||||||
|
|
||||||
Then in order to get this working add the following to ocelot.json..
|
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
|
.. code-block:: json
|
||||||
|
|
||||||
"ServiceDiscoveryProvider": {
|
"eureka": {
|
||||||
"Type": "Eureka"
|
"client": {
|
||||||
}
|
"serviceUrl": "http://localhost:8761/eureka/",
|
||||||
|
"shouldRegisterWithEureka": false,
|
||||||
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.
|
"shouldFetchRegistry": true
|
||||||
|
}
|
||||||
.. code-block:: json
|
}
|
||||||
|
|
||||||
"eureka": {
|
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.
|
||||||
"client": {
|
|
||||||
"serviceUrl": "http://localhost:8761/eureka/",
|
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.
|
||||||
"shouldRegisterWithEureka": false,
|
|
||||||
"shouldFetchRegistry": true
|
Ocelot will use the scheme (http/https) set in Eureka if these values are not provided in ocelot.json
|
||||||
}
|
|
||||||
}
|
Dynamic Routing
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
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.
|
|
||||||
|
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
|
||||||
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.
|
An example of this would be calling Ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
|
||||||
When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code
|
the path which is product and use it as a key to look up the service in Consul. If Consul returns a service Ocelot will request it on whatever host and port comes back from Consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal.
|
||||||
is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work.
|
|
||||||
|
In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
|
||||||
Ocelot will use the scheme (http/https) set in Eureka if these values are not provided in ocelot.json
|
|
||||||
|
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic ReRoutes.
|
||||||
Dynamic Routing
|
|
||||||
^^^^^^^^^^^^^^^
|
The config might look something like
|
||||||
|
|
||||||
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
|
.. code-block:: json
|
||||||
|
|
||||||
An example of this would be calling Ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
|
{
|
||||||
the path which is product and use it as a key to look up the service in Consul. If Consul returns a service Ocelot will request it on whatever host and port comes back from Consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal.
|
"ReRoutes": [],
|
||||||
|
"Aggregates": [],
|
||||||
In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
|
"GlobalConfiguration": {
|
||||||
|
"RequestIdKey": null,
|
||||||
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic ReRoutes.
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Host": "localhost",
|
||||||
The config might look something like
|
"Port": 8500,
|
||||||
|
"Type": "Consul",
|
||||||
.. code-block:: json
|
"Token": null,
|
||||||
|
"ConfigurationKey": null
|
||||||
{
|
},
|
||||||
"ReRoutes": [],
|
"RateLimitOptions": {
|
||||||
"Aggregates": [],
|
"ClientIdHeader": "ClientId",
|
||||||
"GlobalConfiguration": {
|
"QuotaExceededMessage": null,
|
||||||
"RequestIdKey": null,
|
"RateLimitCounterPrefix": "ocelot",
|
||||||
"ServiceDiscoveryProvider": {
|
"DisableRateLimitHeaders": false,
|
||||||
"Host": "localhost",
|
"HttpStatusCode": 429
|
||||||
"Port": 8500,
|
},
|
||||||
"Type": "Consul",
|
"QoSOptions": {
|
||||||
"Token": null,
|
"ExceptionsAllowedBeforeBreaking": 0,
|
||||||
"ConfigurationKey": null
|
"DurationOfBreak": 0,
|
||||||
},
|
"TimeoutValue": 0
|
||||||
"RateLimitOptions": {
|
},
|
||||||
"ClientIdHeader": "ClientId",
|
"BaseUrl": null,
|
||||||
"QuotaExceededMessage": null,
|
"LoadBalancerOptions": {
|
||||||
"RateLimitCounterPrefix": "ocelot",
|
"Type": "LeastConnection",
|
||||||
"DisableRateLimitHeaders": false,
|
"Key": null,
|
||||||
"HttpStatusCode": 429
|
"Expiry": 0
|
||||||
},
|
},
|
||||||
"QoSOptions": {
|
"DownstreamScheme": "http",
|
||||||
"ExceptionsAllowedBeforeBreaking": 0,
|
"HttpHandlerOptions": {
|
||||||
"DurationOfBreak": 0,
|
"AllowAutoRedirect": false,
|
||||||
"TimeoutValue": 0
|
"UseCookieContainer": false,
|
||||||
},
|
"UseTracing": false
|
||||||
"BaseUrl": null,
|
}
|
||||||
"LoadBalancerOptions": {
|
}
|
||||||
"Type": "LeastConnection",
|
}
|
||||||
"Key": null,
|
|
||||||
"Expiry": 0
|
Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows.
|
||||||
},
|
|
||||||
"DownstreamScheme": "http",
|
.. code-block:: json
|
||||||
"HttpHandlerOptions": {
|
|
||||||
"AllowAutoRedirect": false,
|
{
|
||||||
"UseCookieContainer": false,
|
"DynamicReRoutes": [
|
||||||
"UseTracing": false
|
{
|
||||||
}
|
"ServiceName": "product",
|
||||||
}
|
"RateLimitRule": {
|
||||||
}
|
"ClientWhitelist": [],
|
||||||
|
"EnableRateLimiting": true,
|
||||||
Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows.
|
"Period": "1s",
|
||||||
|
"PeriodTimespan": 1000.0,
|
||||||
.. code-block:: json
|
"Limit": 3
|
||||||
|
}
|
||||||
{
|
}
|
||||||
"DynamicReRoutes": [
|
],
|
||||||
{
|
"GlobalConfiguration": {
|
||||||
"ServiceName": "product",
|
"RequestIdKey": null,
|
||||||
"RateLimitRule": {
|
"ServiceDiscoveryProvider": {
|
||||||
"ClientWhitelist": [],
|
"Host": "localhost",
|
||||||
"EnableRateLimiting": true,
|
"Port": 8523,
|
||||||
"Period": "1s",
|
"Type": "Consul"
|
||||||
"PeriodTimespan": 1000.0,
|
},
|
||||||
"Limit": 3
|
"RateLimitOptions": {
|
||||||
}
|
"ClientIdHeader": "ClientId",
|
||||||
}
|
"QuotaExceededMessage": "",
|
||||||
],
|
"RateLimitCounterPrefix": "",
|
||||||
"GlobalConfiguration": {
|
"DisableRateLimitHeaders": false,
|
||||||
"RequestIdKey": null,
|
"HttpStatusCode": 428
|
||||||
"ServiceDiscoveryProvider": {
|
}
|
||||||
"Host": "localhost",
|
"DownstreamScheme": "http",
|
||||||
"Port": 8523,
|
}
|
||||||
"Type": "Consul"
|
}
|
||||||
},
|
|
||||||
"RateLimitOptions": {
|
This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicReRoutes section.
|
||||||
"ClientIdHeader": "ClientId",
|
|
||||||
"QuotaExceededMessage": "",
|
Please take a look through all of the docs to understand these options.
|
||||||
"RateLimitCounterPrefix": "",
|
|
||||||
"DisableRateLimitHeaders": false,
|
|
||||||
"HttpStatusCode": 428
|
|
||||||
}
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicReRoutes section.
|
|
||||||
|
|
||||||
Please take a look through all of the docs to understand these options.
|
|
||||||
|
@ -1,42 +1,40 @@
|
|||||||
Service Fabric
|
Service Fabric
|
||||||
==============
|
==============
|
||||||
|
|
||||||
If you have services deployed in Service Fabric you will normally use the naming service to access them.
|
If you have services deployed in Service Fabric you will normally use the naming service to access them.
|
||||||
|
|
||||||
The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the
|
The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081.
|
||||||
Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in
|
|
||||||
GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081.
|
The example below is taken from the samples folder so please check it if this doesnt make sense!
|
||||||
|
|
||||||
The example below is taken from the samples folder so please check it if this doesnt make sense!
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
"ReRoutes": [
|
"DownstreamPathTemplate": "/api/values",
|
||||||
{
|
"UpstreamPathTemplate": "/EquipmentInterfaces",
|
||||||
"DownstreamPathTemplate": "/api/values",
|
"UpstreamHttpMethod": [
|
||||||
"UpstreamPathTemplate": "/EquipmentInterfaces",
|
"Get"
|
||||||
"UpstreamHttpMethod": [
|
],
|
||||||
"Get"
|
"DownstreamScheme": "http",
|
||||||
],
|
"ServiceName": "OcelotServiceApplication/OcelotApplicationService",
|
||||||
"DownstreamScheme": "http",
|
}
|
||||||
"ServiceName": "OcelotServiceApplication/OcelotApplicationService",
|
],
|
||||||
}
|
"GlobalConfiguration": {
|
||||||
],
|
"RequestIdKey": "OcRequestId",
|
||||||
"GlobalConfiguration": {
|
"ServiceDiscoveryProvider": {
|
||||||
"RequestIdKey": "OcRequestId",
|
"Host": "localhost",
|
||||||
"ServiceDiscoveryProvider": {
|
"Port": 19081,
|
||||||
"Host": "localhost",
|
"Type": "ServiceFabric"
|
||||||
"Port": 19081,
|
}
|
||||||
"Type": "ServiceFabric"
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
If you are using stateless / guest exe services ocelot will be able to proxy through the naming service without anything else. However
|
||||||
|
if you are using statefull / actor services you must send the PartitionKind and PartitionKey query string values with the client
|
||||||
If you are using stateless / guest exe services ocelot will be able to proxy through the naming service without anything else. However
|
request e.g.
|
||||||
if you are using statefull / actor services you must send the PartitionKind and PartitionKey query string values with the client
|
|
||||||
request e.g.
|
GET http://ocelot.com/EquipmentInterfaces?PartitionKind=xxx&PartitionKey=xxx
|
||||||
|
|
||||||
GET http://ocelot.com/EquipmentInterfaces?PartitionKind=xxx&PartitionKey=xxx
|
There is no way for Ocelot to work these out for you.
|
||||||
|
|
||||||
There is no way for Ocelot to work these out for you.
|
|
||||||
|
@ -1,114 +1,109 @@
|
|||||||
Websockets
|
Websockets
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 <https://github.com/ThreeMammals/Ocelot/issues/212>`_.
|
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 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.
|
In your Configure method you need to tell your application to use WebSockets.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
Configure(app =>
|
Configure(app =>
|
||||||
{
|
{
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
app.UseOcelot().Wait();
|
app.UseOcelot().Wait();
|
||||||
})
|
})
|
||||||
|
|
||||||
Then in your ocelot.json add the following to proxy a ReRoute using websockets.
|
Then in your ocelot.json add the following to proxy a ReRoute using websockets.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/ws",
|
"DownstreamPathTemplate": "/ws",
|
||||||
"UpstreamPathTemplate": "/",
|
"UpstreamPathTemplate": "/",
|
||||||
"DownstreamScheme": "ws",
|
"DownstreamScheme": "ws",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 5001
|
"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
|
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.
|
||||||
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.
|
SignalR
|
||||||
|
^^^^^^^
|
||||||
SignalR
|
|
||||||
^^^^^^^
|
Ocelot supports proxying SignalR. This functionality was requested in `Issue 344 <https://github.com/ThreeMammals/Ocelot/issues/344>`_.
|
||||||
|
|
||||||
Ocelot supports proxying SignalR. This functionality was requested in `Issue 344 <https://github.com/ThreeMammals/Ocelot/issues/344>`_.
|
In order to get websocket proxying working with Ocelot you need to do the following.
|
||||||
|
|
||||||
In order to get websocket proxying working with Ocelot you need to do the following.
|
Install Microsoft.AspNetCore.SignalR.Client 1.0.2 you can try other packages but this one is tested.
|
||||||
|
|
||||||
Install Microsoft.AspNetCore.SignalR.Client 1.0.2 you can try other packages but this one is tested.
|
Do not run it in IISExpress or install the websockets feature in the IIS features
|
||||||
|
|
||||||
Do not run it in IISExpress or install the websockets feature in the IIS features
|
In your Configure method you need to tell your application to use SignalR.
|
||||||
|
|
||||||
In your Configure method you need to tell your application to use SignalR.
|
.. code-block:: csharp
|
||||||
|
|
||||||
.. code-block:: csharp
|
Configure(app =>
|
||||||
|
{
|
||||||
Configure(app =>
|
app.UseWebSockets();
|
||||||
{
|
app.UseOcelot().Wait();
|
||||||
app.UseWebSockets();
|
})
|
||||||
app.UseOcelot().Wait();
|
|
||||||
})
|
Then in your ocelot.json add the following to proxy a ReRoute using SignalR. Note normal Ocelot routing rules apply the main thing is the scheme which is set to "ws".
|
||||||
|
|
||||||
Then in your ocelot.json add the following to proxy a ReRoute using SignalR. Note normal Ocelot routing rules apply the main thing is the scheme which is set to "ws".
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
"ReRoutes": [
|
"DownstreamPathTemplate": "/{catchAll}",
|
||||||
{
|
"DownstreamScheme": "ws",
|
||||||
"DownstreamPathTemplate": "/{catchAll}",
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamScheme": "ws",
|
{
|
||||||
"DownstreamHostAndPorts": [
|
"Host": "localhost",
|
||||||
{
|
"Port": 50000
|
||||||
"Host": "localhost",
|
}
|
||||||
"Port": 50000
|
],
|
||||||
}
|
"UpstreamPathTemplate": "/gateway/{catchAll}",
|
||||||
],
|
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ]
|
||||||
"UpstreamPathTemplate": "/gateway/{catchAll}",
|
}
|
||||||
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ]
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
With this configuration set Ocelot will match any SignalR 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.
|
||||||
|
|
||||||
With this configuration set Ocelot will match any SignalR traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer
|
Supported
|
||||||
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.
|
|
||||||
|
1. Load Balancer
|
||||||
Supported
|
2. Routing
|
||||||
^^^^^^^^^
|
3. Service Discovery
|
||||||
|
|
||||||
1. Load Balancer
|
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 :)
|
||||||
2. Routing
|
|
||||||
3. Service Discovery
|
Not Supported
|
||||||
|
^^^^^^^^^^^^^
|
||||||
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 :)
|
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.
|
||||||
|
|
||||||
Not Supported
|
1. Tracing
|
||||||
^^^^^^^^^^^^^
|
2. RequestId
|
||||||
|
3. Request Aggregation
|
||||||
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.
|
4. Rate Limiting
|
||||||
|
5. Quality of Service
|
||||||
1. Tracing
|
6. Middleware Injection
|
||||||
2. RequestId
|
7. Header Transformation
|
||||||
3. Request Aggregation
|
8. Delegating Handlers
|
||||||
4. Rate Limiting
|
9. Claims Transformation
|
||||||
5. Quality of Service
|
10. Caching
|
||||||
6. Middleware Injection
|
11. Authentication - If anyone requests it we might be able to do something with basic authentication.
|
||||||
7. Header Transformation
|
12. Authorisation
|
||||||
8. Delegating Handlers
|
|
||||||
9. Claims Transformation
|
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!
|
||||||
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!
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,23 +1,13 @@
|
|||||||
Big Picture
|
Big Picture
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Ocelot is aimed at people using .NET running
|
Ocelot is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system.
|
||||||
a micro services / service orientated architecture
|
|
||||||
that need a unified point of entry into their system.
|
|
||||||
|
|
||||||
In particular I want easy integration with
|
In particular I want easy integration with IdentityServer reference and bearer tokens.
|
||||||
IdentityServer reference and bearer tokens.
|
|
||||||
|
|
||||||
Ocelot is a bunch of middlewares in a specific order.
|
Ocelot is a bunch of middlewares in a specific order.
|
||||||
|
|
||||||
Ocelot manipulates the HttpRequest object into a state specified by its configuration until
|
Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.
|
||||||
it reaches a request builder middleware where it creates a HttpRequestMessage object which is
|
|
||||||
used to make a request to a downstream service. The middleware that makes the request is
|
|
||||||
the last thing in the Ocelot pipeline. It does not call the next middleware.
|
|
||||||
The response from the downstream service is stored in a per request scoped repository
|
|
||||||
and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware
|
|
||||||
that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client.
|
|
||||||
That is basically it with a bunch of other features.
|
|
||||||
|
|
||||||
The following are configurations that you use when deploying Ocelot.
|
The following are configurations that you use when deploying Ocelot.
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
Contributing
|
Contributing
|
||||||
============
|
============
|
||||||
|
|
||||||
Pull requests, issues and commentary welcome! No special process just create a request and get in
|
Pull requests, issues and commentary welcome! No special process just create a request and get in touch either via gitter or create an issue.
|
||||||
touch either via gitter or create an issue.
|
|
@ -52,16 +52,13 @@ If you want some example that actually does something use the following:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under
|
The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com.
|
||||||
in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com.
|
|
||||||
|
|
||||||
If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543
|
If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543 then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP.
|
||||||
then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP.
|
|
||||||
|
|
||||||
**Program**
|
**Program**
|
||||||
|
|
||||||
Then in your Program.cs you will want to have the following. The main things to note are
|
Then in your Program.cs you will want to have the following. The main things to note are AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot middleware).
|
||||||
AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot middleware).
|
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
@ -116,8 +113,7 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m
|
|||||||
|
|
||||||
**Install NuGet package**
|
**Install NuGet package**
|
||||||
|
|
||||||
Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
|
Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed.
|
||||||
to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed.
|
|
||||||
|
|
||||||
All versions can be found `here <https://www.nuget.org/packages/Ocelot/>`_.
|
All versions can be found `here <https://www.nuget.org/packages/Ocelot/>`_.
|
||||||
|
|
||||||
|
@ -7,10 +7,7 @@ Ocelot does not support...
|
|||||||
|
|
||||||
* Forwarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :(
|
* Forwarding 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 ocelot.json but it doesnt fit into the vision
|
* 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
|
||||||
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
|
||||||
|
|
||||||
@ -28,16 +25,8 @@ 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 ocelot.json.
|
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.
|
||||||
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 their product service
|
In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to their 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.
|
||||||
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
|
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.
|
||||||
even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this.
|
|
||||||
|
11
helpers.txt
Normal file
11
helpers.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
var downstreamReRoute = RequestScopedDataRepository.Get<DownstreamReRoute>("DownstreamReRoute");
|
||||||
|
// todo check downstreamReRoute is ok
|
||||||
|
|
||||||
|
var errors = RequestScopedDataRepository.Get<List<Error>>("Errors");
|
||||||
|
// todo check errors is ok
|
||||||
|
|
||||||
|
var downstreamResponse = RequestScopedDataRepository.Get<DownstreamResponse>("DownstreamResponse");
|
||||||
|
// todo check downstreamResponse is ok
|
||||||
|
|
||||||
|
var context = RequestScopedDataRepository.Get<DownstreamContext>("DownstreamContext").Data;
|
||||||
|
// todo check downstreamcontext is ok
|
@ -1,11 +1,11 @@
|
|||||||
namespace Ocelot.Provider.Consul
|
namespace Ocelot.Provider.Consul
|
||||||
{
|
{
|
||||||
using Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
public class UnableToSetConfigInConsulError : Error
|
public class UnableToSetConfigInConsulError : Error
|
||||||
{
|
{
|
||||||
public UnableToSetConfigInConsulError(string s)
|
public UnableToSetConfigInConsulError(string s)
|
||||||
: base(s, OcelotErrorCode.UnknownError)
|
: base(s, OcelotErrorCode.UnknownError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
namespace Ocelot.Provider.Polly
|
namespace Ocelot.Provider.Polly
|
||||||
{
|
{
|
||||||
using Errors;
|
using Ocelot.Errors;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
public class RequestTimedOutError : Error
|
public class RequestTimedOutError : Error
|
||||||
{
|
{
|
||||||
public RequestTimedOutError(Exception exception)
|
public RequestTimedOutError(Exception exception)
|
||||||
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
|
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError, 503)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
public class UnableToSaveAcceptCommand : Error
|
public class UnableToSaveAcceptCommand : Error
|
||||||
{
|
{
|
||||||
public UnableToSaveAcceptCommand(string message)
|
public UnableToSaveAcceptCommand(string message)
|
||||||
: base(message, OcelotErrorCode.UnknownError)
|
: base(message, OcelotErrorCode.UnknownError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,56 @@
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
namespace Ocelot.Authentication.Middleware
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.Authentication.Middleware
|
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
public class AuthenticationMiddleware : OcelotMiddleware
|
public class AuthenticationMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
public AuthenticationMiddleware(OcelotRequestDelegate next,
|
public AuthenticationMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory)
|
IOcelotLoggerFactory loggerFactory)
|
||||||
: base(loggerFactory.CreateLogger<AuthenticationMiddleware>())
|
: base(loggerFactory.CreateLogger<AuthenticationMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if (context.HttpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(context.DownstreamReRoute))
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
|
if (httpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(downstreamReRoute))
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated");
|
Logger.LogInformation($"{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 httpContext.AuthenticateAsync(downstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
|
||||||
|
|
||||||
context.HttpContext.User = result.Principal;
|
httpContext.User = result.Principal;
|
||||||
|
|
||||||
if (context.HttpContext.User.Identity.IsAuthenticated)
|
if (httpContext.User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}");
|
Logger.LogInformation($"Client has been authenticated for {httpContext.Request.Path}");
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var error = new UnauthenticatedError(
|
var error = new UnauthenticatedError(
|
||||||
$"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated");
|
$"Request for authenticated route {httpContext.Request.Path} by {httpContext.User.Identity.Name} was unauthenticated");
|
||||||
|
|
||||||
Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}");
|
Logger.LogWarning($"Client has NOT been authenticated for {httpContext.Request.Path} and pipeline error set. {error}");
|
||||||
|
|
||||||
SetPipelineError(context, error);
|
httpContext.Items.SetError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}");
|
Logger.LogInformation($"No authentication needed for {httpContext.Request.Path}");
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Middleware.Pipeline;
|
namespace Ocelot.Authentication.Middleware
|
||||||
|
{
|
||||||
namespace Ocelot.Authentication.Middleware
|
using Microsoft.AspNetCore.Builder;
|
||||||
{
|
|
||||||
public static class AuthenticationMiddlewareMiddlewareExtensions
|
public static class AuthenticationMiddlewareMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseAuthenticationMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<AuthenticationMiddleware>();
|
return builder.UseMiddleware<AuthenticationMiddleware>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace Ocelot.Authorisation
|
|
||||||
{
|
|
||||||
public class ClaimValueNotAuthorisedError : Error
|
public class ClaimValueNotAuthorisedError : Error
|
||||||
{
|
{
|
||||||
public ClaimValueNotAuthorisedError(string message)
|
public ClaimValueNotAuthorisedError(string message)
|
||||||
: base(message, OcelotErrorCode.ClaimValueNotAuthorisedError)
|
: base(message, OcelotErrorCode.ClaimValueNotAuthorisedError, 403)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,91 +1,90 @@
|
|||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
namespace Ocelot.Authorisation
|
||||||
using Ocelot.Responses;
|
{
|
||||||
using System.Collections.Generic;
|
using Ocelot.Infrastructure.Claims.Parser;
|
||||||
using System.Linq;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using System.Security.Claims;
|
using Ocelot.Responses;
|
||||||
using System.Text.RegularExpressions;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
namespace Ocelot.Authorisation
|
using System.Security.Claims;
|
||||||
{
|
using System.Text.RegularExpressions;
|
||||||
using Infrastructure.Claims.Parser;
|
|
||||||
|
public class ClaimsAuthoriser : IClaimsAuthoriser
|
||||||
public class ClaimsAuthoriser : IClaimsAuthoriser
|
{
|
||||||
{
|
private readonly IClaimsParser _claimsParser;
|
||||||
private readonly IClaimsParser _claimsParser;
|
|
||||||
|
public ClaimsAuthoriser(IClaimsParser claimsParser)
|
||||||
public ClaimsAuthoriser(IClaimsParser claimsParser)
|
{
|
||||||
{
|
_claimsParser = claimsParser;
|
||||||
_claimsParser = claimsParser;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Response<bool> Authorise(
|
public Response<bool> Authorise(
|
||||||
ClaimsPrincipal claimsPrincipal,
|
ClaimsPrincipal claimsPrincipal,
|
||||||
Dictionary<string, string> routeClaimsRequirement,
|
Dictionary<string, string> routeClaimsRequirement,
|
||||||
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
|
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
foreach (var required in routeClaimsRequirement)
|
foreach (var required in routeClaimsRequirement)
|
||||||
{
|
{
|
||||||
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
|
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
|
||||||
|
|
||||||
if (values.IsError)
|
if (values.IsError)
|
||||||
{
|
|
||||||
return new ErrorResponse<bool>(values.Errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.Data != null)
|
|
||||||
{
|
{
|
||||||
// dynamic claim
|
return new ErrorResponse<bool>(values.Errors);
|
||||||
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
|
}
|
||||||
if (match.Success)
|
|
||||||
{
|
if (values.Data != null)
|
||||||
var variableName = match.Captures[0].Value;
|
{
|
||||||
|
// dynamic claim
|
||||||
var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray();
|
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
|
||||||
if (matchingPlaceholders.Length == 1)
|
if (match.Success)
|
||||||
{
|
|
||||||
// match
|
|
||||||
var actualValue = matchingPlaceholders[0].Value;
|
|
||||||
var authorised = values.Data.Contains(actualValue);
|
|
||||||
if (!authorised)
|
|
||||||
{
|
|
||||||
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
|
||||||
$"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// config error
|
|
||||||
if (matchingPlaceholders.Length == 0)
|
|
||||||
{
|
|
||||||
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
|
||||||
$"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
|
||||||
$"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// static claim
|
var variableName = match.Captures[0].Value;
|
||||||
var authorised = values.Data.Contains(required.Value);
|
|
||||||
if (!authorised)
|
var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray();
|
||||||
{
|
if (matchingPlaceholders.Length == 1)
|
||||||
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
{
|
||||||
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
|
// match
|
||||||
}
|
var actualValue = matchingPlaceholders[0].Value;
|
||||||
}
|
var authorised = values.Data.Contains(actualValue);
|
||||||
}
|
if (!authorised)
|
||||||
else
|
{
|
||||||
{
|
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
||||||
return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}"));
|
$"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return new OkResponse<bool>(true);
|
{
|
||||||
}
|
// config error
|
||||||
}
|
if (matchingPlaceholders.Length == 0)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
||||||
|
$"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
||||||
|
$"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// static claim
|
||||||
|
var authorised = values.Data.Contains(required.Value);
|
||||||
|
if (!authorised)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
||||||
|
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<bool>(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,108 +1,112 @@
|
|||||||
namespace Ocelot.Authorisation.Middleware
|
namespace Ocelot.Authorisation.Middleware
|
||||||
{
|
{
|
||||||
using Configuration;
|
using Ocelot.Configuration;
|
||||||
using Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Responses;
|
using Ocelot.Responses;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
public class AuthorisationMiddleware : OcelotMiddleware
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
{
|
|
||||||
private readonly OcelotRequestDelegate _next;
|
|
||||||
private readonly IClaimsAuthoriser _claimsAuthoriser;
|
|
||||||
private readonly IScopesAuthoriser _scopesAuthoriser;
|
|
||||||
|
|
||||||
public AuthorisationMiddleware(OcelotRequestDelegate next,
|
|
||||||
IClaimsAuthoriser claimsAuthoriser,
|
|
||||||
IScopesAuthoriser scopesAuthoriser,
|
|
||||||
IOcelotLoggerFactory loggerFactory)
|
|
||||||
: base(loggerFactory.CreateLogger<AuthorisationMiddleware>())
|
|
||||||
{
|
|
||||||
_next = next;
|
|
||||||
_claimsAuthoriser = claimsAuthoriser;
|
|
||||||
_scopesAuthoriser = scopesAuthoriser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
|
||||||
{
|
|
||||||
if (!IsOptionsHttpMethod(context) && IsAuthenticatedRoute(context.DownstreamReRoute))
|
|
||||||
{
|
|
||||||
Logger.LogInformation("route is authenticated scopes must be checked");
|
|
||||||
|
|
||||||
var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes);
|
|
||||||
|
|
||||||
if (authorised.IsError)
|
|
||||||
{
|
|
||||||
Logger.LogWarning("error authorising user scopes");
|
|
||||||
|
|
||||||
SetPipelineError(context, authorised.Errors);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsAuthorised(authorised))
|
|
||||||
{
|
|
||||||
Logger.LogInformation("user scopes is authorised calling next authorisation checks");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogWarning("user scopes is not authorised setting pipeline error");
|
|
||||||
|
|
||||||
SetPipelineError(context, new UnauthorisedError(
|
|
||||||
$"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsOptionsHttpMethod(context) && IsAuthorisedRoute(context.DownstreamReRoute))
|
|
||||||
{
|
|
||||||
Logger.LogInformation("route is authorised");
|
|
||||||
|
|
||||||
var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement, context.TemplatePlaceholderNameAndValues);
|
public class AuthorisationMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
if (authorised.IsError)
|
private readonly RequestDelegate _next;
|
||||||
{
|
private readonly IClaimsAuthoriser _claimsAuthoriser;
|
||||||
Logger.LogWarning($"Error whilst authorising {context.HttpContext.User.Identity.Name}. Setting pipeline error");
|
private readonly IScopesAuthoriser _scopesAuthoriser;
|
||||||
|
|
||||||
SetPipelineError(context, authorised.Errors);
|
public AuthorisationMiddleware(RequestDelegate next,
|
||||||
return;
|
IClaimsAuthoriser claimsAuthoriser,
|
||||||
}
|
IScopesAuthoriser scopesAuthoriser,
|
||||||
|
IOcelotLoggerFactory loggerFactory)
|
||||||
if (IsAuthorised(authorised))
|
: base(loggerFactory.CreateLogger<AuthorisationMiddleware>())
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}.");
|
_next = next;
|
||||||
await _next.Invoke(context);
|
_claimsAuthoriser = claimsAuthoriser;
|
||||||
}
|
_scopesAuthoriser = scopesAuthoriser;
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error");
|
|
||||||
|
|
||||||
SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
|
|
||||||
await _next.Invoke(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsAuthorised(Response<bool> authorised)
|
|
||||||
{
|
|
||||||
return authorised.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute)
|
|
||||||
{
|
|
||||||
return reRoute.IsAuthenticated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsAuthorisedRoute(DownstreamReRoute reRoute)
|
|
||||||
{
|
|
||||||
return reRoute.IsAuthorised;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsOptionsHttpMethod(DownstreamContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
return context.HttpContext.Request.Method.ToUpper() == "OPTIONS";
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
}
|
|
||||||
}
|
if (!IsOptionsHttpMethod(httpContext) && IsAuthenticatedRoute(downstreamReRoute))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("route is authenticated scopes must be checked");
|
||||||
|
|
||||||
|
var authorised = _scopesAuthoriser.Authorise(httpContext.User, downstreamReRoute.AuthenticationOptions.AllowedScopes);
|
||||||
|
|
||||||
|
if (authorised.IsError)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("error authorising user scopes");
|
||||||
|
|
||||||
|
httpContext.Items.UpsertErrors(authorised.Errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAuthorised(authorised))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("user scopes is authorised calling next authorisation checks");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning("user scopes is not authorised setting pipeline error");
|
||||||
|
|
||||||
|
httpContext.Items.SetError(new UnauthorisedError(
|
||||||
|
$"{httpContext.User.Identity.Name} unable to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsOptionsHttpMethod(httpContext) && IsAuthorisedRoute(downstreamReRoute))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("route is authorised");
|
||||||
|
|
||||||
|
var authorised = _claimsAuthoriser.Authorise(httpContext.User, downstreamReRoute.RouteClaimsRequirement, httpContext.Items.TemplatePlaceholderNameAndValues());
|
||||||
|
|
||||||
|
if (authorised.IsError)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Error whilst authorising {httpContext.User.Identity.Name}. Setting pipeline error");
|
||||||
|
|
||||||
|
httpContext.Items.UpsertErrors(authorised.Errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAuthorised(authorised))
|
||||||
|
{
|
||||||
|
Logger.LogInformation($"{httpContext.User.Identity.Name} has succesfully been authorised for {downstreamReRoute.UpstreamPathTemplate.OriginalValue}.");
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"{httpContext.User.Identity.Name} is not authorised to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error");
|
||||||
|
|
||||||
|
httpContext.Items.SetError(new UnauthorisedError($"{httpContext.User.Identity.Name} is not authorised to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAuthorised(Response<bool> authorised)
|
||||||
|
{
|
||||||
|
return authorised.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute)
|
||||||
|
{
|
||||||
|
return reRoute.IsAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAuthorisedRoute(DownstreamReRoute reRoute)
|
||||||
|
{
|
||||||
|
return reRoute.IsAuthorised;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsOptionsHttpMethod(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
return httpContext.Request.Method.ToUpper() == "OPTIONS";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Middleware.Pipeline;
|
namespace Ocelot.Authorisation.Middleware
|
||||||
|
{
|
||||||
namespace Ocelot.Authorisation.Middleware
|
using Microsoft.AspNetCore.Builder;
|
||||||
{
|
|
||||||
public static class AuthorisationMiddlewareMiddlewareExtensions
|
public static class AuthorisationMiddlewareMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseAuthorisationMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<AuthorisationMiddleware>();
|
return builder.UseMiddleware<AuthorisationMiddleware>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using Ocelot.Errors;
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Authorisation
|
|
||||||
{
|
|
||||||
public class ScopeNotAuthorisedError : Error
|
public class ScopeNotAuthorisedError : Error
|
||||||
{
|
{
|
||||||
public ScopeNotAuthorisedError(string message)
|
public ScopeNotAuthorisedError(string message)
|
||||||
: base(message, OcelotErrorCode.ScopeNotAuthorisedError)
|
: base(message, OcelotErrorCode.ScopeNotAuthorisedError, 403)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
namespace Ocelot.Authorisation
|
using Ocelot.Errors;
|
||||||
{
|
|
||||||
public class UnauthorisedError : Error
|
public class UnauthorisedError : Error
|
||||||
{
|
{
|
||||||
public UnauthorisedError(string message)
|
public UnauthorisedError(string message)
|
||||||
: base(message, OcelotErrorCode.UnauthorizedError)
|
: base(message, OcelotErrorCode.UnauthorizedError, 403)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using Ocelot.Errors;
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Authorisation
|
|
||||||
{
|
|
||||||
public class UserDoesNotHaveClaimError : Error
|
public class UserDoesNotHaveClaimError : Error
|
||||||
{
|
{
|
||||||
public UserDoesNotHaveClaimError(string message)
|
public UserDoesNotHaveClaimError(string message)
|
||||||
: base(message, OcelotErrorCode.UserDoesNotHaveClaimError)
|
: base(message, OcelotErrorCode.UserDoesNotHaveClaimError, 403)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
using Ocelot.Middleware;
|
namespace Ocelot.Cache
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.Cache
|
|
||||||
{
|
{
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class CacheKeyGenerator : ICacheKeyGenerator
|
public class CacheKeyGenerator : ICacheKeyGenerator
|
||||||
{
|
{
|
||||||
public string GenerateRequestCacheKey(DownstreamContext context)
|
public string GenerateRequestCacheKey(DownstreamRequest downstreamRequest)
|
||||||
{
|
{
|
||||||
string hashedContent = null;
|
string hashedContent = null;
|
||||||
StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}");
|
StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{downstreamRequest.Method}-{downstreamRequest.OriginalString}");
|
||||||
if (context.DownstreamRequest.Content != null)
|
if (downstreamRequest.Content != null)
|
||||||
{
|
{
|
||||||
string requestContentString = Task.Run(async () => await context.DownstreamRequest.Content.ReadAsStringAsync()).Result;
|
string requestContentString = Task.Run(async () => await downstreamRequest.Content.ReadAsStringAsync()).Result;
|
||||||
downStreamUrlKeyBuilder.Append(requestContentString);
|
downStreamUrlKeyBuilder.Append(requestContentString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using Ocelot.Middleware;
|
namespace Ocelot.Cache
|
||||||
|
|
||||||
namespace Ocelot.Cache
|
|
||||||
{
|
{
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
|
||||||
public interface ICacheKeyGenerator
|
public interface ICacheKeyGenerator
|
||||||
{
|
{
|
||||||
string GenerateRequestCacheKey(DownstreamContext context);
|
string GenerateRequestCacheKey(DownstreamRequest downstreamRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,122 +1,129 @@
|
|||||||
namespace Ocelot.Cache.Middleware
|
namespace Ocelot.Cache.Middleware
|
||||||
{
|
{
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
public class OutputCacheMiddleware : OcelotMiddleware
|
public class OutputCacheMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IOcelotCache<CachedResponse> _outputCache;
|
private readonly IOcelotCache<CachedResponse> _outputCache;
|
||||||
private readonly ICacheKeyGenerator _cacheGeneratot;
|
private readonly ICacheKeyGenerator _cacheGenerator;
|
||||||
|
|
||||||
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
public OutputCacheMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IOcelotCache<CachedResponse> outputCache,
|
IOcelotCache<CachedResponse> outputCache,
|
||||||
ICacheKeyGenerator cacheGeneratot)
|
ICacheKeyGenerator cacheGenerator)
|
||||||
: base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
: base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_outputCache = outputCache;
|
_outputCache = outputCache;
|
||||||
_cacheGeneratot = cacheGeneratot;
|
_cacheGenerator = cacheGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if (!context.DownstreamReRoute.IsCached)
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
{
|
|
||||||
await _next.Invoke(context);
|
if (!downstreamReRoute.IsCached)
|
||||||
return;
|
{
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
|
var downstreamRequest = httpContext.Items.DownstreamRequest();
|
||||||
string downStreamRequestCacheKey = _cacheGeneratot.GenerateRequestCacheKey(context);
|
|
||||||
|
var downstreamUrlKey = $"{downstreamRequest.Method}-{downstreamRequest.OriginalString}";
|
||||||
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
|
string downStreamRequestCacheKey = _cacheGenerator.GenerateRequestCacheKey(downstreamRequest);
|
||||||
|
|
||||||
var cached = _outputCache.Get(downStreamRequestCacheKey, context.DownstreamReRoute.CacheOptions.Region);
|
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
|
||||||
|
|
||||||
if (cached != null)
|
var cached = _outputCache.Get(downStreamRequestCacheKey, downstreamReRoute.CacheOptions.Region);
|
||||||
{
|
|
||||||
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
|
if (cached != null)
|
||||||
|
{
|
||||||
var response = CreateHttpResponseMessage(cached);
|
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
|
||||||
SetHttpResponseMessageThisRequest(context, response);
|
|
||||||
|
var response = CreateHttpResponseMessage(cached);
|
||||||
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
|
SetHttpResponseMessageThisRequest(httpContext, response);
|
||||||
|
|
||||||
return;
|
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
|
||||||
|
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
|
|
||||||
|
if (httpContext.Items.Errors().Count > 0)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
|
var downstreamResponse = httpContext.Items.DownstreamResponse();
|
||||||
|
|
||||||
await _next.Invoke(context);
|
cached = await CreateCachedResponse(downstreamResponse);
|
||||||
|
|
||||||
if (context.IsError)
|
_outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(downstreamReRoute.CacheOptions.TtlSeconds), downstreamReRoute.CacheOptions.Region);
|
||||||
{
|
|
||||||
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
|
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
|
||||||
|
}
|
||||||
return;
|
|
||||||
}
|
private void SetHttpResponseMessageThisRequest(HttpContext context,
|
||||||
|
DownstreamResponse response)
|
||||||
cached = await CreateCachedResponse(context.DownstreamResponse);
|
{
|
||||||
|
context.Items.UpsertDownstreamResponse(response);
|
||||||
_outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
|
}
|
||||||
|
|
||||||
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
|
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
|
||||||
}
|
{
|
||||||
|
if (cached == null)
|
||||||
private void SetHttpResponseMessageThisRequest(DownstreamContext context,
|
{
|
||||||
DownstreamResponse response)
|
return null;
|
||||||
{
|
}
|
||||||
context.DownstreamResponse = response;
|
|
||||||
}
|
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
|
||||||
|
|
||||||
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
|
var streamContent = new StreamContent(content);
|
||||||
{
|
|
||||||
if (cached == null)
|
foreach (var header in cached.ContentHeaders)
|
||||||
{
|
{
|
||||||
return null;
|
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
|
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase);
|
||||||
|
}
|
||||||
var streamContent = new StreamContent(content);
|
|
||||||
|
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
|
||||||
foreach (var header in cached.ContentHeaders)
|
{
|
||||||
{
|
if (response == null)
|
||||||
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
{
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase);
|
|
||||||
}
|
var statusCode = response.StatusCode;
|
||||||
|
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
|
||||||
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
|
string body = null;
|
||||||
{
|
|
||||||
if (response == null)
|
if (response.Content != null)
|
||||||
{
|
{
|
||||||
return null;
|
var content = await response.Content.ReadAsByteArrayAsync();
|
||||||
}
|
body = Convert.ToBase64String(content);
|
||||||
|
}
|
||||||
var statusCode = response.StatusCode;
|
|
||||||
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
|
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
|
||||||
string body = null;
|
|
||||||
|
var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase);
|
||||||
if (response.Content != null)
|
return cached;
|
||||||
{
|
}
|
||||||
var content = await response.Content.ReadAsByteArrayAsync();
|
}
|
||||||
body = Convert.ToBase64String(content);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
|
|
||||||
|
|
||||||
var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase);
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
namespace Ocelot.Cache.Middleware
|
||||||
using Ocelot.Middleware.Pipeline;
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
namespace Ocelot.Cache.Middleware
|
|
||||||
{
|
public static class OutputCacheMiddlewareExtensions
|
||||||
public static class OutputCacheMiddlewareExtensions
|
{
|
||||||
{
|
public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder)
|
||||||
public static IOcelotPipelineBuilder UseOutputCacheMiddleware(this IOcelotPipelineBuilder builder)
|
{
|
||||||
{
|
return builder.UseMiddleware<OutputCacheMiddleware>();
|
||||||
return builder.UseMiddleware<OutputCacheMiddleware>();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using Ocelot.Middleware.Pipeline;
|
namespace Ocelot.Claims.Middleware
|
||||||
|
|
||||||
namespace Ocelot.Claims.Middleware
|
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
public static class ClaimsBuilderMiddlewareExtensions
|
public static class ClaimsBuilderMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseClaimsToClaimsMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<ClaimsToClaimsMiddleware>();
|
return builder.UseMiddleware<ClaimsToClaimsMiddleware>();
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,46 @@
|
|||||||
using Ocelot.Logging;
|
namespace Ocelot.Claims.Middleware
|
||||||
using Ocelot.Middleware;
|
{
|
||||||
using System.Linq;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System.Threading.Tasks;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
using Ocelot.Logging;
|
||||||
namespace Ocelot.Claims.Middleware
|
using Ocelot.Middleware;
|
||||||
{
|
using System.Linq;
|
||||||
public class ClaimsToClaimsMiddleware : OcelotMiddleware
|
using System.Threading.Tasks;
|
||||||
{
|
|
||||||
private readonly OcelotRequestDelegate _next;
|
public class ClaimsToClaimsMiddleware : OcelotMiddleware
|
||||||
private readonly IAddClaimsToRequest _addClaimsToRequest;
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
public ClaimsToClaimsMiddleware(OcelotRequestDelegate next,
|
private readonly IAddClaimsToRequest _addClaimsToRequest;
|
||||||
IOcelotLoggerFactory loggerFactory,
|
|
||||||
IAddClaimsToRequest addClaimsToRequest)
|
public ClaimsToClaimsMiddleware(RequestDelegate next,
|
||||||
: base(loggerFactory.CreateLogger<ClaimsToClaimsMiddleware>())
|
IOcelotLoggerFactory loggerFactory,
|
||||||
{
|
IAddClaimsToRequest addClaimsToRequest)
|
||||||
_next = next;
|
: base(loggerFactory.CreateLogger<ClaimsToClaimsMiddleware>())
|
||||||
_addClaimsToRequest = addClaimsToRequest;
|
{
|
||||||
}
|
_next = next;
|
||||||
|
_addClaimsToRequest = addClaimsToRequest;
|
||||||
public async Task Invoke(DownstreamContext context)
|
}
|
||||||
{
|
|
||||||
if (context.DownstreamReRoute.ClaimsToClaims.Any())
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("this route has instructions to convert claims to other claims");
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext);
|
if (downstreamReRoute.ClaimsToClaims.Any())
|
||||||
|
{
|
||||||
if (result.IsError)
|
Logger.LogDebug("this route has instructions to convert claims to other claims");
|
||||||
{
|
|
||||||
Logger.LogDebug("error converting claims to other claims, setting pipeline error");
|
var result = _addClaimsToRequest.SetClaimsOnContext(downstreamReRoute.ClaimsToClaims, httpContext);
|
||||||
|
|
||||||
SetPipelineError(context, result.Errors);
|
if (result.IsError)
|
||||||
return;
|
{
|
||||||
}
|
Logger.LogDebug("error converting claims to other claims, setting pipeline error");
|
||||||
}
|
|
||||||
|
httpContext.Items.UpsertErrors(result.Errors);
|
||||||
await _next.Invoke(context);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Parser
|
namespace Ocelot.Configuration.Parser
|
||||||
{
|
{
|
||||||
public class InstructionNotForClaimsError : Error
|
public class InstructionNotForClaimsError : Error
|
||||||
{
|
{
|
||||||
public InstructionNotForClaimsError()
|
public InstructionNotForClaimsError()
|
||||||
: base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError)
|
: base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Parser
|
namespace Ocelot.Configuration.Parser
|
||||||
{
|
{
|
||||||
public class NoInstructionsError : Error
|
public class NoInstructionsError : Error
|
||||||
{
|
{
|
||||||
public NoInstructionsError(string splitToken)
|
public NoInstructionsError(string splitToken)
|
||||||
: base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError)
|
: base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Parser
|
namespace Ocelot.Configuration.Parser
|
||||||
{
|
{
|
||||||
public class ParsingConfigurationHeaderError : Error
|
public class ParsingConfigurationHeaderError : Error
|
||||||
{
|
{
|
||||||
public ParsingConfigurationHeaderError(Exception exception)
|
public ParsingConfigurationHeaderError(Exception exception)
|
||||||
: base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError)
|
: base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
public class ReRoute
|
public class ReRoute
|
||||||
{
|
{
|
||||||
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
|
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
|
||||||
List<AggregateReRouteConfig> downstreamReRouteConfig,
|
List<AggregateReRouteConfig> downstreamReRouteConfig,
|
||||||
List<HttpMethod> upstreamHttpMethod,
|
List<HttpMethod> upstreamHttpMethod,
|
||||||
UpstreamPathTemplate upstreamTemplatePattern,
|
UpstreamPathTemplate upstreamTemplatePattern,
|
||||||
string upstreamHost,
|
string upstreamHost,
|
||||||
string aggregator)
|
string aggregator)
|
||||||
{
|
{
|
||||||
UpstreamHost = upstreamHost;
|
UpstreamHost = upstreamHost;
|
||||||
DownstreamReRoute = downstreamReRoute;
|
DownstreamReRoute = downstreamReRoute;
|
||||||
DownstreamReRouteConfig = downstreamReRouteConfig;
|
DownstreamReRouteConfig = downstreamReRouteConfig;
|
||||||
UpstreamHttpMethod = upstreamHttpMethod;
|
UpstreamHttpMethod = upstreamHttpMethod;
|
||||||
UpstreamTemplatePattern = upstreamTemplatePattern;
|
UpstreamTemplatePattern = upstreamTemplatePattern;
|
||||||
Aggregator = aggregator;
|
Aggregator = aggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; }
|
public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; }
|
||||||
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 List<AggregateReRouteConfig> DownstreamReRouteConfig { get; private set; }
|
public List<AggregateReRouteConfig> DownstreamReRouteConfig { get; private set; }
|
||||||
public string Aggregator { get; private set; }
|
public string Aggregator { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
namespace Ocelot.Configuration.Validator
|
namespace Ocelot.Configuration.Validator
|
||||||
{
|
{
|
||||||
using Errors;
|
using Errors;
|
||||||
|
|
||||||
public class FileValidationFailedError : Error
|
public class FileValidationFailedError : Error
|
||||||
{
|
{
|
||||||
public FileValidationFailedError(string message)
|
public FileValidationFailedError(string message)
|
||||||
: base(message, OcelotErrorCode.FileValidationFailedError)
|
: base(message, OcelotErrorCode.FileValidationFailedError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Multiplexer;
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
@ -27,7 +27,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Multiplexer;
|
||||||
using Ocelot.PathManipulation;
|
using Ocelot.PathManipulation;
|
||||||
using Ocelot.QueryStrings;
|
using Ocelot.QueryStrings;
|
||||||
using Ocelot.RateLimit;
|
using Ocelot.RateLimit;
|
||||||
@ -128,7 +128,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
Services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
|
Services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
|
||||||
Services.AddMemoryCache();
|
Services.AddMemoryCache();
|
||||||
Services.TryAddSingleton<OcelotDiagnosticListener>();
|
Services.TryAddSingleton<OcelotDiagnosticListener>();
|
||||||
Services.TryAddSingleton<IMultiplexer, Multiplexer>();
|
|
||||||
Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
|
Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
|
||||||
Services.TryAddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
|
Services.TryAddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
|
||||||
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
||||||
|
@ -1,42 +1,50 @@
|
|||||||
using Ocelot.Logging;
|
namespace Ocelot.DownstreamPathManipulation.Middleware
|
||||||
using Ocelot.Middleware;
|
{
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.PathManipulation;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.PathManipulation.Middleware
|
public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware
|
private readonly RequestDelegate _next;
|
||||||
{
|
private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate;
|
||||||
private readonly OcelotRequestDelegate _next;
|
|
||||||
private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate;
|
public ClaimsToDownstreamPathMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IChangeDownstreamPathTemplate changeDownstreamPathTemplate)
|
||||||
|
: base(loggerFactory.CreateLogger<ClaimsToDownstreamPathMiddleware>())
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_changeDownstreamPathTemplate = changeDownstreamPathTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
|
if (downstreamReRoute.ClaimsToPath.Any())
|
||||||
|
{
|
||||||
|
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path");
|
||||||
|
|
||||||
public ClaimsToDownstreamPathMiddleware(OcelotRequestDelegate next,
|
var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues();
|
||||||
IOcelotLoggerFactory loggerFactory,
|
|
||||||
IChangeDownstreamPathTemplate changeDownstreamPathTemplate)
|
|
||||||
: base(loggerFactory.CreateLogger<ClaimsToDownstreamPathMiddleware>())
|
|
||||||
{
|
|
||||||
_next = next;
|
|
||||||
_changeDownstreamPathTemplate = changeDownstreamPathTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(downstreamReRoute.ClaimsToPath, httpContext.User.Claims,
|
||||||
{
|
downstreamReRoute.DownstreamPathTemplate, templatePlaceholderNameAndValues);
|
||||||
if (context.DownstreamReRoute.ClaimsToPath.Any())
|
|
||||||
{
|
if (response.IsError)
|
||||||
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path");
|
{
|
||||||
var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(context.DownstreamReRoute.ClaimsToPath, context.HttpContext.User.Claims,
|
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
|
||||||
context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
|
|
||||||
|
httpContext.Items.UpsertErrors(response.Errors);
|
||||||
if (response.IsError)
|
return;
|
||||||
{
|
}
|
||||||
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
|
}
|
||||||
|
|
||||||
SetPipelineError(context, response.Errors);
|
await _next.Invoke(httpContext);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _next.Invoke(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using Ocelot.Middleware.Pipeline;
|
namespace Ocelot.DownstreamPathManipulation.Middleware
|
||||||
|
|
||||||
namespace Ocelot.PathManipulation.Middleware
|
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
public static class ClaimsToDownstreamPathMiddlewareExtensions
|
public static class ClaimsToDownstreamPathMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseClaimsToDownstreamPathMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseClaimsToDownstreamPathMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<ClaimsToDownstreamPathMiddleware>();
|
return builder.UseMiddleware<ClaimsToDownstreamPathMiddleware>();
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
using Ocelot.Configuration;
|
namespace Ocelot.DownstreamRouteFinder
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
{
|
||||||
using System.Collections.Generic;
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
namespace Ocelot.DownstreamRouteFinder
|
using System.Collections.Generic;
|
||||||
{
|
|
||||||
public class DownstreamRoute
|
public class DownstreamRoute
|
||||||
{
|
{
|
||||||
public DownstreamRoute(List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
|
public DownstreamRoute()
|
||||||
{
|
{
|
||||||
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
|
}
|
||||||
ReRoute = reRoute;
|
|
||||||
}
|
public DownstreamRoute(List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
|
||||||
|
{
|
||||||
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
|
||||||
public ReRoute ReRoute { get; private set; }
|
ReRoute = reRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
||||||
|
public ReRoute ReRoute { get; private set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.Finder
|
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||||
{
|
{
|
||||||
public class UnableToFindDownstreamRouteError : Error
|
public class UnableToFindDownstreamRouteError : Error
|
||||||
{
|
{
|
||||||
public UnableToFindDownstreamRouteError(string path, string httpVerb)
|
public UnableToFindDownstreamRouteError(string path, string httpVerb)
|
||||||
: base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError)
|
: base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,59 +1,61 @@
|
|||||||
using Ocelot.DownstreamRouteFinder.Finder;
|
|
||||||
using Ocelot.Infrastructure.Extensions;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.Middleware
|
namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Finder;
|
||||||
|
using Ocelot.Infrastructure.Extensions;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class DownstreamRouteFinderMiddleware : OcelotMiddleware
|
public class DownstreamRouteFinderMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IDownstreamRouteProviderFactory _factory;
|
private readonly IDownstreamRouteProviderFactory _factory;
|
||||||
private readonly IMultiplexer _multiplexer;
|
|
||||||
|
|
||||||
public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next,
|
public DownstreamRouteFinderMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IDownstreamRouteProviderFactory downstreamRouteFinder,
|
IDownstreamRouteProviderFactory downstreamRouteFinder
|
||||||
IMultiplexer multiplexer)
|
)
|
||||||
: base(loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>())
|
: base(loggerFactory.CreateLogger<DownstreamRouteFinderMiddleware>())
|
||||||
{
|
{
|
||||||
_multiplexer = multiplexer;
|
|
||||||
_next = next;
|
_next = next;
|
||||||
_factory = downstreamRouteFinder;
|
_factory = downstreamRouteFinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var upstreamUrlPath = context.HttpContext.Request.Path.ToString();
|
var upstreamUrlPath = httpContext.Request.Path.ToString();
|
||||||
|
|
||||||
var upstreamQueryString = context.HttpContext.Request.QueryString.ToString();
|
var upstreamQueryString = httpContext.Request.QueryString.ToString();
|
||||||
|
|
||||||
var upstreamHost = context.HttpContext.Request.Headers["Host"];
|
var upstreamHost = httpContext.Request.Headers["Host"];
|
||||||
|
|
||||||
Logger.LogDebug($"Upstream url path is {upstreamUrlPath}");
|
Logger.LogDebug($"Upstream url path is {upstreamUrlPath}");
|
||||||
|
|
||||||
var provider = _factory.Get(context.Configuration);
|
var internalConfiguration = httpContext.Items.IInternalConfiguration();
|
||||||
|
|
||||||
var downstreamRoute = provider.Get(upstreamUrlPath, upstreamQueryString, context.HttpContext.Request.Method, context.Configuration, upstreamHost);
|
var provider = _factory.Get(internalConfiguration);
|
||||||
|
|
||||||
if (downstreamRoute.IsError)
|
var response = provider.Get(upstreamUrlPath, upstreamQueryString, httpContext.Request.Method, internalConfiguration, upstreamHost);
|
||||||
|
|
||||||
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}");
|
Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {response.Errors.ToErrorString()}");
|
||||||
|
|
||||||
SetPipelineError(context, downstreamRoute.Errors);
|
httpContext.Items.UpsertErrors(response.Errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
|
var downstreamPathTemplates = string.Join(", ", response.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
|
||||||
|
|
||||||
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
||||||
|
|
||||||
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
|
// why set both of these on HttpContext
|
||||||
|
httpContext.Items.UpsertTemplatePlaceholderNameAndValues(response.Data.TemplatePlaceholderNameAndValues);
|
||||||
|
|
||||||
await _multiplexer.Multiplex(context, downstreamRoute.Data.ReRoute, _next);
|
httpContext.Items.UpsertDownstreamRoute(response.Data);
|
||||||
|
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Ocelot.Middleware.Pipeline;
|
|
||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.Middleware
|
namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
public static class DownstreamRouteFinderMiddlewareExtensions
|
public static class DownstreamRouteFinderMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseDownstreamRouteFinderMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<DownstreamRouteFinderMiddleware>();
|
return builder.UseMiddleware<DownstreamRouteFinderMiddleware>();
|
||||||
}
|
}
|
||||||
|
@ -1,132 +1,149 @@
|
|||||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||||
using Ocelot.Logging;
|
{
|
||||||
using Ocelot.Middleware;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Responses;
|
using System.Text.RegularExpressions;
|
||||||
using Ocelot.Values;
|
using Ocelot.Configuration;
|
||||||
using System;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using System.Threading.Tasks;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
using System.Text.RegularExpressions;
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IDownstreamPathPlaceholderReplacer _replacer;
|
||||||
|
|
||||||
|
public DownstreamUrlCreatorMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IDownstreamPathPlaceholderReplacer replacer
|
||||||
|
)
|
||||||
|
: base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_replacer = replacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues();
|
||||||
{
|
|
||||||
private readonly OcelotRequestDelegate _next;
|
var response = _replacer
|
||||||
private readonly IDownstreamPathPlaceholderReplacer _replacer;
|
.Replace(downstreamReRoute.DownstreamPathTemplate.Value, templatePlaceholderNameAndValues);
|
||||||
|
|
||||||
public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next,
|
var downstreamRequest = httpContext.Items.DownstreamRequest();
|
||||||
IOcelotLoggerFactory loggerFactory,
|
|
||||||
IDownstreamPathPlaceholderReplacer replacer)
|
|
||||||
: base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
|
|
||||||
{
|
|
||||||
_next = next;
|
|
||||||
_replacer = replacer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
var response = _replacer
|
Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
|
||||||
.Replace(context.DownstreamReRoute.DownstreamPathTemplate.Value, context.TemplatePlaceholderNameAndValues);
|
|
||||||
|
httpContext.Items.UpsertErrors(response.Errors);
|
||||||
if (response.IsError)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(downstreamReRoute.DownstreamScheme))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
|
//todo make sure this works, hopefully there is a test ;E
|
||||||
|
httpContext.Items.DownstreamRequest().Scheme = downstreamReRoute.DownstreamScheme;
|
||||||
SetPipelineError(context, response.Errors);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(context.DownstreamReRoute.DownstreamScheme))
|
var internalConfiguration = httpContext.Items.IInternalConfiguration();
|
||||||
{
|
|
||||||
context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ServiceFabricRequest(context))
|
if (ServiceFabricRequest(internalConfiguration, downstreamReRoute))
|
||||||
{
|
{
|
||||||
var pathAndQuery = CreateServiceFabricUri(context, response);
|
var pathAndQuery = CreateServiceFabricUri(downstreamRequest, downstreamReRoute, templatePlaceholderNameAndValues, response);
|
||||||
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
|
|
||||||
context.DownstreamRequest.Query = pathAndQuery.query;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dsPath = response.Data;
|
|
||||||
|
|
||||||
if (ContainsQueryString(dsPath))
|
//todo check this works again hope there is a test..
|
||||||
|
downstreamRequest.AbsolutePath = pathAndQuery.path;
|
||||||
|
downstreamRequest.Query = pathAndQuery.query;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dsPath = response.Data;
|
||||||
|
|
||||||
|
if (ContainsQueryString(dsPath))
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.AbsolutePath = GetPath(dsPath);
|
downstreamRequest.AbsolutePath = GetPath(dsPath);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(context.DownstreamRequest.Query))
|
if (string.IsNullOrEmpty(downstreamRequest.Query))
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.Query = GetQueryString(dsPath);
|
downstreamRequest.Query = GetQueryString(dsPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&');
|
downstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context);
|
RemoveQueryStringParametersThatHaveBeenUsedInTemplate(downstreamRequest, templatePlaceholderNameAndValues);
|
||||||
|
|
||||||
context.DownstreamRequest.AbsolutePath = dsPath.Value;
|
downstreamRequest.AbsolutePath = dsPath.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug($"Downstream url is {context.DownstreamRequest}");
|
Logger.LogDebug($"Downstream url is {downstreamRequest}");
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamContext context)
|
private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamRequest downstreamRequest, List<PlaceholderNameAndValue> templatePlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
foreach (var nAndV in context.TemplatePlaceholderNameAndValues)
|
foreach (var nAndV in templatePlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
var name = nAndV.Name.Replace("{", "").Replace("}", "");
|
var name = nAndV.Name.Replace("{", "").Replace("}", "");
|
||||||
|
|
||||||
if (context.DownstreamRequest.Query.Contains(name) &&
|
if (downstreamRequest.Query.Contains(name) &&
|
||||||
context.DownstreamRequest.Query.Contains(nAndV.Value))
|
downstreamRequest.Query.Contains(nAndV.Value))
|
||||||
{
|
{
|
||||||
var questionMarkOrAmpersand = context.DownstreamRequest.Query.IndexOf(name, StringComparison.Ordinal);
|
var questionMarkOrAmpersand = downstreamRequest.Query.IndexOf(name, StringComparison.Ordinal);
|
||||||
context.DownstreamRequest.Query = context.DownstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1);
|
downstreamRequest.Query = downstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1);
|
||||||
|
|
||||||
var rgx = new Regex($@"\b{name}={nAndV.Value}\b");
|
var rgx = new Regex($@"\b{name}={nAndV.Value}\b");
|
||||||
context.DownstreamRequest.Query = rgx.Replace(context.DownstreamRequest.Query, "");
|
downstreamRequest.Query = rgx.Replace(downstreamRequest.Query, "");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(context.DownstreamRequest.Query))
|
if (!string.IsNullOrEmpty(downstreamRequest.Query))
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.Query = '?' + context.DownstreamRequest.Query.Substring(1);
|
downstreamRequest.Query = '?' + downstreamRequest.Query.Substring(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPath(DownstreamPath dsPath)
|
private string GetPath(DownstreamPath dsPath)
|
||||||
{
|
{
|
||||||
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetQueryString(DownstreamPath dsPath)
|
private string GetQueryString(DownstreamPath dsPath)
|
||||||
{
|
{
|
||||||
return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ContainsQueryString(DownstreamPath dsPath)
|
private bool ContainsQueryString(DownstreamPath dsPath)
|
||||||
{
|
{
|
||||||
return dsPath.Value.Contains("?");
|
return dsPath.Value.Contains("?");
|
||||||
}
|
}
|
||||||
|
|
||||||
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
|
private (string path, string query) CreateServiceFabricUri(DownstreamRequest downstreamRequest, DownstreamReRoute downstreamReRoute, List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, Response<DownstreamPath> dsPath)
|
||||||
{
|
{
|
||||||
var query = context.DownstreamRequest.Query;
|
var query = downstreamRequest.Query;
|
||||||
var serviceName = _replacer.Replace(context.DownstreamReRoute.ServiceName, context.TemplatePlaceholderNameAndValues);
|
var serviceName = _replacer.Replace(downstreamReRoute.ServiceName, templatePlaceholderNameAndValues);
|
||||||
var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}";
|
var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}";
|
||||||
return (pathTemplate, query);
|
return (pathTemplate, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ServiceFabricRequest(DownstreamContext context)
|
private static bool ServiceFabricRequest(IInternalConfiguration config, DownstreamReRoute downstreamReRoute)
|
||||||
{
|
{
|
||||||
return context.Configuration.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && context.DownstreamReRoute.UseServiceDiscovery;
|
return config.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && downstreamReRoute.UseServiceDiscovery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||||
using Ocelot.Middleware.Pipeline;
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
|
||||||
{
|
public static class DownstreamUrlCreatorMiddlewareExtensions
|
||||||
public static class DownstreamUrlCreatorMiddlewareExtensions
|
{
|
||||||
{
|
public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder)
|
||||||
public static IOcelotPipelineBuilder UseDownstreamUrlCreatorMiddleware(this IOcelotPipelineBuilder builder)
|
{
|
||||||
{
|
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
|
||||||
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace Ocelot.Errors
|
namespace Ocelot.Errors
|
||||||
{
|
{
|
||||||
public abstract class Error
|
public abstract class Error
|
||||||
{
|
{
|
||||||
protected Error(string message, OcelotErrorCode code)
|
protected Error(string message, OcelotErrorCode code, int httpStatusCode)
|
||||||
{
|
{
|
||||||
|
HttpStatusCode = httpStatusCode;
|
||||||
Message = message;
|
Message = message;
|
||||||
Code = code;
|
Code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Message { get; private set; }
|
public string Message { get; private set; }
|
||||||
public OcelotErrorCode Code { get; private set; }
|
public OcelotErrorCode Code { get; private set; }
|
||||||
|
public int HttpStatusCode { get; private set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
@ -1,102 +1,89 @@
|
|||||||
namespace Ocelot.Errors.Middleware
|
namespace Ocelot.Errors.Middleware
|
||||||
{
|
{
|
||||||
using Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Repository;
|
|
||||||
using Ocelot.Infrastructure.Extensions;
|
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
|
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExceptionHandlerMiddleware : OcelotMiddleware
|
public class ExceptionHandlerMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IInternalConfigurationRepository _configRepo;
|
|
||||||
private readonly IRequestScopedDataRepository _repo;
|
private readonly IRequestScopedDataRepository _repo;
|
||||||
|
|
||||||
public ExceptionHandlerMiddleware(OcelotRequestDelegate next,
|
public ExceptionHandlerMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IInternalConfigurationRepository configRepo,
|
|
||||||
IRequestScopedDataRepository repo)
|
IRequestScopedDataRepository repo)
|
||||||
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
|
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
|
||||||
{
|
{
|
||||||
_configRepo = configRepo;
|
|
||||||
_repo = repo;
|
|
||||||
_next = next;
|
_next = next;
|
||||||
|
_repo = repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.HttpContext.RequestAborted.ThrowIfCancellationRequested();
|
httpContext.RequestAborted.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
//try and get the global request id and set it for logs...
|
var internalConfiguration = httpContext.Items.IInternalConfiguration();
|
||||||
//should this basically be immutable per request...i guess it should!
|
|
||||||
//first thing is get config
|
|
||||||
var configuration = _configRepo.Get();
|
|
||||||
|
|
||||||
if (configuration.IsError)
|
TrySetGlobalRequestId(httpContext, internalConfiguration);
|
||||||
{
|
|
||||||
throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
TrySetGlobalRequestId(context, configuration.Data);
|
|
||||||
|
|
||||||
context.Configuration = configuration.Data;
|
|
||||||
|
|
||||||
Logger.LogDebug("ocelot pipeline started");
|
Logger.LogDebug("ocelot pipeline started");
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (context.HttpContext.RequestAborted.IsCancellationRequested)
|
catch (OperationCanceledException) when (httpContext.RequestAborted.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("operation canceled");
|
Logger.LogDebug("operation canceled");
|
||||||
if (!context.HttpContext.Response.HasStarted)
|
if (!httpContext.Response.HasStarted)
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.StatusCode = 499;
|
httpContext.Response.StatusCode = 499;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("error calling middleware");
|
Logger.LogDebug("error calling middleware");
|
||||||
|
|
||||||
var message = CreateMessage(context, e);
|
var message = CreateMessage(httpContext, e);
|
||||||
|
|
||||||
Logger.LogError(message, e);
|
Logger.LogError(message, e);
|
||||||
|
|
||||||
SetInternalServerErrorOnResponse(context);
|
SetInternalServerErrorOnResponse(httpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug("ocelot pipeline finished");
|
Logger.LogDebug("ocelot pipeline finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration)
|
private void TrySetGlobalRequestId(HttpContext httpContext, IInternalConfiguration configuration)
|
||||||
{
|
{
|
||||||
var key = configuration.RequestId;
|
var key = configuration.RequestId;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
|
if (!string.IsNullOrEmpty(key) && httpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
|
||||||
{
|
{
|
||||||
context.HttpContext.TraceIdentifier = upstreamRequestIds.First();
|
httpContext.TraceIdentifier = upstreamRequestIds.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
_repo.Add("RequestId", context.HttpContext.TraceIdentifier);
|
_repo.Add("RequestId", httpContext.TraceIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetInternalServerErrorOnResponse(DownstreamContext context)
|
private void SetInternalServerErrorOnResponse(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if (!context.HttpContext.Response.HasStarted)
|
if (!httpContext.Response.HasStarted)
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.StatusCode = 500;
|
httpContext.Response.StatusCode = 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateMessage(DownstreamContext context, Exception e)
|
private string CreateMessage(HttpContext httpContext, Exception e)
|
||||||
{
|
{
|
||||||
var message =
|
var message =
|
||||||
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
|
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
|
||||||
@ -107,7 +94,7 @@ namespace Ocelot.Errors.Middleware
|
|||||||
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
|
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{message} RequestId: {context.HttpContext.TraceIdentifier}";
|
return $"{message} RequestId: {httpContext.TraceIdentifier}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Ocelot.Middleware.Pipeline;
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.Errors.Middleware
|
namespace Ocelot.Errors.Middleware
|
||||||
{
|
{
|
||||||
public static class ExceptionHandlerMiddlewareExtensions
|
public static class ExceptionHandlerMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseExceptionHandlerMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<ExceptionHandlerMiddleware>();
|
return builder.UseMiddleware<ExceptionHandlerMiddleware>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
namespace Ocelot.Errors
|
namespace Ocelot.Errors
|
||||||
{
|
{
|
||||||
public enum OcelotErrorCode
|
public enum OcelotErrorCode
|
||||||
{
|
{
|
||||||
UnauthenticatedError = 0,
|
UnauthenticatedError = 0,
|
||||||
UnknownError = 1,
|
UnknownError = 1,
|
||||||
DownstreampathTemplateAlreadyUsedError = 2,
|
DownstreampathTemplateAlreadyUsedError = 2,
|
||||||
UnableToFindDownstreamRouteError = 3,
|
UnableToFindDownstreamRouteError = 3,
|
||||||
CannotAddDataError = 4,
|
CannotAddDataError = 4,
|
||||||
CannotFindDataError = 5,
|
CannotFindDataError = 5,
|
||||||
UnableToCompleteRequestError = 6,
|
UnableToCompleteRequestError = 6,
|
||||||
UnableToCreateAuthenticationHandlerError = 7,
|
UnableToCreateAuthenticationHandlerError = 7,
|
||||||
UnsupportedAuthenticationProviderError = 8,
|
UnsupportedAuthenticationProviderError = 8,
|
||||||
CannotFindClaimError = 9,
|
CannotFindClaimError = 9,
|
||||||
ParsingConfigurationHeaderError = 10,
|
ParsingConfigurationHeaderError = 10,
|
||||||
NoInstructionsError = 11,
|
NoInstructionsError = 11,
|
||||||
InstructionNotForClaimsError = 12,
|
InstructionNotForClaimsError = 12,
|
||||||
UnauthorizedError = 13,
|
UnauthorizedError = 13,
|
||||||
ClaimValueNotAuthorisedError = 14,
|
ClaimValueNotAuthorisedError = 14,
|
||||||
ScopeNotAuthorisedError = 15,
|
ScopeNotAuthorisedError = 15,
|
||||||
UserDoesNotHaveClaimError = 16,
|
UserDoesNotHaveClaimError = 16,
|
||||||
DownstreamPathTemplateContainsSchemeError = 17,
|
DownstreamPathTemplateContainsSchemeError = 17,
|
||||||
DownstreamPathNullOrEmptyError = 18,
|
DownstreamPathNullOrEmptyError = 18,
|
||||||
DownstreamSchemeNullOrEmptyError = 19,
|
DownstreamSchemeNullOrEmptyError = 19,
|
||||||
DownstreamHostNullOrEmptyError = 20,
|
DownstreamHostNullOrEmptyError = 20,
|
||||||
ServicesAreNullError = 21,
|
ServicesAreNullError = 21,
|
||||||
ServicesAreEmptyError = 22,
|
ServicesAreEmptyError = 22,
|
||||||
UnableToFindServiceDiscoveryProviderError = 23,
|
UnableToFindServiceDiscoveryProviderError = 23,
|
||||||
UnableToFindLoadBalancerError = 24,
|
UnableToFindLoadBalancerError = 24,
|
||||||
RequestTimedOutError = 25,
|
RequestTimedOutError = 25,
|
||||||
UnableToFindQoSProviderError = 26,
|
UnableToFindQoSProviderError = 26,
|
||||||
UnmappableRequestError = 27,
|
UnmappableRequestError = 27,
|
||||||
RateLimitOptionsError = 28,
|
RateLimitOptionsError = 28,
|
||||||
PathTemplateDoesntStartWithForwardSlash = 29,
|
PathTemplateDoesntStartWithForwardSlash = 29,
|
||||||
FileValidationFailedError = 30,
|
FileValidationFailedError = 30,
|
||||||
UnableToFindDelegatingHandlerProviderError = 31,
|
UnableToFindDelegatingHandlerProviderError = 31,
|
||||||
CouldNotFindPlaceholderError = 32,
|
CouldNotFindPlaceholderError = 32,
|
||||||
CouldNotFindAggregatorError = 33,
|
CouldNotFindAggregatorError = 33,
|
||||||
CannotAddPlaceholderError = 34,
|
CannotAddPlaceholderError = 34,
|
||||||
CannotRemovePlaceholderError = 35,
|
CannotRemovePlaceholderError = 35,
|
||||||
QuotaExceededError = 36,
|
QuotaExceededError = 36,
|
||||||
RequestCanceled = 37,
|
RequestCanceled = 37,
|
||||||
ConnectionToDownstreamServiceError = 38,
|
ConnectionToDownstreamServiceError = 38,
|
||||||
CouldNotFindLoadBalancerCreator = 39,
|
CouldNotFindLoadBalancerCreator = 39,
|
||||||
ErrorInvokingLoadBalancerCreator = 40,
|
ErrorInvokingLoadBalancerCreator = 40,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ namespace Ocelot.Headers
|
|||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
||||||
{
|
{
|
||||||
@ -17,10 +19,10 @@ namespace Ocelot.Headers
|
|||||||
_placeholders = placeholders;
|
_placeholders = placeholders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs)
|
public Response Replace(HttpContext httpContext, List<HeaderFindAndReplace> fAndRs)
|
||||||
{
|
{
|
||||||
var response = context.DownstreamResponse;
|
var response = httpContext.Items.DownstreamResponse();
|
||||||
var request = context.DownstreamRequest;
|
var request = httpContext.Items.DownstreamRequest();
|
||||||
|
|
||||||
foreach (var f in fAndRs)
|
foreach (var f in fAndRs)
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
namespace Ocelot.Headers
|
namespace Ocelot.Headers
|
||||||
{
|
{
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Middleware;
|
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
public interface IHttpResponseHeaderReplacer
|
|
||||||
{
|
public interface IHttpResponseHeaderReplacer
|
||||||
Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs);
|
{
|
||||||
}
|
public Response Replace(HttpContext httpContext, List<HeaderFindAndReplace> fAndRs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,50 @@
|
|||||||
using Ocelot.Logging;
|
namespace Ocelot.Headers.Middleware
|
||||||
using Ocelot.Middleware;
|
{
|
||||||
using System.Linq;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System.Threading.Tasks;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class ClaimsToHeadersMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IAddHeadersToRequest _addHeadersToRequest;
|
||||||
|
|
||||||
|
public ClaimsToHeadersMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IAddHeadersToRequest addHeadersToRequest)
|
||||||
|
: base(loggerFactory.CreateLogger<ClaimsToHeadersMiddleware>())
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_addHeadersToRequest = addHeadersToRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
|
if (downstreamReRoute.ClaimsToHeaders.Any())
|
||||||
|
{
|
||||||
|
Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers");
|
||||||
|
|
||||||
namespace Ocelot.Headers.Middleware
|
var downstreamRequest = httpContext.Items.DownstreamRequest();
|
||||||
{
|
|
||||||
public class ClaimsToHeadersMiddleware : OcelotMiddleware
|
var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(downstreamReRoute.ClaimsToHeaders, httpContext.User.Claims, downstreamRequest);
|
||||||
{
|
|
||||||
private readonly OcelotRequestDelegate _next;
|
if (response.IsError)
|
||||||
private readonly IAddHeadersToRequest _addHeadersToRequest;
|
{
|
||||||
|
Logger.LogWarning("Error setting headers on context, setting pipeline error");
|
||||||
public ClaimsToHeadersMiddleware(OcelotRequestDelegate next,
|
|
||||||
IOcelotLoggerFactory loggerFactory,
|
httpContext.Items.UpsertErrors(response.Errors);
|
||||||
IAddHeadersToRequest addHeadersToRequest)
|
return;
|
||||||
: base(loggerFactory.CreateLogger<ClaimsToHeadersMiddleware>())
|
}
|
||||||
{
|
|
||||||
_next = next;
|
Logger.LogInformation("headers have been set on context");
|
||||||
_addHeadersToRequest = addHeadersToRequest;
|
}
|
||||||
}
|
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
public async Task Invoke(DownstreamContext context)
|
}
|
||||||
{
|
}
|
||||||
if (context.DownstreamReRoute.ClaimsToHeaders.Any())
|
}
|
||||||
{
|
|
||||||
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers");
|
|
||||||
|
|
||||||
var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest);
|
|
||||||
|
|
||||||
if (response.IsError)
|
|
||||||
{
|
|
||||||
Logger.LogWarning("Error setting headers on context, setting pipeline error");
|
|
||||||
|
|
||||||
SetPipelineError(context, response.Errors);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.LogInformation("headers have been set on context");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _next.Invoke(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Ocelot.Middleware.Pipeline;
|
|
||||||
|
|
||||||
namespace Ocelot.Headers.Middleware
|
namespace Ocelot.Headers.Middleware
|
||||||
{
|
{
|
||||||
public static class ClaimsToHeadersMiddlewareExtensions
|
public static class ClaimsToHeadersMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseClaimsToHeadersMiddleware(this IOcelotPipelineBuilder builder)
|
public static IApplicationBuilder UseClaimsToHeadersMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<ClaimsToHeadersMiddleware>();
|
return builder.UseMiddleware<ClaimsToHeadersMiddleware>();
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.Headers.Middleware
|
namespace Ocelot.Headers.Middleware
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
|
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
||||||
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
||||||
private readonly IAddHeadersToResponse _addHeadersToResponse;
|
private readonly IAddHeadersToResponse _addHeadersToResponse;
|
||||||
private readonly IAddHeadersToRequest _addHeadersToRequest;
|
private readonly IAddHeadersToRequest _addHeadersToRequest;
|
||||||
|
|
||||||
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
|
public HttpHeadersTransformationMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IHttpContextRequestHeaderReplacer preReplacer,
|
IHttpContextRequestHeaderReplacer preReplacer,
|
||||||
IHttpResponseHeaderReplacer postReplacer,
|
IHttpResponseHeaderReplacer postReplacer,
|
||||||
IAddHeadersToResponse addHeadersToResponse,
|
IAddHeadersToResponse addHeadersToResponse,
|
||||||
IAddHeadersToRequest addHeadersToRequest)
|
IAddHeadersToRequest addHeadersToRequest
|
||||||
|
)
|
||||||
: base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>())
|
: base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>())
|
||||||
{
|
{
|
||||||
_addHeadersToResponse = addHeadersToResponse;
|
_addHeadersToResponse = addHeadersToResponse;
|
||||||
@ -27,27 +30,33 @@ namespace Ocelot.Headers.Middleware
|
|||||||
_preReplacer = preReplacer;
|
_preReplacer = preReplacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var preFAndRs = context.DownstreamReRoute.UpstreamHeadersFindAndReplace;
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
|
var preFAndRs = downstreamReRoute.UpstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
//todo - this should be on httprequestmessage not httpcontext?
|
//todo - this should be on httprequestmessage not httpcontext?
|
||||||
_preReplacer.Replace(context.HttpContext, preFAndRs);
|
_preReplacer.Replace(httpContext, preFAndRs);
|
||||||
|
|
||||||
_addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext);
|
_addHeadersToRequest.SetHeadersOnDownstreamRequest(downstreamReRoute.AddHeadersToUpstream, httpContext);
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(httpContext);
|
||||||
|
|
||||||
if (context.IsError)
|
// todo check errors is ok
|
||||||
|
//todo put this check on the base class?
|
||||||
|
if (httpContext.Items.Errors().Count > 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
|
var postFAndRs = downstreamReRoute.DownstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
_postReplacer.Replace(context, postFAndRs);
|
_postReplacer.Replace(httpContext, postFAndRs);
|
||||||
|
|
||||||
_addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
|
var downstreamResponse = httpContext.Items.DownstreamResponse();
|
||||||
|
|
||||||
|
_addHeadersToResponse.Add(downstreamReRoute.AddHeadersToDownstream, downstreamResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
namespace Ocelot.Headers.Middleware
|
||||||
using Ocelot.Middleware.Pipeline;
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
namespace Ocelot.Headers.Middleware
|
|
||||||
{
|
public static class HttpHeadersTransformationMiddlewareExtensions
|
||||||
public static class HttpHeadersTransformationMiddlewareExtensions
|
{
|
||||||
{
|
public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder)
|
||||||
public static IOcelotPipelineBuilder UseHttpHeadersTransformationMiddleware(this IOcelotPipelineBuilder builder)
|
{
|
||||||
{
|
return builder.UseMiddleware<HttpHeadersTransformationMiddleware>();
|
||||||
return builder.UseMiddleware<HttpHeadersTransformationMiddleware>();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure
|
namespace Ocelot.Infrastructure
|
||||||
{
|
{
|
||||||
public class CannotAddPlaceholderError : Error
|
public class CannotAddPlaceholderError : Error
|
||||||
{
|
{
|
||||||
public CannotAddPlaceholderError(string message)
|
public CannotAddPlaceholderError(string message)
|
||||||
: base(message, OcelotErrorCode.CannotAddPlaceholderError)
|
: base(message, OcelotErrorCode.CannotAddPlaceholderError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ namespace Ocelot.Infrastructure
|
|||||||
public class CannotRemovePlaceholderError : Error
|
public class CannotRemovePlaceholderError : Error
|
||||||
{
|
{
|
||||||
public CannotRemovePlaceholderError(string message)
|
public CannotRemovePlaceholderError(string message)
|
||||||
: base(message, OcelotErrorCode.CannotRemovePlaceholderError)
|
: base(message, OcelotErrorCode.CannotRemovePlaceholderError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
namespace Ocelot.Infrastructure.Claims.Parser
|
namespace Ocelot.Infrastructure.Claims.Parser
|
||||||
{
|
{
|
||||||
using Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
public class CannotFindClaimError : Error
|
public class CannotFindClaimError : Error
|
||||||
{
|
{
|
||||||
public CannotFindClaimError(string message)
|
public CannotFindClaimError(string message)
|
||||||
: base(message, OcelotErrorCode.CannotFindClaimError)
|
: base(message, OcelotErrorCode.CannotFindClaimError, 403)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ namespace Ocelot.Infrastructure
|
|||||||
public class CouldNotFindPlaceholderError : Error
|
public class CouldNotFindPlaceholderError : Error
|
||||||
{
|
{
|
||||||
public CouldNotFindPlaceholderError(string placeholder)
|
public CouldNotFindPlaceholderError(string placeholder)
|
||||||
: base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError)
|
: base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure.RequestData
|
namespace Ocelot.Infrastructure.RequestData
|
||||||
{
|
{
|
||||||
public class CannotAddDataError : Error
|
public class CannotAddDataError : Error
|
||||||
{
|
{
|
||||||
public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError)
|
public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure.RequestData
|
namespace Ocelot.Infrastructure.RequestData
|
||||||
{
|
{
|
||||||
public class CannotFindDataError : Error
|
public class CannotFindDataError : Error
|
||||||
{
|
{
|
||||||
public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError)
|
public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
{
|
{
|
||||||
using Ocelot.Infrastructure;
|
using Ocelot.Infrastructure;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Responses;
|
using Ocelot.Responses;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Values;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
public class CookieStickySessions : ILoadBalancer
|
public class CookieStickySessions : ILoadBalancer
|
||||||
{
|
{
|
||||||
@ -41,9 +42,9 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var key = context.HttpContext.Request.Cookies[_key];
|
var key = httpContext.Request.Cookies[_key];
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
@ -61,7 +62,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var next = await _loadBalancer.Lease(context);
|
var next = await _loadBalancer.Lease(httpContext);
|
||||||
|
|
||||||
if (next.IsError)
|
if (next.IsError)
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
public class CouldNotFindLoadBalancerCreator : Error
|
public class CouldNotFindLoadBalancerCreator : Error
|
||||||
{
|
{
|
||||||
public CouldNotFindLoadBalancerCreator(string message)
|
public CouldNotFindLoadBalancerCreator(string message)
|
||||||
: base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator)
|
: base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
public class ErrorInvokingLoadBalancerCreator : Error
|
public class ErrorInvokingLoadBalancerCreator : Error
|
||||||
{
|
{
|
||||||
public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator)
|
public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator, 500)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
using Ocelot.Middleware;
|
|
||||||
using Ocelot.Responses;
|
|
||||||
using Ocelot.Values;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public interface ILoadBalancer
|
public interface ILoadBalancer
|
||||||
{
|
{
|
||||||
Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context);
|
Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext);
|
||||||
|
|
||||||
void Release(ServiceHostAndPort hostAndPort);
|
void Release(ServiceHostAndPort hostAndPort);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
using Ocelot.Middleware;
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
using Ocelot.Responses;
|
|
||||||
using Ocelot.Values;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class LeastConnection : ILoadBalancer
|
public class LeastConnection : ILoadBalancer
|
||||||
{
|
{
|
||||||
private readonly Func<Task<List<Service>>> _services;
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
@ -22,7 +23,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
_leases = new List<Lease>();
|
_leases = new List<Lease>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var services = await _services.Invoke();
|
var services = await _services.Invoke();
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
using Ocelot.Middleware;
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
using Ocelot.Responses;
|
|
||||||
using Ocelot.Values;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class NoLoadBalancer : ILoadBalancer
|
public class NoLoadBalancer : ILoadBalancer
|
||||||
{
|
{
|
||||||
private readonly Func<Task<List<Service>>> _services;
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
@ -17,7 +18,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
_services = services;
|
_services = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var services = await _services();
|
var services = await _services();
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
public class RoundRobin : ILoadBalancer
|
public class RoundRobin : ILoadBalancer
|
||||||
{
|
{
|
||||||
@ -19,7 +20,7 @@
|
|||||||
_services = services;
|
_services = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var services = await _services();
|
var services = await _services();
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
@ -5,8 +5,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
public class ServicesAreEmptyError : Error
|
public class ServicesAreEmptyError : Error
|
||||||
{
|
{
|
||||||
public ServicesAreEmptyError(string message)
|
public ServicesAreEmptyError(string message)
|
||||||
: base(message, OcelotErrorCode.ServicesAreEmptyError)
|
: base(message, OcelotErrorCode.ServicesAreEmptyError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
public class ServicesAreNullError : Error
|
public class ServicesAreNullError : Error
|
||||||
{
|
{
|
||||||
public ServicesAreNullError(string message)
|
public ServicesAreNullError(string message)
|
||||||
: base(message, OcelotErrorCode.ServicesAreNullError)
|
: base(message, OcelotErrorCode.ServicesAreNullError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
public class UnableToFindLoadBalancerError : Errors.Error
|
public class UnableToFindLoadBalancerError : Errors.Error
|
||||||
{
|
{
|
||||||
public UnableToFindLoadBalancerError(string message)
|
public UnableToFindLoadBalancerError(string message)
|
||||||
: base(message, OcelotErrorCode.UnableToFindLoadBalancerError)
|
: base(message, OcelotErrorCode.UnableToFindLoadBalancerError, 404)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,68 +1,78 @@
|
|||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
namespace Ocelot.LoadBalancer.Middleware
|
||||||
using Ocelot.Logging;
|
{
|
||||||
using Ocelot.Middleware;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
using System.Threading.Tasks;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class LoadBalancingMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||||
|
|
||||||
|
public LoadBalancingMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
ILoadBalancerHouse loadBalancerHouse)
|
||||||
|
: base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_loadBalancerHouse = loadBalancerHouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var downstreamReRoute = httpContext.Items.DownstreamReRoute();
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.Middleware
|
var internalConfiguration = httpContext.Items.IInternalConfiguration();
|
||||||
{
|
|
||||||
public class LoadBalancingMiddleware : OcelotMiddleware
|
var loadBalancer = _loadBalancerHouse.Get(downstreamReRoute, internalConfiguration.ServiceProviderConfiguration);
|
||||||
{
|
|
||||||
private readonly OcelotRequestDelegate _next;
|
if (loadBalancer.IsError)
|
||||||
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
{
|
||||||
|
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
|
||||||
public LoadBalancingMiddleware(OcelotRequestDelegate next,
|
httpContext.Items.UpsertErrors(loadBalancer.Errors);
|
||||||
IOcelotLoggerFactory loggerFactory,
|
return;
|
||||||
ILoadBalancerHouse loadBalancerHouse)
|
}
|
||||||
: base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
|
|
||||||
{
|
var hostAndPort = await loadBalancer.Data.Lease(httpContext);
|
||||||
_next = next;
|
if (hostAndPort.IsError)
|
||||||
_loadBalancerHouse = loadBalancerHouse;
|
{
|
||||||
}
|
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
|
||||||
|
httpContext.Items.UpsertErrors(hostAndPort.Errors);
|
||||||
public async Task Invoke(DownstreamContext context)
|
return;
|
||||||
{
|
|
||||||
var loadBalancer = _loadBalancerHouse.Get(context.DownstreamReRoute, context.Configuration.ServiceProviderConfiguration);
|
|
||||||
if (loadBalancer.IsError)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
|
|
||||||
SetPipelineError(context, loadBalancer.Errors);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostAndPort = await loadBalancer.Data.Lease(context);
|
var downstreamRequest = httpContext.Items.DownstreamRequest();
|
||||||
if (hostAndPort.IsError)
|
|
||||||
{
|
//todo check downstreamRequest is ok
|
||||||
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
|
downstreamRequest.Host = hostAndPort.Data.DownstreamHost;
|
||||||
SetPipelineError(context, hostAndPort.Errors);
|
|
||||||
return;
|
if (hostAndPort.Data.DownstreamPort > 0)
|
||||||
}
|
{
|
||||||
|
downstreamRequest.Port = hostAndPort.Data.DownstreamPort;
|
||||||
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
|
}
|
||||||
|
|
||||||
if (hostAndPort.Data.DownstreamPort > 0)
|
if (!string.IsNullOrEmpty(hostAndPort.Data.Scheme))
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
|
downstreamRequest.Scheme = hostAndPort.Data.Scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(hostAndPort.Data.Scheme))
|
try
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.Scheme = hostAndPort.Data.Scheme;
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
try
|
{
|
||||||
{
|
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
|
||||||
await _next.Invoke(context);
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
finally
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
|
loadBalancer.Data.Release(hostAndPort.Data);
|
||||||
throw;
|
}
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
}
|
||||||
loadBalancer.Data.Release(hostAndPort.Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
namespace Ocelot.LoadBalancer.Middleware
|
||||||
using Ocelot.Middleware.Pipeline;
|
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.Middleware
|
|
||||||
{
|
{
|
||||||
public static class LoadBalancingMiddlewareExtensions
|
using Microsoft.AspNetCore.Builder;
|
||||||
{
|
|
||||||
public static IOcelotPipelineBuilder UseLoadBalancingMiddleware(this IOcelotPipelineBuilder builder)
|
public static class LoadBalancingMiddlewareExtensions
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<LoadBalancingMiddleware>();
|
public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder)
|
||||||
}
|
{
|
||||||
}
|
return builder.UseMiddleware<LoadBalancingMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
namespace Ocelot.Logging
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.DiagnosticAdapter;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ocelot.Logging
|
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DiagnosticAdapter;
|
||||||
|
using System;
|
||||||
|
|
||||||
public class OcelotDiagnosticListener
|
public class OcelotDiagnosticListener
|
||||||
{
|
{
|
||||||
private readonly IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
@ -17,27 +16,6 @@ namespace Ocelot.Logging
|
|||||||
_tracer = serviceProvider.GetService<ITracer>();
|
_tracer = serviceProvider.GetService<ITracer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[DiagnosticName("Ocelot.MiddlewareException")]
|
|
||||||
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
|
|
||||||
{
|
|
||||||
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};");
|
|
||||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[DiagnosticName("Ocelot.MiddlewareStarted")]
|
|
||||||
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
|
|
||||||
{
|
|
||||||
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
|
||||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[DiagnosticName("Ocelot.MiddlewareFinished")]
|
|
||||||
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
|
|
||||||
{
|
|
||||||
_logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
|
||||||
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
|
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
|
||||||
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
|
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
|
||||||
{
|
{
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Ocelot.Middleware
|
namespace Ocelot.Middleware
|
||||||
{
|
{
|
||||||
public class BaseUrlFinder : IBaseUrlFinder
|
public class BaseUrlFinder : IBaseUrlFinder
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
|
||||||
public BaseUrlFinder(IConfiguration config)
|
public BaseUrlFinder(IConfiguration config)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Find()
|
public string Find()
|
||||||
{
|
{
|
||||||
//tries to get base url out of file...
|
//tries to get base url out of file...
|
||||||
var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", "");
|
var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", "");
|
||||||
|
|
||||||
//falls back to memory config then finally default..
|
//falls back to memory config then finally default..
|
||||||
return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl;
|
return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
src/Ocelot/Middleware/ConfigurationMiddleware.cs
Normal file
37
src/Ocelot/Middleware/ConfigurationMiddleware.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
namespace Ocelot.Middleware
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Errors.Middleware;
|
||||||
|
using Ocelot.Configuration.Repository;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
|
|
||||||
|
public class ConfigurationMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IInternalConfigurationRepository _configRepo;
|
||||||
|
|
||||||
|
public ConfigurationMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IInternalConfigurationRepository configRepo)
|
||||||
|
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_configRepo = configRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
//todo check the config is actually ok?
|
||||||
|
var config = _configRepo.Get();
|
||||||
|
|
||||||
|
if(config.IsError)
|
||||||
|
{
|
||||||
|
throw new System.Exception("OOOOPS this should not happen raise an issue in GitHub");
|
||||||
|
}
|
||||||
|
|
||||||
|
httpContext.Items.SetIInternalConfiguration(config.Data);
|
||||||
|
|
||||||
|
await _next.Invoke(httpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
|
||||||
using Ocelot.Errors;
|
|
||||||
using Ocelot.Request.Middleware;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ocelot.Middleware
|
|
||||||
{
|
|
||||||
public class DownstreamContext
|
|
||||||
{
|
|
||||||
public DownstreamContext(HttpContext httpContext)
|
|
||||||
{
|
|
||||||
HttpContext = httpContext;
|
|
||||||
Errors = new List<Error>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; set; }
|
|
||||||
|
|
||||||
public HttpContext HttpContext { get; }
|
|
||||||
|
|
||||||
public DownstreamReRoute DownstreamReRoute { get; set; }
|
|
||||||
|
|
||||||
public DownstreamRequest DownstreamRequest { get; set; }
|
|
||||||
|
|
||||||
public DownstreamResponse DownstreamResponse { get; set; }
|
|
||||||
|
|
||||||
public List<Error> Errors { get; }
|
|
||||||
|
|
||||||
public IInternalConfiguration Configuration { get; set; }
|
|
||||||
|
|
||||||
public bool IsError => Errors.Count > 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ocelot.Middleware
|
||||||
|
{
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
public static class DownstreamContextMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseDownstreamContextMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<ConfigurationMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
src/Ocelot/Middleware/HttpItemsExtensions.cs
Normal file
117
src/Ocelot/Middleware/HttpItemsExtensions.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
namespace Ocelot.Middleware
|
||||||
|
{
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.DownstreamRouteFinder;
|
||||||
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public static class HttpItemsExtensions
|
||||||
|
{
|
||||||
|
public static void UpsertDownstreamRequest(this IDictionary<object, object> input, DownstreamRequest downstreamRequest)
|
||||||
|
{
|
||||||
|
input.Upsert("DownstreamRequest", downstreamRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpsertDownstreamResponse(this IDictionary<object, object> input, DownstreamResponse downstreamResponse)
|
||||||
|
{
|
||||||
|
input.Upsert("DownstreamResponse", downstreamResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpsertDownstreamReRoute(this IDictionary<object, object> input, DownstreamReRoute downstreamReRoute)
|
||||||
|
{
|
||||||
|
input.Upsert("DownstreamReRoute", downstreamReRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpsertTemplatePlaceholderNameAndValues(this IDictionary<object, object> input, List<PlaceholderNameAndValue> tPNV)
|
||||||
|
{
|
||||||
|
input.Upsert("TemplatePlaceholderNameAndValues", tPNV);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpsertDownstreamRoute(this IDictionary<object, object> input, DownstreamRoute downstreamRoute)
|
||||||
|
{
|
||||||
|
input.Upsert("DownstreamRoute", downstreamRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpsertErrors(this IDictionary<object, object> input, List<Error> errors)
|
||||||
|
{
|
||||||
|
input.Upsert("Errors", errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetError(this IDictionary<object, object> input, Error error)
|
||||||
|
{
|
||||||
|
var errors = new List<Error>() { error };
|
||||||
|
input.Upsert("Errors", errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetIInternalConfiguration(this IDictionary<object, object> input, IInternalConfiguration config)
|
||||||
|
{
|
||||||
|
input.Upsert("IInternalConfiguration", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IInternalConfiguration IInternalConfiguration(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
return input.Get<IInternalConfiguration>("IInternalConfiguration");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Error> Errors(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
var errors = input.Get<List<Error>>("Errors");
|
||||||
|
return errors == null ? new List<Error>() : errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownstreamRoute DownstreamRoute(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
return input.Get<DownstreamRoute>("DownstreamRoute");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
return input.Get<List<PlaceholderNameAndValue>>("TemplatePlaceholderNameAndValues");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownstreamRequest DownstreamRequest(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
return input.Get<DownstreamRequest>("DownstreamRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownstreamResponse DownstreamResponse(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
return input.Get<DownstreamResponse>("DownstreamResponse");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownstreamReRoute DownstreamReRoute(this IDictionary<object, object> input)
|
||||||
|
{
|
||||||
|
return input.Get<DownstreamReRoute>("DownstreamReRoute");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T Get<T>(this IDictionary<object, object> input, string key)
|
||||||
|
{
|
||||||
|
if (input.TryGetValue(key, out var value))
|
||||||
|
{
|
||||||
|
return (T)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Upsert<T>(this IDictionary<object, object> input, string key, T value)
|
||||||
|
{
|
||||||
|
if (input.DoesntExist(key))
|
||||||
|
{
|
||||||
|
input.Add(key, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
input.Remove(key);
|
||||||
|
input.Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DoesntExist(this IDictionary<object, object> input, string key)
|
||||||
|
{
|
||||||
|
return !input.ContainsKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.Middleware.Multiplexer
|
|
||||||
{
|
|
||||||
public interface IDefinedAggregator
|
|
||||||
{
|
|
||||||
Task<DownstreamResponse> Aggregate(List<DownstreamContext> responses);
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user