mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 02:42:52 +08:00
commit
b334ed8b3a
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,7 +6,7 @@
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
.DS_Store
|
*.DS_Store
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|
||||||
|
@ -1,113 +1,139 @@
|
|||||||
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
|
internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own
|
||||||
IdentityServer.
|
IdentityServer.
|
||||||
|
|
||||||
Providing 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..
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
``Install-Package Ocelot.Administration``
|
||||||
All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method.
|
|
||||||
|
This will bring down everything needed by the admin API.
|
||||||
.. code-block:: csharp
|
|
||||||
|
Providing your own IdentityServer
|
||||||
public virtual void ConfigureServices(IServiceCollection services)
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
{
|
|
||||||
Action<IdentityServerAuthenticationOptions> options = o => {
|
All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method.
|
||||||
// o.Authority = ;
|
|
||||||
// o.ApiName = ;
|
.. code-block:: csharp
|
||||||
// etc....
|
|
||||||
};
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
services
|
Action<IdentityServerAuthenticationOptions> options = o => {
|
||||||
.AddOcelot()
|
// o.Authority = ;
|
||||||
.AddAdministration("/administration", options);
|
// o.ApiName = ;
|
||||||
}
|
// etc....
|
||||||
|
};
|
||||||
You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API.
|
|
||||||
|
services
|
||||||
This feature was implemented for `issue 228 <https://github.com/TomPallister/Ocelot/issues/228>`_. It is useful because the IdentityServer authentication
|
.AddOcelot()
|
||||||
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.
|
.AddAdministration("/administration", options);
|
||||||
|
}
|
||||||
Internal IdentityServer
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API.
|
||||||
|
|
||||||
The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing
|
This feature was implemented for `issue 228 <https://github.com/TomPallister/Ocelot/issues/228>`_. It is useful because the IdentityServer authentication
|
||||||
`Identity Server <https://github.com/IdentityServer/IdentityServer4>`_ project that I have been using for a few years now. Check them out.
|
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.
|
||||||
|
|
||||||
In order to enable the administration section you need to do a few things. First of all add this to your
|
Internal IdentityServer
|
||||||
initial Startup.cs.
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The path can be anything you want and it is obviously reccomended don't use
|
The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing
|
||||||
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
`Identity Server <https://github.com/IdentityServer/IdentityServer4>`_ project that I have been using for a few years now. Check them out.
|
||||||
MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not
|
|
||||||
to the Ocelot middleware.
|
In order to enable the administration section you need to do a few things. First of all add this to your
|
||||||
|
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
|
||||||
.. code-block:: csharp
|
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
||||||
|
MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not
|
||||||
public virtual void ConfigureServices(IServiceCollection services)
|
to the Ocelot middleware.
|
||||||
{
|
|
||||||
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!
|
||||||
.AddOcelot()
|
|
||||||
.AddAdministration("/administration", "secret");
|
.. code-block:: csharp
|
||||||
}
|
|
||||||
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
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
|
services
|
||||||
will need to be changed if you are running Ocelot on a different url to http://localhost:5000.
|
.AddOcelot()
|
||||||
|
.AddAdministration("/administration", "secret");
|
||||||
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.
|
|
||||||
|
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
|
||||||
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.
|
to global configuration if it is not default (http://localhost:5000). This can be done as follows..
|
||||||
|
|
||||||
In order to do this you need to add two more environmental variables for each Ocelot in the cluster.
|
If you want to run on a different host and port locally..
|
||||||
|
|
||||||
``OCELOT_CERTIFICATE``
|
.. code-block:: json
|
||||||
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``
|
"GlobalConfiguration": {
|
||||||
The password for the certificate.
|
"BaseUrl": "http://localhost:55580"
|
||||||
|
}
|
||||||
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!
|
|
||||||
|
or if Ocelot is exposed via dns
|
||||||
|
|
||||||
Administration API
|
.. code-block:: json
|
||||||
^^^^^^^^^^^^^^^^^^
|
|
||||||
|
"GlobalConfiguration": {
|
||||||
**POST {adminPath}/connect/token**
|
"BaseUrl": "http://mydns.com"
|
||||||
|
}
|
||||||
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.
|
|
||||||
|
Now if you went with the configuration options above and want to access the API you can use the postman scripts
|
||||||
The body of the request is form-data as follows
|
called 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.
|
||||||
``client_id`` set as admin
|
|
||||||
|
|
||||||
``client_secret`` set as whatever you used when setting up the administration services.
|
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.
|
||||||
``scope`` set as admin
|
|
||||||
|
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.
|
||||||
``grant_type`` set as client_credentials
|
|
||||||
|
In order to do this you need to add two more environmental variables for each Ocelot in the cluster.
|
||||||
**GET {adminPath}/configuration**
|
|
||||||
|
``OCELOT_CERTIFICATE``
|
||||||
|
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.
|
||||||
This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place.
|
``OCELOT_CERTIFICATE_PASSWORD``
|
||||||
|
The password for the certificate.
|
||||||
**POST {adminPath}/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 the cluster have the same certificate then you are good!
|
||||||
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 body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up
|
Administration API
|
||||||
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
|
**POST {adminPath}/connect/token**
|
||||||
where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save.
|
|
||||||
|
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.
|
||||||
**DELETE {adminPath}/outputcache/{region}**
|
|
||||||
|
The body of the request is form-data as follows
|
||||||
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.
|
|
||||||
|
``client_id`` set as admin
|
||||||
The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration.
|
|
||||||
|
``client_secret`` set as whatever you used when setting up the administration services.
|
||||||
|
|
||||||
|
``scope`` set as admin
|
||||||
|
|
||||||
|
``grant_type`` set as client_credentials
|
||||||
|
|
||||||
|
**GET {adminPath}/configuration**
|
||||||
|
|
||||||
|
|
||||||
|
This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place.
|
||||||
|
|
||||||
|
**POST {adminPath}/configuration**
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
@ -6,7 +6,15 @@ the `CacheManager <https://github.com/MichaCo/CacheManager>`_ project. This is a
|
|||||||
that is solving a lot of caching problems. I would reccomend using this package to
|
that is solving a lot of caching problems. I would reccomend using this package to
|
||||||
cache with Ocelot.
|
cache with Ocelot.
|
||||||
|
|
||||||
The following example shows how to add CacheManager to Ocelot so that you can do output caching. The first thing you need to do is add the following to your ConfigureServices..
|
The following example shows how to add CacheManager to Ocelot so that you can do output caching.
|
||||||
|
|
||||||
|
First of all add the following NuGet package.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Cache.CacheManager``
|
||||||
|
|
||||||
|
This will give you access to the Ocelot cache manager extension methods.
|
||||||
|
|
||||||
|
The second thing you need to do something like the following to your ConfigureServices..
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
@ -16,7 +24,7 @@ The following example shows how to add CacheManager to Ocelot so that you can do
|
|||||||
x.WithDictionaryHandle();
|
x.WithDictionaryHandle();
|
||||||
})
|
})
|
||||||
|
|
||||||
In order to use caching on a route in your ReRoute configuration add this setting.
|
Finally in order to use caching on a route in your ReRoute configuration add this setting.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
@ -24,11 +32,23 @@ In order to use caching on a route in your ReRoute configuration add this settin
|
|||||||
|
|
||||||
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/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager is setup and then passed into the Ocelot
|
If you look at the example `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager
|
||||||
AddOcelotOutputCaching configuration method. You can use any settings supported by
|
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.
|
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
|
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.
|
by calling Ocelot's administration API.
|
||||||
|
|
||||||
|
Your own caching
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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>()``
|
||||||
|
|
||||||
|
``IOcelotCache<CachedResponse>`` this is for output caching.
|
||||||
|
|
||||||
|
``IOcelotCache<FileConfiguration>`` this is for caching the file configuration if you are calling something remote to get your config such as Consul.
|
||||||
|
|
||||||
|
Please dig into the Ocelot source code to find more. I would really appreciate it if anyone wants to implement Redis, memcache etc..
|
||||||
|
|
||||||
|
@ -64,7 +64,6 @@ Here is an example ReRoute configuration, You don't need to set all of these thi
|
|||||||
"UseCookieContainer": true,
|
"UseCookieContainer": true,
|
||||||
"UseTracing": true
|
"UseTracing": true
|
||||||
},
|
},
|
||||||
"UseServiceDiscovery": false,
|
|
||||||
"DangerousAcceptAnyServerCertificateValidator": false
|
"DangerousAcceptAnyServerCertificateValidator": false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,13 +120,18 @@ At the moment there is no validation at this stage it only happens when Ocelot v
|
|||||||
Store configuration in consul
|
Store configuration in consul
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
|
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Provider.Consul``
|
||||||
|
|
||||||
|
Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddStoreOcelotConfigurationInConsul();
|
.AddConsul()
|
||||||
|
.AddConfigStoredInConsul();
|
||||||
|
|
||||||
You also need to add the following to your ocelot.json. This is how Ocelot
|
You also need to add the following to your ocelot.json. This is how Ocelot
|
||||||
finds your Consul agent and interacts to load and store the configuration from Consul.
|
finds your Consul agent and interacts to load and store the configuration from Consul.
|
||||||
@ -146,6 +150,16 @@ I guess it means if you want to use Ocelot to its fullest you take on Consul as
|
|||||||
|
|
||||||
This feature has a 3 second ttl cache before making a new request to your local consul agent.
|
This feature has a 3 second ttl cache before making a new request to your local consul agent.
|
||||||
|
|
||||||
|
Reload JSON config on change
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
|
||||||
|
|
||||||
Configuration Key
|
Configuration Key
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -1,111 +1,330 @@
|
|||||||
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 LeadConnection load balancer. This is the simplest way to get load balancing set up.
|
||||||
|
|
||||||
The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
{
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamHostAndPorts": [
|
||||||
"DownstreamScheme": "https",
|
{
|
||||||
"DownstreamHostAndPorts": [
|
"Host": "10.0.1.10",
|
||||||
{
|
"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": {
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"Type": "LeastConnection"
|
||||||
"LoadBalancerOptions": {
|
},
|
||||||
"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 LeadConnection load balancer.
|
||||||
|
|
||||||
The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer.
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
{
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"DownstreamScheme": "https",
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"ServiceName": "product",
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
"LoadBalancerOptions": {
|
||||||
"ServiceName": "product",
|
"Type": "LeastConnection"
|
||||||
"LoadBalancerOptions": {
|
},
|
||||||
"Type": "LeastConnection"
|
}
|
||||||
},
|
|
||||||
"UseServiceDiscovery": true
|
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.
|
||||||
|
|
||||||
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
|
CookieStickySessions
|
||||||
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
|
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>`_
|
||||||
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
|
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.
|
||||||
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
|
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
||||||
refreshes on every request which is meant to mimick how sessions work usually.
|
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
||||||
|
|
||||||
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
||||||
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
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.
|
||||||
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
||||||| merged common ancestors
|
||||||
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
Load Balancer
|
||||||
moment but could be changed.
|
=============
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You must choose in your configuration which load balancer to use.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.11",
|
||||||
|
"Port": 5000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection"
|
||||||
|
},
|
||||||
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Service Discovery
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"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. 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
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!
|
||||||
|
|
||||||
|
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": [
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.11",
|
||||||
|
"Port": 5000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "CookieStickySessions",
|
||||||
|
"Key": "ASP.NET_SessionId",
|
||||||
|
"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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
=======
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.11",
|
||||||
|
"Port": 5000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection"
|
||||||
|
},
|
||||||
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Service Discovery
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"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. 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
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!
|
||||||
|
|
||||||
|
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": [
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.11",
|
||||||
|
"Port": 5000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "CookieStickySessions",
|
||||||
|
"Key": "ASP.NET_SessionId",
|
||||||
|
"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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
@ -5,7 +5,22 @@ Ocelot supports one QoS capability at the current time. You can set on a per ReR
|
|||||||
want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
|
want to use a circuit breaker when making requests to a downstream service. This uses the 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>`_.
|
||||||
|
|
||||||
Add the following section to a ReRoute configuration.
|
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.Provider.Polly``
|
||||||
|
|
||||||
|
Then in your ConfigureServices method
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services
|
||||||
|
.AddOcelot()
|
||||||
|
.AddPolly();
|
||||||
|
}
|
||||||
|
|
||||||
|
Then add the following section to a ReRoute configuration.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
Ocelot has recenely integrated `Rafty <https://github.com/TomPallister/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/TomPallister/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 algorythm 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 algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server).
|
||||||
|
|
||||||
In order to enable Rafty in Ocelot you must make the following changes to your Startup.cs.
|
To get Raft support you must first install the Ocelot Rafty package.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Provider.Rafty``
|
||||||
|
|
||||||
|
Then you must make the following changes to your Startup.cs / Program.cs.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ Ocelot will also forward the request id with the specified header to the downstr
|
|||||||
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 reques tid feature you have two options.
|
In order to use the request id feature you have two options.
|
||||||
|
|
||||||
*Global*
|
*Global*
|
||||||
|
|
||||||
|
@ -211,7 +211,6 @@ Ocelot allows you to specify a querystring as part of the DownstreamPathTemplate
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
"UseServiceDiscovery": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,9 +238,8 @@ Ocelot will also allow you to put query string parameters in the UpstreamPathTem
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
"UseServiceDiscovery": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this
|
In this example Ocelot will only match requests that have a matching url path and the querystring 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.
|
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.
|
||||||
|
@ -11,6 +11,17 @@ 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.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Provider.Consul``
|
||||||
|
|
||||||
|
Then add the following to your ConfigureServices method.
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
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
|
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.
|
will be used.
|
||||||
|
|
||||||
@ -25,7 +36,7 @@ will be used.
|
|||||||
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
|
In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the
|
||||||
ServiceName, UseServiceDiscovery and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin
|
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.
|
and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
@ -39,7 +50,6 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel
|
|||||||
"LoadBalancerOptions": {
|
"LoadBalancerOptions": {
|
||||||
"Type": "LeastConnection"
|
"Type": "LeastConnection"
|
||||||
},
|
},
|
||||||
"UseServiceDiscovery": true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services.
|
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services.
|
||||||
@ -63,7 +73,7 @@ You services need to be added to Consul something like below (c# style but hopef
|
|||||||
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
|
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.
|
in address. After reading `this <https://www.consul.io/docs/agent/services.html>`_ I don't think the scheme should be in there.
|
||||||
|
|
||||||
.. code-block: json
|
.. code-block: csharp
|
||||||
|
|
||||||
new AgentService()
|
new AgentService()
|
||||||
{
|
{
|
||||||
@ -73,6 +83,17 @@ in address. After reading `this <https://www.consul.io/docs/agent/services.html>
|
|||||||
ID = "some-id",
|
ID = "some-id",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Or
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"Service": {
|
||||||
|
"ID": "some-id",
|
||||||
|
"Service": "some-service-name",
|
||||||
|
"Address": "localhost",
|
||||||
|
"Port": 8080
|
||||||
|
}
|
||||||
|
|
||||||
ACL Token
|
ACL Token
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@ -83,7 +104,8 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade
|
|||||||
"ServiceDiscoveryProvider": {
|
"ServiceDiscoveryProvider": {
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 8500,
|
"Port": 8500,
|
||||||
"Token": "footoken"
|
"Token": "footoken",
|
||||||
|
"Type": "Consul"
|
||||||
}
|
}
|
||||||
|
|
||||||
Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request.
|
Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request.
|
||||||
@ -95,7 +117,18 @@ This feature was requested as part of `Issue 262 <https://github.com/TomPalliste
|
|||||||
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
|
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.
|
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
|
||||||
|
|
||||||
In order to get this working add the following to ocelot.json..
|
The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Provider.Eureka``
|
||||||
|
|
||||||
|
Then add the following to your ConfigureServices method.
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
s.AddOcelot()
|
||||||
|
.AddEureka();
|
||||||
|
|
||||||
|
Then in order to get this working add the following to ocelot.json..
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
@ -125,20 +158,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them
|
|||||||
Dynamic Routing
|
Dynamic Routing
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using
|
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/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.
|
||||||
a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segmentof the upstream path to lookup the
|
|
||||||
downstream service with the service discovery provider.
|
|
||||||
|
|
||||||
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
|
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
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
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.
|
||||||
need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
|
|
||||||
|
|
||||||
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but
|
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.
|
||||||
talk to private services over http) that will be applied to all of the dynamic ReRoutes.
|
|
||||||
|
|
||||||
The config might look something like
|
The config might look something like
|
||||||
|
|
||||||
@ -183,4 +210,41 @@ The config might look something like
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DynamicReRoutes": [
|
||||||
|
{
|
||||||
|
"ServiceName": "product",
|
||||||
|
"RateLimitRule": {
|
||||||
|
"ClientWhitelist": [],
|
||||||
|
"EnableRateLimiting": true,
|
||||||
|
"Period": "1s",
|
||||||
|
"PeriodTimespan": 1000.0,
|
||||||
|
"Limit": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
"RequestIdKey": null,
|
||||||
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Host": "localhost",
|
||||||
|
"Port": 8523,
|
||||||
|
"Type": "Consul"
|
||||||
|
},
|
||||||
|
"RateLimitOptions": {
|
||||||
|
"ClientIdHeader": "ClientId",
|
||||||
|
"QuotaExceededMessage": "",
|
||||||
|
"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.
|
Please take a look through all of the docs to understand these options.
|
||||||
|
@ -4,7 +4,7 @@ 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 UseServiceDiscovery as true and set up the ServiceDiscoveryProvider in
|
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.
|
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!
|
||||||
@ -21,7 +21,6 @@ The example below is taken from the samples folder so please check it if this do
|
|||||||
],
|
],
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"ServiceName": "OcelotServiceApplication/OcelotApplicationService",
|
"ServiceName": "OcelotServiceApplication/OcelotApplicationService",
|
||||||
"UseServiceDiscovery" : true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
|
@ -1,19 +1,29 @@
|
|||||||
Tracing
|
Tracing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/ButterflyAPM>`_ project.
|
This page details how to perform distributed tracing with Ocelot. At the moment we only support Butterfly but other tracers might just work without
|
||||||
|
anything Ocelot specific.
|
||||||
|
|
||||||
|
Butterfly
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/liuhaoyang/butterfly>`_ project. The code for the Ocelot integration
|
||||||
|
can be found `here <https://github.com/ThreeMammals/Ocelot.Tracing.Butterfly>`_.
|
||||||
|
|
||||||
In order to use the tracing please read the Butterfly documentation.
|
In order to use the tracing please read the Butterfly documentation.
|
||||||
|
|
||||||
In ocelot you need to do the following if you wish to trace a ReRoute.
|
In ocelot you need to do the following if you wish to trace a ReRoute.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Tracing.Butterfly``
|
||||||
|
|
||||||
In your ConfigureServices method
|
In your ConfigureServices method
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddOpenTracing(option =>
|
// this comes from Ocelot.Tracing.Butterfly package
|
||||||
|
.AddButterfly(option =>
|
||||||
{
|
{
|
||||||
//this is the url that the butterfly collector server is running on...
|
//this is the url that the butterfly collector server is running on...
|
||||||
option.CollectorUrl = "http://localhost:9618";
|
option.CollectorUrl = "http://localhost:9618";
|
||||||
@ -28,4 +38,4 @@ Then in your ocelot.json add the following to the ReRoute you want to trace..
|
|||||||
"UseTracing": true
|
"UseTracing": true
|
||||||
},
|
},
|
||||||
|
|
||||||
Ocelot will now send tracing information to Butterfly when this ReRoute is called.
|
Ocelot will now send tracing information to Butterfly when this ReRoute is called.
|
||||||
|
@ -35,6 +35,52 @@ With this configuration set Ocelot will match any websocket traffic that comes i
|
|||||||
Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and
|
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.
|
proxy these to the upstream client.
|
||||||
|
|
||||||
|
SignalR
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
In your Configure method you need to tell your application to use SignalR.
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
Configure(app =>
|
||||||
|
{
|
||||||
|
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".
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"ReRoutes": [
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/{catchAll}",
|
||||||
|
"DownstreamScheme": "ws",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "localhost",
|
||||||
|
"Port": 50000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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.
|
||||||
|
|
||||||
Supported
|
Supported
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
"DownstreamPathTemplate": "/api/Category",
|
"DownstreamPathTemplate": "/api/Category",
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"UpstreamPathTemplate": "/Category",
|
"UpstreamPathTemplate": "/Category",
|
||||||
"UseServiceDiscovery": true,
|
|
||||||
"ServiceName": "ncore-rat",
|
"ServiceName": "ncore-rat",
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
"QoSOptions": {
|
"QoSOptions": {
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
"Get"
|
"Get"
|
||||||
],
|
],
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"ServiceName": "OcelotServiceApplication/OcelotApplicationService",
|
"ServiceName": "OcelotServiceApplication/OcelotApplicationService"
|
||||||
"UseServiceDiscovery" : true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
Logger.LogWarning("user scopes is not authorised setting pipeline error");
|
Logger.LogWarning("user scopes is not authorised setting pipeline error");
|
||||||
|
|
||||||
SetPipelineError(context, new UnauthorisedError(
|
SetPipelineError(context, new UnauthorisedError(
|
||||||
$"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}"));
|
$"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,19 +70,19 @@
|
|||||||
|
|
||||||
if (IsAuthorised(authorised))
|
if (IsAuthorised(authorised))
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}.");
|
Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}.");
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error");
|
Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error");
|
||||||
|
|
||||||
SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}"));
|
SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised");
|
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} route does not require user to be authorised");
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
using IdentityModel;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Responses;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Security.Claims;
|
||||||
using System.Security.Claims;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
namespace Ocelot.Authorisation
|
{
|
||||||
{
|
using Infrastructure.Claims.Parser;
|
||||||
using Infrastructure.Claims.Parser;
|
|
||||||
|
public class ScopesAuthoriser : IScopesAuthoriser
|
||||||
public class ScopesAuthoriser : IScopesAuthoriser
|
{
|
||||||
{
|
private readonly IClaimsParser _claimsParser;
|
||||||
private readonly IClaimsParser _claimsParser;
|
private readonly string _scope = "scope";
|
||||||
|
|
||||||
public ScopesAuthoriser(IClaimsParser claimsParser)
|
public ScopesAuthoriser(IClaimsParser claimsParser)
|
||||||
{
|
{
|
||||||
_claimsParser = claimsParser;
|
_claimsParser = claimsParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes)
|
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes)
|
||||||
{
|
{
|
||||||
if (routeAllowedScopes == null || routeAllowedScopes.Count == 0)
|
if (routeAllowedScopes == null || routeAllowedScopes.Count == 0)
|
||||||
{
|
{
|
||||||
return new OkResponse<bool>(true);
|
return new OkResponse<bool>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope);
|
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope);
|
||||||
|
|
||||||
if (values.IsError)
|
if (values.IsError)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<bool>(values.Errors);
|
return new ErrorResponse<bool>(values.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
var userScopes = values.Data;
|
var userScopes = values.Data;
|
||||||
|
|
||||||
var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList();
|
var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList();
|
||||||
|
|
||||||
if (matchesScopes.Count == 0)
|
if (matchesScopes.Count == 0)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<bool>(
|
return new ErrorResponse<bool>(
|
||||||
new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'"));
|
new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<bool>(true);
|
return new OkResponse<bool>(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/Ocelot/Cache/CacheObject.cs
Normal file
16
src/Ocelot/Cache/CacheObject.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace Ocelot.Cache
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
|
||||||
|
class CacheObject<T>
|
||||||
|
{
|
||||||
|
public CacheObject(T value, DateTime expires)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Expires = expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Value { get; }
|
||||||
|
public DateTime Expires { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Ocelot.Cache
|
||||||
namespace Ocelot.Cache
|
{
|
||||||
{
|
public interface IOcelotCache<T>
|
||||||
public interface IOcelotCache<T>
|
{
|
||||||
{
|
void Add(string key, T value, TimeSpan ttl, string region);
|
||||||
void Add(string key, T value, TimeSpan ttl, string region);
|
T Get(string key, string region);
|
||||||
void AddAndDelete(string key, T value, TimeSpan ttl, string region);
|
void ClearRegion(string region);
|
||||||
T Get(string key, string region);
|
void AddAndDelete(string key, T value, TimeSpan ttl, string region);
|
||||||
void ClearRegion(string region);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
81
src/Ocelot/Cache/InMemoryCache.cs
Normal file
81
src/Ocelot/Cache/InMemoryCache.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
namespace Ocelot.Cache
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class InMemoryCache<T> : IOcelotCache<T>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, CacheObject<T>> _cache;
|
||||||
|
private readonly Dictionary<string, List<string>> _regions;
|
||||||
|
|
||||||
|
public InMemoryCache()
|
||||||
|
{
|
||||||
|
_cache = new Dictionary<string, CacheObject<T>>();
|
||||||
|
_regions = new Dictionary<string, List<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string key, T value, TimeSpan ttl, string region)
|
||||||
|
{
|
||||||
|
if (ttl.TotalMilliseconds <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expires = DateTime.UtcNow.Add(ttl);
|
||||||
|
|
||||||
|
_cache.Add(key, new CacheObject<T>(value, expires));
|
||||||
|
|
||||||
|
if (_regions.ContainsKey(region))
|
||||||
|
{
|
||||||
|
var current = _regions[region];
|
||||||
|
if (!current.Contains(key))
|
||||||
|
{
|
||||||
|
current.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_regions.Add(region, new List<string>{ key });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
|
||||||
|
{
|
||||||
|
if (_cache.ContainsKey(key))
|
||||||
|
{
|
||||||
|
_cache.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(key, value, ttl, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearRegion(string region)
|
||||||
|
{
|
||||||
|
if (_regions.ContainsKey(region))
|
||||||
|
{
|
||||||
|
var keys = _regions[region];
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
_cache.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get(string key, string region)
|
||||||
|
{
|
||||||
|
if (_cache.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var cached = _cache[key];
|
||||||
|
|
||||||
|
if (cached.Expires > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
return cached.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +1,25 @@
|
|||||||
using System;
|
namespace Ocelot.Cache.Middleware
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using System.IO;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
|
||||||
|
|
||||||
namespace Ocelot.Cache.Middleware
|
|
||||||
{
|
{
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
public class OutputCacheMiddleware : OcelotMiddleware
|
public class OutputCacheMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly IOcelotCache<CachedResponse> _outputCache;
|
private readonly IOcelotCache<CachedResponse> _outputCache;
|
||||||
private readonly IRegionCreator _regionCreator;
|
|
||||||
|
|
||||||
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IOcelotCache<CachedResponse> outputCache,
|
IOcelotCache<CachedResponse> outputCache)
|
||||||
IRegionCreator regionCreator)
|
|
||||||
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_outputCache = outputCache;
|
_outputCache = outputCache;
|
||||||
_regionCreator = regionCreator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using CacheManager.Core;
|
|
||||||
|
|
||||||
namespace Ocelot.Cache
|
|
||||||
{
|
|
||||||
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
|
|
||||||
{
|
|
||||||
private readonly ICacheManager<T> _cacheManager;
|
|
||||||
|
|
||||||
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
|
|
||||||
{
|
|
||||||
_cacheManager = cacheManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(string key, T value, TimeSpan ttl, string region)
|
|
||||||
{
|
|
||||||
_cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
|
|
||||||
{
|
|
||||||
var exists = _cacheManager.Get(key);
|
|
||||||
|
|
||||||
if (exists != null)
|
|
||||||
{
|
|
||||||
_cacheManager.Remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Add(key, value, ttl, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Get(string key, string region)
|
|
||||||
{
|
|
||||||
return _cacheManager.Get<T>(key, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearRegion(string region)
|
|
||||||
{
|
|
||||||
_cacheManager.ClearRegion(region);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,14 @@
|
|||||||
using System.Collections.Generic;
|
namespace Ocelot.Cache
|
||||||
|
{
|
||||||
namespace Ocelot.Cache
|
using System.Collections.Generic;
|
||||||
{
|
|
||||||
public class Regions
|
public class Regions
|
||||||
{
|
{
|
||||||
public Regions(List<string> value)
|
public Regions(List<string> value)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> Value {get;private set;}
|
public List<string> Value { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private AuthenticationOptions _authenticationOptions;
|
private AuthenticationOptions _authenticationOptions;
|
||||||
private string _loadBalancerKey;
|
private string _loadBalancerKey;
|
||||||
private string _downstreamPathTemplate;
|
private string _downstreamPathTemplate;
|
||||||
private string _upstreamTemplate;
|
|
||||||
private UpstreamPathTemplate _upstreamTemplatePattern;
|
private UpstreamPathTemplate _upstreamTemplatePattern;
|
||||||
private List<HttpMethod> _upstreamHttpMethod;
|
private List<HttpMethod> _upstreamHttpMethod;
|
||||||
private bool _isAuthenticated;
|
private bool _isAuthenticated;
|
||||||
@ -79,13 +78,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithUpstreamPathTemplate(string input)
|
public DownstreamReRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input)
|
||||||
{
|
|
||||||
_upstreamTemplate = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input)
|
|
||||||
{
|
{
|
||||||
_upstreamTemplatePattern = input;
|
_upstreamTemplatePattern = input;
|
||||||
return this;
|
return this;
|
||||||
@ -245,7 +238,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
{
|
{
|
||||||
return new DownstreamReRoute(
|
return new DownstreamReRoute(
|
||||||
_key,
|
_key,
|
||||||
new PathTemplate(_upstreamTemplate),
|
_upstreamTemplatePattern,
|
||||||
_upstreamHeaderFindAndReplace,
|
_upstreamHeaderFindAndReplace,
|
||||||
_downstreamHeaderFindAndReplace,
|
_downstreamHeaderFindAndReplace,
|
||||||
_downstreamAddresses,
|
_downstreamAddresses,
|
||||||
@ -267,7 +260,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_isAuthenticated,
|
_isAuthenticated,
|
||||||
_isAuthorised,
|
_isAuthorised,
|
||||||
_authenticationOptions,
|
_authenticationOptions,
|
||||||
new PathTemplate(_downstreamPathTemplate),
|
new DownstreamPathTemplate(_downstreamPathTemplate),
|
||||||
_loadBalancerKey,
|
_loadBalancerKey,
|
||||||
_delegatingHandlers,
|
_delegatingHandlers,
|
||||||
_addHeadersToDownstream,
|
_addHeadersToDownstream,
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
namespace Ocelot.Configuration.Builder
|
||||||
using System.Net.Http;
|
|
||||||
using Ocelot.Values;
|
|
||||||
using System.Linq;
|
|
||||||
using Ocelot.Configuration.Creator;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Builder
|
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
public class ReRouteBuilder
|
public class ReRouteBuilder
|
||||||
{
|
{
|
||||||
private string _upstreamTemplate;
|
|
||||||
private UpstreamPathTemplate _upstreamTemplatePattern;
|
private UpstreamPathTemplate _upstreamTemplatePattern;
|
||||||
private List<HttpMethod> _upstreamHttpMethod;
|
private List<HttpMethod> _upstreamHttpMethod;
|
||||||
private string _upstreamHost;
|
private string _upstreamHost;
|
||||||
@ -39,13 +36,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReRouteBuilder WithUpstreamPathTemplate(string input)
|
public ReRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input)
|
||||||
{
|
|
||||||
_upstreamTemplate = input;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input)
|
|
||||||
{
|
{
|
||||||
_upstreamTemplatePattern = input;
|
_upstreamTemplatePattern = input;
|
||||||
return this;
|
return this;
|
||||||
@ -67,7 +58,6 @@ namespace Ocelot.Configuration.Builder
|
|||||||
{
|
{
|
||||||
return new ReRoute(
|
return new ReRoute(
|
||||||
_downstreamReRoutes,
|
_downstreamReRoutes,
|
||||||
new PathTemplate(_upstreamTemplate),
|
|
||||||
_upstreamHttpMethod,
|
_upstreamHttpMethod,
|
||||||
_upstreamTemplatePattern,
|
_upstreamTemplatePattern,
|
||||||
_upstreamHost,
|
_upstreamHost,
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
namespace Ocelot.Configuration.Builder
|
||||||
|
{
|
||||||
|
using Values;
|
||||||
|
|
||||||
|
public class UpstreamPathTemplateBuilder
|
||||||
|
{
|
||||||
|
private string _template;
|
||||||
|
private int _priority;
|
||||||
|
private bool _containsQueryString;
|
||||||
|
private string _originalValue;
|
||||||
|
|
||||||
|
public UpstreamPathTemplateBuilder WithTemplate(string template)
|
||||||
|
{
|
||||||
|
_template = template;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpstreamPathTemplateBuilder WithPriority(int priority)
|
||||||
|
{
|
||||||
|
_priority = priority;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpstreamPathTemplateBuilder WithContainsQueryString(bool containsQueryString)
|
||||||
|
{
|
||||||
|
_containsQueryString = containsQueryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpstreamPathTemplateBuilder WithOriginalValue(string originalValue)
|
||||||
|
{
|
||||||
|
_originalValue = originalValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpstreamPathTemplate Build()
|
||||||
|
{
|
||||||
|
return new UpstreamPathTemplate(_template, _priority, _containsQueryString, _originalValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,8 @@
|
|||||||
Region = region;
|
Region = region;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int TtlSeconds { get; private set; }
|
public int TtlSeconds { get; private set; }
|
||||||
public string Region {get;private set;}
|
|
||||||
|
public string Region { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using Ocelot.Configuration.Validator;
|
|||||||
using Ocelot.DependencyInjection;
|
using Ocelot.DependencyInjection;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
@ -49,14 +50,14 @@ namespace Ocelot.Configuration.Creator
|
|||||||
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
||||||
IRegionCreator regionCreator,
|
IRegionCreator regionCreator,
|
||||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
||||||
IAdministrationPath adminPath,
|
IServiceProvider serviceProvider,
|
||||||
IHeaderFindAndReplaceCreator headerFAndRCreator,
|
IHeaderFindAndReplaceCreator headerFAndRCreator,
|
||||||
IDownstreamAddressesCreator downstreamAddressesCreator
|
IDownstreamAddressesCreator downstreamAddressesCreator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_downstreamAddressesCreator = downstreamAddressesCreator;
|
_downstreamAddressesCreator = downstreamAddressesCreator;
|
||||||
_headerFAndRCreator = headerFAndRCreator;
|
_headerFAndRCreator = headerFAndRCreator;
|
||||||
_adminPath = adminPath;
|
_adminPath = serviceProvider.GetService<IAdministrationPath>();
|
||||||
_regionCreator = regionCreator;
|
_regionCreator = regionCreator;
|
||||||
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
||||||
_requestIdKeyCreator = requestIdKeyCreator;
|
_requestIdKeyCreator = requestIdKeyCreator;
|
||||||
@ -103,6 +104,12 @@ namespace Ocelot.Configuration.Creator
|
|||||||
reRoutes.Add(ocelotReRoute);
|
reRoutes.Add(ocelotReRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes)
|
||||||
|
{
|
||||||
|
var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration);
|
||||||
|
reRoutes.Add(reRoute);
|
||||||
|
}
|
||||||
|
|
||||||
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
||||||
|
|
||||||
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
|
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
|
||||||
@ -111,8 +118,10 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions);
|
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions);
|
||||||
|
|
||||||
|
var adminPath = _adminPath != null ? _adminPath.Path : null;
|
||||||
|
|
||||||
var config = new InternalConfiguration(reRoutes,
|
var config = new InternalConfiguration(reRoutes,
|
||||||
_adminPath.Path,
|
adminPath,
|
||||||
serviceProviderConfiguration,
|
serviceProviderConfiguration,
|
||||||
fileConfiguration.GlobalConfiguration.RequestIdKey,
|
fileConfiguration.GlobalConfiguration.RequestIdKey,
|
||||||
lbOptions,
|
lbOptions,
|
||||||
@ -124,7 +133,24 @@ namespace Ocelot.Configuration.Creator
|
|||||||
return new OkResponse<IInternalConfiguration>(config);
|
return new OkResponse<IInternalConfiguration>(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
|
{
|
||||||
|
var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
|
||||||
|
|
||||||
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithEnableRateLimiting(true)
|
||||||
|
.WithRateLimitOptions(rateLimitOption)
|
||||||
|
.WithServiceName(fileDynamicReRoute.ServiceName)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var reRoute = new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
return reRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
{
|
{
|
||||||
var applicableReRoutes = reRoutes
|
var applicableReRoutes = reRoutes
|
||||||
.SelectMany(x => x.DownstreamReRoute)
|
.SelectMany(x => x.DownstreamReRoute)
|
||||||
@ -140,9 +166,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute);
|
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute);
|
||||||
|
|
||||||
var reRoute = new ReRouteBuilder()
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate)
|
|
||||||
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
|
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
|
||||||
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
.WithUpstreamPathTemplate(upstreamTemplatePattern)
|
||||||
.WithDownstreamReRoutes(applicableReRoutes)
|
.WithDownstreamReRoutes(applicableReRoutes)
|
||||||
.WithUpstreamHost(aggregateReRoute.UpstreamHost)
|
.WithUpstreamHost(aggregateReRoute.UpstreamHost)
|
||||||
.WithAggregator(aggregateReRoute.Aggregator)
|
.WithAggregator(aggregateReRoute.Aggregator)
|
||||||
@ -156,9 +181,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var reRoute = new ReRouteBuilder()
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
|
||||||
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
||||||
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
.WithUpstreamPathTemplate(upstreamTemplatePattern)
|
||||||
.WithDownstreamReRoute(downstreamReRoutes)
|
.WithDownstreamReRoute(downstreamReRoutes)
|
||||||
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
||||||
.Build();
|
.Build();
|
||||||
@ -186,7 +210,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
|
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
|
||||||
|
|
||||||
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
|
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
|
||||||
|
|
||||||
var region = _regionCreator.Create(fileReRoute);
|
var region = _regionCreator.Create(fileReRoute);
|
||||||
|
|
||||||
@ -198,12 +222,13 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var lbOptions = CreateLoadBalancerOptions(fileReRoute.LoadBalancerOptions);
|
var lbOptions = CreateLoadBalancerOptions(fileReRoute.LoadBalancerOptions);
|
||||||
|
|
||||||
|
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName);
|
||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithKey(fileReRoute.Key)
|
.WithKey(fileReRoute.Key)
|
||||||
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
||||||
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
|
||||||
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
||||||
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
.WithUpstreamPathTemplate(upstreamTemplatePattern)
|
||||||
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
|
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
|
||||||
.WithAuthenticationOptions(authOptionsForRoute)
|
.WithAuthenticationOptions(authOptionsForRoute)
|
||||||
.WithClaimsToHeaders(claimsToHeaders)
|
.WithClaimsToHeaders(claimsToHeaders)
|
||||||
@ -223,7 +248,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
.WithRateLimitOptions(rateLimitOption)
|
.WithRateLimitOptions(rateLimitOption)
|
||||||
.WithHttpHandlerOptions(httpHandlerOptions)
|
.WithHttpHandlerOptions(httpHandlerOptions)
|
||||||
.WithServiceName(fileReRoute.ServiceName)
|
.WithServiceName(fileReRoute.ServiceName)
|
||||||
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
|
.WithUseServiceDiscovery(useServiceDiscovery)
|
||||||
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
|
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
|
||||||
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
||||||
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
using Butterfly.Client.Tracing;
|
namespace Ocelot.Configuration.Creator
|
||||||
using Ocelot.Configuration.File;
|
{
|
||||||
using Ocelot.Requester;
|
using System;
|
||||||
|
using Logging;
|
||||||
namespace Ocelot.Configuration.Creator
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
{
|
using Ocelot.Configuration.File;
|
||||||
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
|
|
||||||
{
|
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
|
||||||
private readonly IServiceTracer _tracer;
|
{
|
||||||
|
private readonly ITracer _tracer;
|
||||||
public HttpHandlerOptionsCreator(IServiceTracer tracer)
|
|
||||||
{
|
public HttpHandlerOptionsCreator(IServiceProvider services)
|
||||||
_tracer = tracer;
|
{
|
||||||
}
|
_tracer = services.GetService<ITracer>();
|
||||||
|
}
|
||||||
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
|
||||||
{
|
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
||||||
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing;
|
{
|
||||||
|
var useTracing = _tracer!= null && options.UseTracing;
|
||||||
return new HttpHandlerOptions(options.AllowAutoRedirect,
|
|
||||||
options.UseCookieContainer, useTracing, options.UseProxy);
|
return new HttpHandlerOptions(options.AllowAutoRedirect,
|
||||||
}
|
options.UseCookieContainer, useTracing, options.UseProxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public interface IRateLimitOptionsCreator
|
public interface IRateLimitOptionsCreator
|
||||||
{
|
{
|
||||||
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting);
|
RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
|
||||||
{
|
|
||||||
public static class IdentityServerConfigurationCreator
|
|
||||||
{
|
|
||||||
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
|
|
||||||
{
|
|
||||||
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
|
||||||
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
|
||||||
|
|
||||||
return new IdentityServerConfiguration(
|
|
||||||
"admin",
|
|
||||||
false,
|
|
||||||
secret,
|
|
||||||
new List<string> { "admin", "openid", "offline_access" },
|
|
||||||
credentialsSigningCertificateLocation,
|
|
||||||
credentialsSigningCertificatePassword
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,8 +17,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
public QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods)
|
public QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods)
|
||||||
{
|
{
|
||||||
var key = CreateKey(pathTemplate, httpMethods);
|
var key = CreateKey(pathTemplate, httpMethods);
|
||||||
|
|
||||||
return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking);
|
return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
using System;
|
using System;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public class RateLimitOptionsCreator : IRateLimitOptionsCreator
|
public class RateLimitOptionsCreator : IRateLimitOptionsCreator
|
||||||
{
|
{
|
||||||
public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting)
|
public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration)
|
||||||
{
|
{
|
||||||
RateLimitOptions rateLimitOption = null;
|
RateLimitOptions rateLimitOption = null;
|
||||||
|
|
||||||
if (enableRateLimiting)
|
if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting)
|
||||||
{
|
{
|
||||||
rateLimitOption = new RateLimitOptionsBuilder()
|
rateLimitOption = new RateLimitOptionsBuilder()
|
||||||
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
|
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
|
||||||
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist)
|
.WithClientWhiteList(fileRateLimitRule.ClientWhitelist)
|
||||||
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
|
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
|
||||||
.WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting)
|
.WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting)
|
||||||
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
|
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
|
||||||
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
|
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
|
||||||
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
|
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
|
||||||
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
|
.WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period,
|
||||||
fileReRoute.RateLimitOptions.PeriodTimespan,
|
fileRateLimitRule.PeriodTimespan,
|
||||||
fileReRoute.RateLimitOptions.Limit))
|
fileRateLimitRule.Limit))
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
return rateLimitOption;
|
return rateLimitOption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,45 @@
|
|||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public class ReRouteOptionsCreator : IReRouteOptionsCreator
|
public class ReRouteOptionsCreator : IReRouteOptionsCreator
|
||||||
{
|
{
|
||||||
public ReRouteOptions Create(FileReRoute fileReRoute)
|
public ReRouteOptions Create(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
var isAuthenticated = IsAuthenticated(fileReRoute);
|
var isAuthenticated = IsAuthenticated(fileReRoute);
|
||||||
var isAuthorised = IsAuthorised(fileReRoute);
|
var isAuthorised = IsAuthorised(fileReRoute);
|
||||||
var isCached = IsCached(fileReRoute);
|
var isCached = IsCached(fileReRoute);
|
||||||
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
|
var enableRateLimiting = IsEnableRateLimiting(fileReRoute);
|
||||||
|
|
||||||
var options = new ReRouteOptionsBuilder()
|
var options = new ReRouteOptionsBuilder()
|
||||||
.WithIsAuthenticated(isAuthenticated)
|
.WithIsAuthenticated(isAuthenticated)
|
||||||
.WithIsAuthorised(isAuthorised)
|
.WithIsAuthorised(isAuthorised)
|
||||||
.WithIsCached(isCached)
|
.WithIsCached(isCached)
|
||||||
.WithRateLimiting(enableRateLimiting)
|
.WithRateLimiting(enableRateLimiting)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
|
private static bool IsEnableRateLimiting(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
|
return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAuthenticated(FileReRoute fileReRoute)
|
private bool IsAuthenticated(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey);
|
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAuthorised(FileReRoute fileReRoute)
|
private bool IsAuthorised(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
return fileReRoute.RouteClaimsRequirement?.Count > 0;
|
return fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsCached(FileReRoute fileReRoute)
|
private bool IsCached(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
return fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,93 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
|
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
|
||||||
{
|
{
|
||||||
private const string RegExMatchOneOrMoreOfEverything = ".+";
|
private const string RegExMatchOneOrMoreOfEverything = ".+";
|
||||||
private const string RegExMatchEndString = "$";
|
private const string RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash = "[^/]+";
|
||||||
private const string RegExIgnoreCase = "(?i)";
|
private const string RegExMatchEndString = "$";
|
||||||
private const string RegExForwardSlashOnly = "^/$";
|
private const string RegExIgnoreCase = "(?i)";
|
||||||
private const string RegExForwardSlashAndOnePlaceHolder = "^/.*";
|
private const string RegExForwardSlashOnly = "^/$";
|
||||||
|
private const string RegExForwardSlashAndOnePlaceHolder = "^/.*";
|
||||||
public UpstreamPathTemplate Create(IReRoute reRoute)
|
|
||||||
{
|
public UpstreamPathTemplate Create(IReRoute reRoute)
|
||||||
|
{
|
||||||
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
||||||
|
|
||||||
|
var placeholders = new List<string>();
|
||||||
var placeholders = new List<string>();
|
|
||||||
|
for (var i = 0; i < upstreamTemplate.Length; i++)
|
||||||
for (var i = 0; i < upstreamTemplate.Length; i++)
|
{
|
||||||
{
|
if (IsPlaceHolder(upstreamTemplate, i))
|
||||||
if (IsPlaceHolder(upstreamTemplate, i))
|
{
|
||||||
{
|
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
|
||||||
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
|
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
|
||||||
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
|
var placeHolderName = upstreamTemplate.Substring(i, difference);
|
||||||
var placeHolderName = upstreamTemplate.Substring(i, difference);
|
placeholders.Add(placeHolderName);
|
||||||
placeholders.Add(placeHolderName);
|
|
||||||
|
//hack to handle /{url} case
|
||||||
//hack to handle /{url} case
|
if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket))
|
||||||
if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket))
|
{
|
||||||
{
|
return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false, reRoute.UpstreamPathTemplate);
|
||||||
return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var containsQueryString = false;
|
var containsQueryString = false;
|
||||||
|
|
||||||
if (upstreamTemplate.Contains("?"))
|
if (upstreamTemplate.Contains("?"))
|
||||||
{
|
{
|
||||||
containsQueryString = true;
|
containsQueryString = true;
|
||||||
upstreamTemplate = upstreamTemplate.Replace("?", "\\?");
|
upstreamTemplate = upstreamTemplate.Replace("?", "\\?");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var placeholder in placeholders)
|
for (int i = 0; i < placeholders.Count; i++)
|
||||||
{
|
{
|
||||||
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchOneOrMoreOfEverything);
|
var indexOfPlaceholder = upstreamTemplate.IndexOf(placeholders[i]);
|
||||||
}
|
var indexOfNextForwardSlash = upstreamTemplate.IndexOf("/", indexOfPlaceholder);
|
||||||
|
if(indexOfNextForwardSlash < indexOfPlaceholder || (containsQueryString && upstreamTemplate.IndexOf("?") < upstreamTemplate.IndexOf(placeholders[i])))
|
||||||
if (upstreamTemplate == "/")
|
{
|
||||||
{
|
upstreamTemplate = upstreamTemplate.Replace(placeholders[i], RegExMatchOneOrMoreOfEverything);
|
||||||
return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString);
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
if(upstreamTemplate.EndsWith("/"))
|
upstreamTemplate = upstreamTemplate.Replace(placeholders[i], RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash);
|
||||||
{
|
}
|
||||||
upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)";
|
}
|
||||||
}
|
|
||||||
|
if (upstreamTemplate == "/")
|
||||||
var route = reRoute.ReRouteIsCaseSensitive
|
{
|
||||||
? $"^{upstreamTemplate}{RegExMatchEndString}"
|
return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString, reRoute.UpstreamPathTemplate);
|
||||||
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
}
|
||||||
|
|
||||||
return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString);
|
if(upstreamTemplate.EndsWith("/"))
|
||||||
}
|
{
|
||||||
|
upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)";
|
||||||
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket)
|
}
|
||||||
{
|
|
||||||
if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1)
|
var route = reRoute.ReRouteIsCaseSensitive
|
||||||
{
|
? $"^{upstreamTemplate}{RegExMatchEndString}"
|
||||||
return true;
|
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
||||||
}
|
|
||||||
|
return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString, reRoute.UpstreamPathTemplate);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket)
|
||||||
private bool IsPlaceHolder(string upstreamTemplate, int i)
|
{
|
||||||
{
|
if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1)
|
||||||
return upstreamTemplate[i] == '{';
|
{
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPlaceHolder(string upstreamTemplate, int i)
|
||||||
|
{
|
||||||
|
return upstreamTemplate[i] == '{';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
public DownstreamReRoute(
|
public DownstreamReRoute(
|
||||||
string key,
|
string key,
|
||||||
PathTemplate upstreamPathTemplate,
|
UpstreamPathTemplate upstreamPathTemplate,
|
||||||
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
|
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
|
||||||
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
|
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
|
||||||
List<DownstreamHostAndPort> downstreamAddresses,
|
List<DownstreamHostAndPort> downstreamAddresses,
|
||||||
@ -30,7 +30,7 @@ namespace Ocelot.Configuration
|
|||||||
bool isAuthenticated,
|
bool isAuthenticated,
|
||||||
bool isAuthorised,
|
bool isAuthorised,
|
||||||
AuthenticationOptions authenticationOptions,
|
AuthenticationOptions authenticationOptions,
|
||||||
PathTemplate downstreamPathTemplate,
|
DownstreamPathTemplate downstreamDownstreamPathTemplate,
|
||||||
string loadBalancerKey,
|
string loadBalancerKey,
|
||||||
List<string> delegatingHandlers,
|
List<string> delegatingHandlers,
|
||||||
List<AddHeader> addHeadersToDownstream,
|
List<AddHeader> addHeadersToDownstream,
|
||||||
@ -63,13 +63,13 @@ namespace Ocelot.Configuration
|
|||||||
IsAuthenticated = isAuthenticated;
|
IsAuthenticated = isAuthenticated;
|
||||||
IsAuthorised = isAuthorised;
|
IsAuthorised = isAuthorised;
|
||||||
AuthenticationOptions = authenticationOptions;
|
AuthenticationOptions = authenticationOptions;
|
||||||
DownstreamPathTemplate = downstreamPathTemplate;
|
DownstreamDownstreamPathTemplate = downstreamDownstreamPathTemplate;
|
||||||
LoadBalancerKey = loadBalancerKey;
|
LoadBalancerKey = loadBalancerKey;
|
||||||
AddHeadersToUpstream = addHeadersToUpstream;
|
AddHeadersToUpstream = addHeadersToUpstream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Key { get; }
|
public string Key { get; }
|
||||||
public PathTemplate UpstreamPathTemplate { get; }
|
public UpstreamPathTemplate UpstreamPathTemplate { get; }
|
||||||
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace { get; }
|
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace { get; }
|
||||||
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; }
|
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; }
|
||||||
public List<DownstreamHostAndPort> DownstreamAddresses { get; }
|
public List<DownstreamHostAndPort> DownstreamAddresses { get; }
|
||||||
@ -91,7 +91,7 @@ namespace Ocelot.Configuration
|
|||||||
public bool IsAuthenticated { get; }
|
public bool IsAuthenticated { get; }
|
||||||
public bool IsAuthorised { get; }
|
public bool IsAuthorised { get; }
|
||||||
public AuthenticationOptions AuthenticationOptions { get; }
|
public AuthenticationOptions AuthenticationOptions { get; }
|
||||||
public PathTemplate DownstreamPathTemplate { get; }
|
public DownstreamPathTemplate DownstreamDownstreamPathTemplate { get; }
|
||||||
public string LoadBalancerKey { get; }
|
public string LoadBalancerKey { get; }
|
||||||
public List<string> DelegatingHandlers { get; }
|
public List<string> DelegatingHandlers { get; }
|
||||||
public List<AddHeader> AddHeadersToDownstream { get; }
|
public List<AddHeader> AddHeadersToDownstream { get; }
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
public class FileConfiguration
|
public class FileConfiguration
|
||||||
{
|
{
|
||||||
public FileConfiguration()
|
public FileConfiguration()
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>();
|
ReRoutes = new List<FileReRoute>();
|
||||||
GlobalConfiguration = new FileGlobalConfiguration();
|
GlobalConfiguration = new FileGlobalConfiguration();
|
||||||
Aggregates = new List<FileAggregateReRoute>();
|
Aggregates = new List<FileAggregateReRoute>();
|
||||||
}
|
DynamicReRoutes = new List<FileDynamicReRoute>();
|
||||||
|
}
|
||||||
public List<FileReRoute> ReRoutes { get; set; }
|
|
||||||
|
public List<FileReRoute> ReRoutes { get; set; }
|
||||||
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
|
public List<FileDynamicReRoute> DynamicReRoutes { get; set; }
|
||||||
public List<FileAggregateReRoute> Aggregates { get;set; }
|
|
||||||
public FileGlobalConfiguration GlobalConfiguration { get; set; }
|
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
|
||||||
}
|
public List<FileAggregateReRoute> Aggregates { get;set; }
|
||||||
}
|
public FileGlobalConfiguration GlobalConfiguration { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
8
src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Normal file
8
src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileDynamicReRoute
|
||||||
|
{
|
||||||
|
public string ServiceName { get; set; }
|
||||||
|
public FileRateLimitRule RateLimitRule { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,30 @@
|
|||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
public class FileGlobalConfiguration
|
public class FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
public FileGlobalConfiguration()
|
public FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
|
||||||
RateLimitOptions = new FileRateLimitOptions();
|
RateLimitOptions = new FileRateLimitOptions();
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions();
|
LoadBalancerOptions = new FileLoadBalancerOptions();
|
||||||
QoSOptions = new FileQoSOptions();
|
QoSOptions = new FileQoSOptions();
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions();
|
HttpHandlerOptions = new FileHttpHandlerOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RequestIdKey { get; set; }
|
public string RequestIdKey { get; set; }
|
||||||
|
|
||||||
public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; }
|
public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; }
|
||||||
|
|
||||||
public FileRateLimitOptions RateLimitOptions { get; set; }
|
public FileRateLimitOptions RateLimitOptions { get; set; }
|
||||||
|
|
||||||
public FileQoSOptions QoSOptions { get; set; }
|
public FileQoSOptions QoSOptions { get; set; }
|
||||||
|
|
||||||
public string BaseUrl { get ;set; }
|
public string BaseUrl { get ;set; }
|
||||||
|
|
||||||
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
|
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
|
||||||
|
|
||||||
public string DownstreamScheme { get; set; }
|
public string DownstreamScheme { get; set; }
|
||||||
|
|
||||||
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
namespace Ocelot.Configuration.File
|
|
||||||
{
|
|
||||||
public class FileIdentityServerConfig
|
|
||||||
{
|
|
||||||
public string ProviderRootUrl { get; set; }
|
|
||||||
public string ApiName { get; set; }
|
|
||||||
public bool RequireHttps { get; set; }
|
|
||||||
public string ApiSecret { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +1,37 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
public class FileRateLimitOptions
|
public class FileRateLimitOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
|
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientIdHeader { get; set; } = "ClientId";
|
public string ClientIdHeader { get; set; } = "ClientId";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
|
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
|
||||||
/// If none specified the default will be:
|
/// If none specified the default will be:
|
||||||
/// API calls quota exceeded! maximum admitted {0} per {1}
|
/// API calls quota exceeded! maximum admitted {0} per {1}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string QuotaExceededMessage { get; set; }
|
public string QuotaExceededMessage { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
|
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RateLimitCounterPrefix { get; set; } = "ocelot";
|
public string RateLimitCounterPrefix { get; set; } = "ocelot";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disables X-Rate-Limit and Rety-After headers
|
/// Disables X-Rate-Limit and Rety-After headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DisableRateLimitHeaders { get; set; }
|
public bool DisableRateLimitHeaders { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int HttpStatusCode { get; set; } = 429;
|
public int HttpStatusCode { get; set; } = 429;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,52 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Infrastructure.Extensions;
|
using Ocelot.Infrastructure.Extensions;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
public class FileRateLimitRule
|
public class FileRateLimitRule
|
||||||
{
|
{
|
||||||
public FileRateLimitRule()
|
public FileRateLimitRule()
|
||||||
{
|
{
|
||||||
ClientWhitelist = new List<string>();
|
ClientWhitelist = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> ClientWhitelist { get; set; }
|
public List<string> ClientWhitelist { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables endpoint rate limiting based URL path and HTTP verb
|
/// Enables endpoint rate limiting based URL path and HTTP verb
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableRateLimiting { get; set; }
|
public bool EnableRateLimiting { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rate limit period as in 1s, 1m, 1h
|
/// Rate limit period as in 1s, 1m, 1h
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Period { get; set; }
|
public string Period { get; set; }
|
||||||
|
|
||||||
public double PeriodTimespan { get; set; }
|
public double PeriodTimespan { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum number of requests that a client can make in a defined period
|
/// Maximum number of requests that a client can make in a defined period
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long Limit { get; set; }
|
public long Limit { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (!EnableRateLimiting)
|
if (!EnableRateLimiting)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.Append(
|
sb.Append(
|
||||||
$"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:[");
|
$"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:[");
|
||||||
|
|
||||||
sb.AppendJoin(',', ClientWhitelist);
|
sb.AppendJoin(',', ClientWhitelist);
|
||||||
sb.Append(']');
|
sb.Append(']');
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,6 @@ namespace Ocelot.Configuration.File
|
|||||||
public FileRateLimitRule RateLimitOptions { get; set; }
|
public FileRateLimitRule RateLimitOptions { get; set; }
|
||||||
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
||||||
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
||||||
public bool UseServiceDiscovery { get;set; }
|
|
||||||
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
|
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
|
||||||
public string UpstreamHost { get; set; }
|
public string UpstreamHost { get; set; }
|
||||||
public string Key { get;set; }
|
public string Key { get;set; }
|
||||||
|
@ -4,11 +4,9 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Raft;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
using Rafty.Concensus.Node;
|
|
||||||
using Repository;
|
using Repository;
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@ -44,20 +42,6 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//todo - this code is a bit shit sort it out..
|
|
||||||
var test = _provider.GetService(typeof(INode));
|
|
||||||
if (test != null)
|
|
||||||
{
|
|
||||||
var node = (INode)test;
|
|
||||||
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
|
||||||
if (result.GetType() == typeof(Rafty.Infrastructure.ErrorResponse<UpdateFileConfiguration>))
|
|
||||||
{
|
|
||||||
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OkObjectResult(result.Command.Configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await _setter.Set(fileConfiguration);
|
var response = await _setter.Set(fileConfiguration);
|
||||||
|
|
||||||
if (response.IsError)
|
if (response.IsError)
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
namespace Ocelot.Configuration
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public interface IIdentityServerConfiguration
|
|
||||||
{
|
|
||||||
string ApiName { get; }
|
|
||||||
string ApiSecret { get; }
|
|
||||||
bool RequireHttps { get; }
|
|
||||||
List<string> AllowedScopes { get; }
|
|
||||||
string CredentialsSigningCertificateLocation { get; }
|
|
||||||
string CredentialsSigningCertificatePassword { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
namespace Ocelot.Configuration
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class IdentityServerConfiguration : IIdentityServerConfiguration
|
|
||||||
{
|
|
||||||
public IdentityServerConfiguration(
|
|
||||||
string apiName,
|
|
||||||
bool requireHttps,
|
|
||||||
string apiSecret,
|
|
||||||
List<string> allowedScopes,
|
|
||||||
string credentialsSigningCertificateLocation,
|
|
||||||
string credentialsSigningCertificatePassword)
|
|
||||||
{
|
|
||||||
ApiName = apiName;
|
|
||||||
RequireHttps = requireHttps;
|
|
||||||
ApiSecret = apiSecret;
|
|
||||||
AllowedScopes = allowedScopes;
|
|
||||||
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
|
||||||
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ApiName { get; }
|
|
||||||
public bool RequireHttps { get; }
|
|
||||||
public List<string> AllowedScopes { get; }
|
|
||||||
public string ApiSecret { get; }
|
|
||||||
public string CredentialsSigningCertificateLocation { get; }
|
|
||||||
public string CredentialsSigningCertificatePassword { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +1,31 @@
|
|||||||
using Polly.Timeout;
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
namespace Ocelot.Configuration
|
public class QoSOptions
|
||||||
{
|
{
|
||||||
public class QoSOptions
|
public QoSOptions(
|
||||||
{
|
int exceptionsAllowedBeforeBreaking,
|
||||||
public QoSOptions(
|
int durationofBreak,
|
||||||
int exceptionsAllowedBeforeBreaking,
|
int timeoutValue,
|
||||||
int durationofBreak,
|
string key,
|
||||||
int timeoutValue,
|
string timeoutStrategy = "Pessimistic")
|
||||||
string key,
|
{
|
||||||
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
|
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
||||||
{
|
DurationOfBreak = durationofBreak;
|
||||||
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
TimeoutValue = timeoutValue;
|
||||||
DurationOfBreak = durationofBreak;
|
TimeoutStrategy = timeoutStrategy;
|
||||||
TimeoutValue = timeoutValue;
|
Key = key;
|
||||||
TimeoutStrategy = timeoutStrategy;
|
}
|
||||||
Key = key;
|
|
||||||
}
|
public int ExceptionsAllowedBeforeBreaking { get; }
|
||||||
|
|
||||||
public int ExceptionsAllowedBeforeBreaking { get; }
|
public int DurationOfBreak { get; }
|
||||||
|
|
||||||
public int DurationOfBreak { get; }
|
public int TimeoutValue { get; }
|
||||||
|
|
||||||
public int TimeoutValue { get; }
|
public string TimeoutStrategy { get; }
|
||||||
|
|
||||||
public TimeoutStrategy TimeoutStrategy { get; }
|
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0;
|
||||||
|
|
||||||
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0;
|
public string Key { get; }
|
||||||
public string Key { get; }
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
namespace Ocelot.Configuration
|
||||||
using System.Net.Http;
|
|
||||||
using Ocelot.Configuration.Creator;
|
|
||||||
using Ocelot.Requester.QoS;
|
|
||||||
using Ocelot.Values;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
public class ReRoute
|
public class ReRoute
|
||||||
{
|
{
|
||||||
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
|
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
|
||||||
PathTemplate upstreamPathTemplate,
|
|
||||||
List<HttpMethod> upstreamHttpMethod,
|
List<HttpMethod> upstreamHttpMethod,
|
||||||
UpstreamPathTemplate upstreamTemplatePattern,
|
UpstreamPathTemplate upstreamTemplatePattern,
|
||||||
string upstreamHost,
|
string upstreamHost,
|
||||||
@ -17,13 +14,11 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
UpstreamHost = upstreamHost;
|
UpstreamHost = upstreamHost;
|
||||||
DownstreamReRoute = downstreamReRoute;
|
DownstreamReRoute = downstreamReRoute;
|
||||||
UpstreamPathTemplate = upstreamPathTemplate;
|
|
||||||
UpstreamHttpMethod = upstreamHttpMethod;
|
UpstreamHttpMethod = upstreamHttpMethod;
|
||||||
UpstreamTemplatePattern = upstreamTemplatePattern;
|
UpstreamTemplatePattern = upstreamTemplatePattern;
|
||||||
Aggregator = aggregator;
|
Aggregator = aggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathTemplate UpstreamPathTemplate { get; private set; }
|
|
||||||
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; }
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
public class ReRouteOptions
|
public class ReRouteOptions
|
||||||
{
|
{
|
||||||
public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting)
|
public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting)
|
||||||
{
|
{
|
||||||
IsAuthenticated = isAuthenticated;
|
IsAuthenticated = isAuthenticated;
|
||||||
IsAuthorised = isAuthorised;
|
IsAuthorised = isAuthorised;
|
||||||
IsCached = isCached;
|
IsCached = isCached;
|
||||||
EnableRateLimiting = isEnableRateLimiting;
|
EnableRateLimiting = isEnableRateLimiting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAuthenticated { get; private set; }
|
public bool IsAuthenticated { get; private set; }
|
||||||
public bool IsAuthorised { get; private set; }
|
public bool IsAuthorised { get; private set; }
|
||||||
public bool IsCached { get; private set; }
|
public bool IsCached { get; private set; }
|
||||||
public bool EnableRateLimiting { get; private set; }
|
public bool EnableRateLimiting { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
namespace Ocelot.Configuration.Repository
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Consul;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Responses;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
|
|
||||||
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
|
||||||
{
|
|
||||||
private readonly IConsulClient _consul;
|
|
||||||
private readonly string _configurationKey;
|
|
||||||
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
|
||||||
private readonly IOcelotLogger _logger;
|
|
||||||
|
|
||||||
public ConsulFileConfigurationRepository(
|
|
||||||
Cache.IOcelotCache<FileConfiguration> cache,
|
|
||||||
IInternalConfigurationRepository repo,
|
|
||||||
IConsulClientFactory factory,
|
|
||||||
IOcelotLoggerFactory loggerFactory)
|
|
||||||
{
|
|
||||||
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
|
||||||
_cache = cache;
|
|
||||||
|
|
||||||
var internalConfig = repo.Get();
|
|
||||||
|
|
||||||
_configurationKey = "InternalConfiguration";
|
|
||||||
|
|
||||||
string token = null;
|
|
||||||
|
|
||||||
if (!internalConfig.IsError)
|
|
||||||
{
|
|
||||||
token = internalConfig.Data.ServiceProviderConfiguration.Token;
|
|
||||||
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
|
|
||||||
internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
|
|
||||||
internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
|
|
||||||
|
|
||||||
_consul = factory.Get(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Response<FileConfiguration>> Get()
|
|
||||||
{
|
|
||||||
var config = _cache.Get(_configurationKey, _configurationKey);
|
|
||||||
|
|
||||||
if (config != null)
|
|
||||||
{
|
|
||||||
return new OkResponse<FileConfiguration>(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryResult = await _consul.KV.Get(_configurationKey);
|
|
||||||
|
|
||||||
if (queryResult.Response == null)
|
|
||||||
{
|
|
||||||
return new OkResponse<FileConfiguration>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes = queryResult.Response.Value;
|
|
||||||
|
|
||||||
var json = Encoding.UTF8.GetString(bytes);
|
|
||||||
|
|
||||||
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
|
||||||
|
|
||||||
return new OkResponse<FileConfiguration>(consulConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
|
||||||
|
|
||||||
var kvPair = new KVPair(_configurationKey)
|
|
||||||
{
|
|
||||||
Value = bytes
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await _consul.KV.Put(kvPair);
|
|
||||||
|
|
||||||
if (result.Response)
|
|
||||||
{
|
|
||||||
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
|
|
||||||
|
|
||||||
return new OkResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +1,112 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Ocelot.Configuration.File;
|
using Newtonsoft.Json;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Configuration.Setter;
|
||||||
namespace Ocelot.Configuration.Repository
|
using Ocelot.Logging;
|
||||||
{
|
|
||||||
public class ConsulFileConfigurationPoller : IDisposable
|
namespace Ocelot.Configuration.Repository
|
||||||
{
|
{
|
||||||
private readonly IOcelotLogger _logger;
|
public class FileConfigurationPoller : IHostedService, IDisposable
|
||||||
private readonly IFileConfigurationRepository _repo;
|
{
|
||||||
private readonly IFileConfigurationSetter _setter;
|
private readonly IOcelotLogger _logger;
|
||||||
private string _previousAsJson;
|
private readonly IFileConfigurationRepository _repo;
|
||||||
private readonly Timer _timer;
|
private string _previousAsJson;
|
||||||
private bool _polling;
|
private Timer _timer;
|
||||||
private readonly IConsulPollerConfiguration _config;
|
private bool _polling;
|
||||||
|
private readonly IFileConfigurationPollerOptions _options;
|
||||||
public ConsulFileConfigurationPoller(
|
private readonly IInternalConfigurationRepository _internalConfigRepo;
|
||||||
IOcelotLoggerFactory factory,
|
private readonly IInternalConfigurationCreator _internalConfigCreator;
|
||||||
IFileConfigurationRepository repo,
|
|
||||||
IFileConfigurationSetter setter,
|
public FileConfigurationPoller(
|
||||||
IConsulPollerConfiguration config)
|
IOcelotLoggerFactory factory,
|
||||||
{
|
IFileConfigurationRepository repo,
|
||||||
_setter = setter;
|
IFileConfigurationPollerOptions options,
|
||||||
_config = config;
|
IInternalConfigurationRepository internalConfigRepo,
|
||||||
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
IInternalConfigurationCreator internalConfigCreator)
|
||||||
_repo = repo;
|
{
|
||||||
_previousAsJson = "";
|
_internalConfigRepo = internalConfigRepo;
|
||||||
_timer = new Timer(async x =>
|
_internalConfigCreator = internalConfigCreator;
|
||||||
{
|
_options = options;
|
||||||
if(_polling)
|
_logger = factory.CreateLogger<FileConfigurationPoller>();
|
||||||
{
|
_repo = repo;
|
||||||
return;
|
_previousAsJson = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
_polling = true;
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
await Poll();
|
{
|
||||||
_polling = false;
|
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting.");
|
||||||
}, null, _config.Delay, _config.Delay);
|
|
||||||
}
|
_timer = new Timer(async x =>
|
||||||
|
{
|
||||||
private async Task Poll()
|
if(_polling)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Started polling consul");
|
return;
|
||||||
|
}
|
||||||
var fileConfig = await _repo.Get();
|
|
||||||
|
_polling = true;
|
||||||
if(fileConfig.IsError)
|
await Poll();
|
||||||
{
|
_polling = false;
|
||||||
_logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
|
}, null, _options.Delay, _options.Delay);
|
||||||
return;
|
|
||||||
}
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
var asJson = ToJson(fileConfig.Data);
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
if(!fileConfig.IsError && asJson != _previousAsJson)
|
{
|
||||||
{
|
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is stopping.");
|
||||||
await _setter.Set(fileConfig.Data);
|
|
||||||
_previousAsJson = asJson;
|
_timer?.Change(Timeout.Infinite, 0);
|
||||||
}
|
|
||||||
|
return Task.CompletedTask;
|
||||||
_logger.LogInformation("Finished polling consul");
|
}
|
||||||
}
|
|
||||||
|
private async Task Poll()
|
||||||
/// <summary>
|
{
|
||||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
_logger.LogInformation("Started polling");
|
||||||
/// </summary>
|
|
||||||
/// <returns>hash of the config</returns>
|
var fileConfig = await _repo.Get();
|
||||||
private string ToJson(FileConfiguration config)
|
|
||||||
{
|
if(fileConfig.IsError)
|
||||||
var currentHash = JsonConvert.SerializeObject(config);
|
{
|
||||||
return currentHash;
|
_logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
public void Dispose()
|
|
||||||
{
|
var asJson = ToJson(fileConfig.Data);
|
||||||
_timer.Dispose();
|
|
||||||
}
|
if(!fileConfig.IsError && asJson != _previousAsJson)
|
||||||
}
|
{
|
||||||
}
|
var config = await _internalConfigCreator.Create(fileConfig.Data);
|
||||||
|
|
||||||
|
if(!config.IsError)
|
||||||
|
{
|
||||||
|
_internalConfigRepo.AddOrReplace(config.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_previousAsJson = asJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Finished polling");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>hash of the config</returns>
|
||||||
|
private string ToJson(FileConfiguration config)
|
||||||
|
{
|
||||||
|
var currentHash = JsonConvert.SerializeObject(config);
|
||||||
|
return currentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace Ocelot.Configuration.Repository
|
namespace Ocelot.Configuration.Repository
|
||||||
{
|
{
|
||||||
public interface IConsulPollerConfiguration
|
public interface IFileConfigurationPollerOptions
|
||||||
{
|
{
|
||||||
int Delay { get; }
|
int Delay { get; }
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace Ocelot.Configuration.Repository
|
namespace Ocelot.Configuration.Repository
|
||||||
{
|
{
|
||||||
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration
|
public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions
|
||||||
{
|
{
|
||||||
public int Delay => 1000;
|
public int Delay => 1000;
|
||||||
}
|
}
|
@ -1,12 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Repository
|
|
||||||
{
|
|
||||||
public class UnableToSetConfigInConsulError : Error
|
|
||||||
{
|
|
||||||
public UnableToSetConfigInConsulError(string message)
|
|
||||||
: base(message, OcelotErrorCode.UnableToSetConfigInConsulError)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter
|
|||||||
{
|
{
|
||||||
public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
|
public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
|
||||||
{
|
{
|
||||||
private readonly IInternalConfigurationRepository _configRepo;
|
private readonly IInternalConfigurationRepository internalConfigRepo;
|
||||||
private readonly IInternalConfigurationCreator _configCreator;
|
private readonly IInternalConfigurationCreator _configCreator;
|
||||||
private readonly IFileConfigurationRepository _repo;
|
private readonly IFileConfigurationRepository _repo;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Setter
|
|||||||
IInternalConfigurationCreator configCreator,
|
IInternalConfigurationCreator configCreator,
|
||||||
IFileConfigurationRepository repo)
|
IFileConfigurationRepository repo)
|
||||||
{
|
{
|
||||||
_configRepo = configRepo;
|
internalConfigRepo = configRepo;
|
||||||
_configCreator = configCreator;
|
_configCreator = configCreator;
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ namespace Ocelot.Configuration.Setter
|
|||||||
|
|
||||||
if(!config.IsError)
|
if(!config.IsError)
|
||||||
{
|
{
|
||||||
_configRepo.AddOrReplace(config.Data);
|
internalConfigRepo.AddOrReplace(config.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ErrorResponse(config.Errors);
|
return new ErrorResponse(config.Errors);
|
||||||
|
@ -1,24 +1,30 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
namespace Ocelot.Configuration.Validator
|
||||||
{
|
{
|
||||||
public class ConfigurationValidationResult
|
public class ConfigurationValidationResult
|
||||||
{
|
{
|
||||||
public ConfigurationValidationResult(bool isError)
|
public ConfigurationValidationResult(bool isError)
|
||||||
{
|
{
|
||||||
IsError = isError;
|
IsError = isError;
|
||||||
Errors = new List<Error>();
|
Errors = new List<Error>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationValidationResult(bool isError, List<Error> errors)
|
public ConfigurationValidationResult(bool isError, Error error)
|
||||||
{
|
{
|
||||||
IsError = isError;
|
IsError = isError;
|
||||||
Errors = errors;
|
Errors = new List<Error> { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsError { get; }
|
public ConfigurationValidationResult(bool isError, List<Error> errors)
|
||||||
|
{
|
||||||
public List<Error> Errors { get; }
|
IsError = isError;
|
||||||
}
|
Errors = errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsError { get; }
|
||||||
|
|
||||||
|
public List<Error> Errors { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,122 +1,180 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
namespace Ocelot.Configuration.Validator
|
||||||
{
|
{
|
||||||
public class FileConfigurationFluentValidator : AbstractValidator<FileConfiguration>, IConfigurationValidator
|
using System;
|
||||||
{
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider)
|
using Ocelot.ServiceDiscovery;
|
||||||
{
|
using Requester;
|
||||||
RuleFor(configuration => configuration.ReRoutes)
|
|
||||||
.SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider));
|
public class FileConfigurationFluentValidator : AbstractValidator<FileConfiguration>, IConfigurationValidator
|
||||||
|
{
|
||||||
RuleForEach(configuration => configuration.ReRoutes)
|
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||||
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes))
|
private readonly List<ServiceDiscoveryFinderDelegate> _serviceDiscoveryFinderDelegates;
|
||||||
.WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate");
|
public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider)
|
||||||
|
{
|
||||||
RuleForEach(configuration => configuration.ReRoutes)
|
_qosDelegatingHandlerDelegate = provider.GetService<QosDelegatingHandlerDelegate>();
|
||||||
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates))
|
_serviceDiscoveryFinderDelegates = provider
|
||||||
.WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate");
|
.GetServices<ServiceDiscoveryFinderDelegate>()
|
||||||
|
.ToList();
|
||||||
RuleForEach(configuration => configuration.Aggregates)
|
|
||||||
.Must((config, aggregateReRoute) => IsNotDuplicateIn(aggregateReRoute, config.Aggregates))
|
RuleFor(configuration => configuration.ReRoutes)
|
||||||
.WithMessage((config, aggregate) => $"{nameof(aggregate)} {aggregate.UpstreamPathTemplate} has duplicate aggregate");
|
.SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, _qosDelegatingHandlerDelegate));
|
||||||
|
|
||||||
RuleForEach(configuration => configuration.Aggregates)
|
RuleFor(configuration => configuration.GlobalConfiguration)
|
||||||
.Must((config, aggregateReRoute) => AllReRoutesForAggregateExist(aggregateReRoute, config.ReRoutes))
|
.SetValidator(new FileGlobalConfigurationFluentValidator(_qosDelegatingHandlerDelegate));
|
||||||
.WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct ServiceName property");
|
|
||||||
|
RuleForEach(configuration => configuration.ReRoutes)
|
||||||
RuleForEach(configuration => configuration.Aggregates)
|
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes))
|
||||||
.Must((config, aggregateReRoute) => DoesNotContainReRoutesWithSpecificRequestIdKeys(aggregateReRoute, config.ReRoutes))
|
.WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate");
|
||||||
.WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates");
|
|
||||||
}
|
RuleForEach(configuration => configuration.ReRoutes)
|
||||||
|
.Must((config, reRoute) => HaveServiceDiscoveryProviderRegitered(reRoute, config.GlobalConfiguration.ServiceDiscoveryProvider))
|
||||||
private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List<FileReRoute> reRoutes)
|
.WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?");
|
||||||
{
|
|
||||||
var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key));
|
RuleFor(configuration => configuration.GlobalConfiguration.ServiceDiscoveryProvider)
|
||||||
|
.Must((config) => HaveServiceDiscoveryProviderRegitered(config))
|
||||||
return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count;
|
.WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?");
|
||||||
}
|
|
||||||
|
RuleForEach(configuration => configuration.ReRoutes)
|
||||||
public async Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates))
|
||||||
{
|
.WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate");
|
||||||
var validateResult = await ValidateAsync(configuration);
|
|
||||||
|
RuleForEach(configuration => configuration.Aggregates)
|
||||||
if (validateResult.IsValid)
|
.Must((config, aggregateReRoute) => IsNotDuplicateIn(aggregateReRoute, config.Aggregates))
|
||||||
{
|
.WithMessage((config, aggregate) => $"{nameof(aggregate)} {aggregate.UpstreamPathTemplate} has duplicate aggregate");
|
||||||
return new OkResponse<ConfigurationValidationResult>(new ConfigurationValidationResult(false));
|
|
||||||
}
|
RuleForEach(configuration => configuration.Aggregates)
|
||||||
|
.Must((config, aggregateReRoute) => AllReRoutesForAggregateExist(aggregateReRoute, config.ReRoutes))
|
||||||
var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage));
|
.WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct ServiceName property");
|
||||||
|
|
||||||
var result = new ConfigurationValidationResult(true, errors.Cast<Error>().ToList());
|
RuleForEach(configuration => configuration.Aggregates)
|
||||||
|
.Must((config, aggregateReRoute) => DoesNotContainReRoutesWithSpecificRequestIdKeys(aggregateReRoute, config.ReRoutes))
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
.WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DoesNotContainReRoutesWithSpecificRequestIdKeys(FileAggregateReRoute fileAggregateReRoute,
|
private bool HaveServiceDiscoveryProviderRegitered(FileReRoute reRoute, FileServiceDiscoveryProvider serviceDiscoveryProvider)
|
||||||
List<FileReRoute> reRoutes)
|
{
|
||||||
{
|
if (string.IsNullOrEmpty(reRoute.ServiceName))
|
||||||
var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key));
|
{
|
||||||
|
return true;
|
||||||
return reRoutesForAggregate.All(r => string.IsNullOrEmpty(r.RequestIdKey));
|
}
|
||||||
}
|
|
||||||
|
if (serviceDiscoveryProvider?.Type?.ToLower() == "servicefabric")
|
||||||
private static bool IsNotDuplicateIn(FileReRoute reRoute,
|
{
|
||||||
List<FileReRoute> reRoutes)
|
return true;
|
||||||
{
|
}
|
||||||
var matchingReRoutes = reRoutes
|
|
||||||
.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate
|
return _serviceDiscoveryFinderDelegates.Any();
|
||||||
&& (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null))
|
}
|
||||||
.ToList();
|
|
||||||
|
private bool HaveServiceDiscoveryProviderRegitered(FileServiceDiscoveryProvider serviceDiscoveryProvider)
|
||||||
if(matchingReRoutes.Count == 1)
|
{
|
||||||
{
|
if(serviceDiscoveryProvider == null)
|
||||||
return true;
|
{
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0);
|
|
||||||
|
if (serviceDiscoveryProvider?.Type?.ToLower() == "servicefabric")
|
||||||
var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1;
|
{
|
||||||
|
return true;
|
||||||
var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0);
|
}
|
||||||
|
|
||||||
var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any();
|
if(string.IsNullOrEmpty(serviceDiscoveryProvider.Type))
|
||||||
|
{
|
||||||
if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs))
|
return true;
|
||||||
{
|
}
|
||||||
return false;
|
|
||||||
}
|
return _serviceDiscoveryFinderDelegates.Any();
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
public async Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
||||||
|
{
|
||||||
private static bool IsNotDuplicateIn(FileReRoute reRoute,
|
var validateResult = await ValidateAsync(configuration);
|
||||||
List<FileAggregateReRoute> aggregateReRoutes)
|
|
||||||
{
|
if (validateResult.IsValid)
|
||||||
var duplicate = aggregateReRoutes
|
{
|
||||||
.Any(a => a.UpstreamPathTemplate == reRoute.UpstreamPathTemplate
|
return new OkResponse<ConfigurationValidationResult>(new ConfigurationValidationResult(false));
|
||||||
&& a.UpstreamHost == reRoute.UpstreamHost
|
}
|
||||||
&& reRoute.UpstreamHttpMethod.Select(x => x.ToLower()).Contains("get"));
|
|
||||||
|
var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage));
|
||||||
return !duplicate;
|
|
||||||
}
|
var result = new ConfigurationValidationResult(true, errors.Cast<Error>().ToList());
|
||||||
|
|
||||||
private static bool IsNotDuplicateIn(FileAggregateReRoute reRoute,
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
List<FileAggregateReRoute> aggregateReRoutes)
|
}
|
||||||
{
|
|
||||||
var matchingReRoutes = aggregateReRoutes
|
private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List<FileReRoute> reRoutes)
|
||||||
.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate
|
{
|
||||||
&& r.UpstreamHost == reRoute.UpstreamHost)
|
var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key));
|
||||||
.ToList();
|
|
||||||
|
return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count;
|
||||||
return matchingReRoutes.Count <= 1;
|
}
|
||||||
}
|
|
||||||
}
|
private static bool DoesNotContainReRoutesWithSpecificRequestIdKeys(FileAggregateReRoute fileAggregateReRoute,
|
||||||
}
|
List<FileReRoute> reRoutes)
|
||||||
|
{
|
||||||
|
var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key));
|
||||||
|
|
||||||
|
return reRoutesForAggregate.All(r => string.IsNullOrEmpty(r.RequestIdKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotDuplicateIn(FileReRoute reRoute,
|
||||||
|
List<FileReRoute> reRoutes)
|
||||||
|
{
|
||||||
|
var matchingReRoutes = reRoutes
|
||||||
|
.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate
|
||||||
|
&& (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (matchingReRoutes.Count == 1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0);
|
||||||
|
|
||||||
|
var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1;
|
||||||
|
|
||||||
|
var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0);
|
||||||
|
|
||||||
|
var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any();
|
||||||
|
|
||||||
|
if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotDuplicateIn(FileReRoute reRoute,
|
||||||
|
List<FileAggregateReRoute> aggregateReRoutes)
|
||||||
|
{
|
||||||
|
var duplicate = aggregateReRoutes
|
||||||
|
.Any(a => a.UpstreamPathTemplate == reRoute.UpstreamPathTemplate
|
||||||
|
&& a.UpstreamHost == reRoute.UpstreamHost
|
||||||
|
&& reRoute.UpstreamHttpMethod.Select(x => x.ToLower()).Contains("get"));
|
||||||
|
|
||||||
|
return !duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotDuplicateIn(FileAggregateReRoute reRoute,
|
||||||
|
List<FileAggregateReRoute> aggregateReRoutes)
|
||||||
|
{
|
||||||
|
var matchingReRoutes = aggregateReRoutes
|
||||||
|
.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate
|
||||||
|
&& r.UpstreamHost == reRoute.UpstreamHost)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return matchingReRoutes.Count <= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
using Requester;
|
||||||
|
|
||||||
|
public class FileGlobalConfigurationFluentValidator : AbstractValidator<FileGlobalConfiguration>
|
||||||
|
{
|
||||||
|
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||||
|
|
||||||
|
public FileGlobalConfigurationFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||||
|
{
|
||||||
|
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||||
|
|
||||||
|
RuleFor(configuration => configuration.QoSOptions)
|
||||||
|
.SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
using Requester;
|
||||||
|
|
||||||
|
public class FileQoSOptionsFluentValidator : AbstractValidator<FileQoSOptions>
|
||||||
|
{
|
||||||
|
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||||
|
|
||||||
|
public FileQoSOptionsFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||||
|
{
|
||||||
|
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||||
|
|
||||||
|
When(qosOptions => qosOptions.TimeoutValue > 0 && qosOptions.ExceptionsAllowedBeforeBreaking > 0, () => {
|
||||||
|
RuleFor(qosOptions => qosOptions)
|
||||||
|
.Must(HaveQosHandlerRegistered)
|
||||||
|
.WithMessage("Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HaveQosHandlerRegistered(FileQoSOptions arg)
|
||||||
|
{
|
||||||
|
return _qosDelegatingHandlerDelegate != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +1,90 @@
|
|||||||
using FluentValidation;
|
namespace Ocelot.Configuration.Validator
|
||||||
using Microsoft.AspNetCore.Authentication;
|
{
|
||||||
using Ocelot.Configuration.File;
|
using FluentValidation;
|
||||||
using System.Linq;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using System.Threading;
|
using Ocelot.Configuration.File;
|
||||||
using System.Threading.Tasks;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
namespace Ocelot.Configuration.Validator
|
using System.Threading.Tasks;
|
||||||
{
|
using System;
|
||||||
public class ReRouteFluentValidator : AbstractValidator<FileReRoute>
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
{
|
using Requester;
|
||||||
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
|
||||||
|
public class ReRouteFluentValidator : AbstractValidator<FileReRoute>
|
||||||
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider)
|
{
|
||||||
{
|
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||||
_authenticationSchemeProvider = authenticationSchemeProvider;
|
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||||
.Must(path => path.StartsWith("/"))
|
{
|
||||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||||
|
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
|
||||||
.Must(path => !path.Contains("//"))
|
RuleFor(reRoute => reRoute.QoSOptions)
|
||||||
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
.SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate));
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||||
.Must(path => !path.Contains("//"))
|
.Must(path => path.StartsWith("/"))
|
||||||
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
.Must(path => path.StartsWith("/"))
|
.Must(path => !path.Contains("//"))
|
||||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||||
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
.Must(path => !path.Contains("//"))
|
||||||
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
.Must(path => path.StartsWith("/"))
|
||||||
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.RateLimitOptions)
|
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||||
.Must(IsValidPeriod)
|
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||||
.WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)");
|
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.AuthenticationOptions)
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
.MustAsync(IsSupportedAuthenticationProviders)
|
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||||
.WithMessage("{PropertyValue} is unsupported authentication provider");
|
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||||
|
|
||||||
When(reRoute => reRoute.UseServiceDiscovery, () => {
|
RuleFor(reRoute => reRoute.RateLimitOptions)
|
||||||
RuleFor(r => r.ServiceName).NotEmpty().WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!");
|
.Must(IsValidPeriod)
|
||||||
});
|
.WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)");
|
||||||
|
|
||||||
When(reRoute => !reRoute.UseServiceDiscovery, () => {
|
RuleFor(reRoute => reRoute.AuthenticationOptions)
|
||||||
RuleFor(r => r.DownstreamHostAndPorts).NotEmpty().WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!");
|
.MustAsync(IsSupportedAuthenticationProviders)
|
||||||
});
|
.WithMessage("{PropertyValue} is unsupported authentication provider");
|
||||||
|
|
||||||
When(reRoute => !reRoute.UseServiceDiscovery, () => {
|
When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => {
|
||||||
RuleFor(reRoute => reRoute.DownstreamHostAndPorts)
|
RuleFor(r => r.DownstreamHostAndPorts).NotEmpty()
|
||||||
.SetCollectionValidator(new HostAndPortValidator());
|
.WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!");
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => {
|
||||||
private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken)
|
RuleFor(reRoute => reRoute.DownstreamHostAndPorts)
|
||||||
{
|
.SetCollectionValidator(new HostAndPortValidator());
|
||||||
if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey))
|
});
|
||||||
{
|
}
|
||||||
return true;
|
|
||||||
}
|
private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
|
if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey))
|
||||||
|
{
|
||||||
var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList();
|
return true;
|
||||||
|
}
|
||||||
return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey);
|
|
||||||
}
|
var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
|
||||||
|
|
||||||
private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions)
|
var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList();
|
||||||
{
|
|
||||||
string period = rateLimitOptions.Period;
|
return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey);
|
||||||
|
}
|
||||||
return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d");
|
|
||||||
}
|
private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions)
|
||||||
}
|
{
|
||||||
}
|
string period = rateLimitOptions.Period;
|
||||||
|
|
||||||
|
return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public interface IOcelotAdministrationBuilder
|
public interface IOcelotAdministrationBuilder
|
||||||
{
|
{
|
||||||
IOcelotAdministrationBuilder AddRafty();
|
IServiceCollection Services { get; }
|
||||||
|
IConfiguration ConfigurationRoot { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
using Butterfly.Client.AspNetCore;
|
|
||||||
using CacheManager.Core;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using IdentityServer4.AccessTokenValidation;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@ -12,22 +9,15 @@ namespace Ocelot.DependencyInjection
|
|||||||
public interface IOcelotBuilder
|
public interface IOcelotBuilder
|
||||||
{
|
{
|
||||||
IServiceCollection Services { get; }
|
IServiceCollection Services { get; }
|
||||||
|
|
||||||
IConfiguration Configuration { get; }
|
IConfiguration Configuration { get; }
|
||||||
IOcelotBuilder AddStoreOcelotConfigurationInConsul();
|
|
||||||
|
|
||||||
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
|
||||||
|
|
||||||
IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings);
|
|
||||||
|
|
||||||
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
|
||||||
|
|
||||||
IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configOptions);
|
|
||||||
|
|
||||||
IOcelotBuilder AddDelegatingHandler<T>(bool global = false)
|
IOcelotBuilder AddDelegatingHandler<T>(bool global = false)
|
||||||
where T : DelegatingHandler;
|
where T : DelegatingHandler;
|
||||||
|
|
||||||
IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
||||||
where T : class, IDefinedAggregator;
|
where T : class, IDefinedAggregator;
|
||||||
|
|
||||||
IOcelotBuilder AddTransientDefinedAggregator<T>()
|
IOcelotBuilder AddTransientDefinedAggregator<T>()
|
||||||
where T : class, IDefinedAggregator;
|
where T : class, IDefinedAggregator;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
namespace Ocelot.DependencyInjection
|
|
||||||
{
|
|
||||||
public class NullAdministrationPath : IAdministrationPath
|
|
||||||
{
|
|
||||||
public NullAdministrationPath()
|
|
||||||
{
|
|
||||||
Path = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Path {get;private set;}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +1,17 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Ocelot.Raft;
|
|
||||||
using Rafty.Concensus;
|
|
||||||
using Rafty.FiniteStateMachine;
|
|
||||||
using Rafty.Infrastructure;
|
|
||||||
using Rafty.Log;
|
|
||||||
|
|
||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
using Rafty.Concensus.Node;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
||||||
{
|
{
|
||||||
private readonly IServiceCollection _services;
|
public IServiceCollection Services { get; }
|
||||||
private readonly IConfiguration _configurationRoot;
|
public IConfiguration ConfigurationRoot { get; }
|
||||||
|
|
||||||
public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
||||||
{
|
{
|
||||||
_configurationRoot = configurationRoot;
|
ConfigurationRoot = configurationRoot;
|
||||||
_services = services;
|
Services = services;
|
||||||
}
|
|
||||||
|
|
||||||
public IOcelotAdministrationBuilder AddRafty()
|
|
||||||
{
|
|
||||||
var settings = new InMemorySettings(4000, 6000, 100, 10000);
|
|
||||||
_services.AddSingleton<ILog, SqlLiteLog>();
|
|
||||||
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
|
||||||
_services.AddSingleton<ISettings>(settings);
|
|
||||||
_services.AddSingleton<IPeersProvider, FilePeersProvider>();
|
|
||||||
_services.AddSingleton<INode, Node>();
|
|
||||||
_services.Configure<FilePeers>(_configurationRoot);
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
using CacheManager.Core;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -30,189 +28,118 @@ namespace Ocelot.DependencyInjection
|
|||||||
using Ocelot.Requester.QoS;
|
using Ocelot.Requester.QoS;
|
||||||
using Ocelot.Responder;
|
using Ocelot.Responder;
|
||||||
using Ocelot.ServiceDiscovery;
|
using Ocelot.ServiceDiscovery;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using IdentityServer4.AccessTokenValidation;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Butterfly.Client.AspNetCore;
|
|
||||||
using Ocelot.Infrastructure;
|
using Ocelot.Infrastructure;
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
using ServiceDiscovery.Providers;
|
|
||||||
using Steeltoe.Common.Discovery;
|
|
||||||
using Pivotal.Discovery.Client;
|
|
||||||
using Ocelot.Request.Creator;
|
using Ocelot.Request.Creator;
|
||||||
|
|
||||||
public class OcelotBuilder : IOcelotBuilder
|
public class OcelotBuilder : IOcelotBuilder
|
||||||
{
|
{
|
||||||
private readonly IServiceCollection _services;
|
public IServiceCollection Services { get; }
|
||||||
private readonly IConfiguration _configurationRoot;
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
public IServiceCollection Services => _services;
|
|
||||||
|
|
||||||
public IConfiguration Configuration => _configurationRoot;
|
|
||||||
|
|
||||||
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
||||||
{
|
{
|
||||||
_configurationRoot = configurationRoot;
|
Configuration = configurationRoot;
|
||||||
_services = services;
|
Services = services;
|
||||||
|
Services.Configure<FileConfiguration>(configurationRoot);
|
||||||
//add default cache settings...
|
|
||||||
Action<ConfigurationBuilderCachePart> defaultCachingSettings = x =>
|
|
||||||
{
|
|
||||||
x.WithDictionaryHandle();
|
|
||||||
};
|
|
||||||
|
|
||||||
AddCacheManager(defaultCachingSettings);
|
Services.TryAddSingleton<IOcelotCache<FileConfiguration>, InMemoryCache<FileConfiguration>>();
|
||||||
|
Services.TryAddSingleton<IOcelotCache<CachedResponse>, InMemoryCache<CachedResponse>>();
|
||||||
//add ocelot services...
|
Services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
|
||||||
_services.Configure<FileConfiguration>(configurationRoot);
|
Services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
|
||||||
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
|
Services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
|
||||||
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
|
Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
|
||||||
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
|
Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
|
||||||
_services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
|
Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
||||||
_services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
|
Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
||||||
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
||||||
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
||||||
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
Services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
|
||||||
_services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
Services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
|
||||||
_services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
|
Services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
|
||||||
_services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
|
Services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
|
||||||
_services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
|
Services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
|
||||||
_services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
|
Services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
|
||||||
_services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
|
Services.TryAddSingleton<IRegionCreator, RegionCreator>();
|
||||||
_services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
|
Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
|
||||||
_services.TryAddSingleton<IRegionCreator, RegionCreator>();
|
Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
|
||||||
_services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
|
Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||||
_services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
|
Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||||
_services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
|
Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||||
_services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
|
Services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||||
_services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
Services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||||
_services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
Services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
||||||
_services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
Services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
|
||||||
_services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
Services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
|
||||||
_services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
Services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
|
||||||
_services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
|
Services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
|
||||||
_services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
|
Services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
|
||||||
_services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
|
Services.TryAddSingleton<IClaimsParser, ClaimsParser>();
|
||||||
_services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
|
Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
||||||
_services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
|
Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
||||||
_services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
|
Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
||||||
_services.TryAddSingleton<IClaimsParser, ClaimsParser>();
|
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
|
||||||
_services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();
|
||||||
_services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();
|
||||||
_services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||||
_services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
|
Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
|
||||||
_services.AddSingleton<IDownstreamRouteProvider, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>();
|
Services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
|
||||||
_services.TryAddSingleton<IDownstreamRouteProviderFactory, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteProviderFactory>();
|
Services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
|
||||||
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
Services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();
|
||||||
_services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
|
Services.TryAddSingleton<IRequestMapper, RequestMapper>();
|
||||||
_services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
|
Services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
|
||||||
_services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
|
Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
|
||||||
_services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();
|
Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
|
||||||
_services.TryAddSingleton<IRequestMapper, RequestMapper>();
|
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||||
_services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
|
|
||||||
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
|
|
||||||
_services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
|
|
||||||
|
|
||||||
if (UsingEurekaServiceDiscoveryProvider(configurationRoot))
|
|
||||||
{
|
|
||||||
_services.AddDiscoveryClient(configurationRoot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_services.TryAddSingleton<IDiscoveryClient, FakeEurekaDiscoveryClient>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
|
||||||
|
|
||||||
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
||||||
// could maybe use a scoped data repository
|
// could maybe use a scoped data repository
|
||||||
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
_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.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
|
||||||
|
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
||||||
|
Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||||
|
Services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||||
|
Services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
||||||
|
Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
||||||
|
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
||||||
|
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
|
||||||
|
Services.TryAddSingleton<IQoSFactory, QoSFactory>();
|
||||||
|
Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>();
|
||||||
|
|
||||||
//add asp.net services..
|
//add asp.net services..
|
||||||
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
|
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
|
||||||
|
|
||||||
_services.AddMvcCore()
|
Services.AddMvcCore()
|
||||||
.AddApplicationPart(assembly)
|
.AddApplicationPart(assembly)
|
||||||
.AddControllersAsServices()
|
.AddControllersAsServices()
|
||||||
.AddAuthorization()
|
.AddAuthorization()
|
||||||
.AddJsonFormatters();
|
.AddJsonFormatters();
|
||||||
|
|
||||||
_services.AddLogging();
|
Services.AddLogging();
|
||||||
_services.AddMiddlewareAnalysis();
|
Services.AddMiddlewareAnalysis();
|
||||||
_services.AddWebEncoders();
|
Services.AddWebEncoders();
|
||||||
_services.AddSingleton<IAdministrationPath>(new NullAdministrationPath());
|
|
||||||
|
|
||||||
_services.TryAddSingleton<IMultiplexer, Multiplexer>();
|
|
||||||
_services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
|
|
||||||
_services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
|
|
||||||
|
|
||||||
// We add this here so that we can always inject something into the factory for IoC..
|
|
||||||
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
|
||||||
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
|
|
||||||
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
|
||||||
_services.TryAddSingleton<IPlaceholders, Placeholders>();
|
|
||||||
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
|
||||||
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
|
||||||
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
|
||||||
_services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
|
||||||
_services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
|
||||||
{
|
|
||||||
var administrationPath = new AdministrationPath(path);
|
|
||||||
|
|
||||||
//add identity server for admin area
|
|
||||||
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
|
|
||||||
|
|
||||||
if (identityServerConfiguration != null)
|
|
||||||
{
|
|
||||||
AddIdentityServer(identityServerConfiguration, administrationPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
|
|
||||||
_services.Replace(descriptor);
|
|
||||||
return new OcelotAdministrationBuilder(_services, _configurationRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configureOptions)
|
|
||||||
{
|
|
||||||
var administrationPath = new AdministrationPath(path);
|
|
||||||
|
|
||||||
if (configureOptions != null)
|
|
||||||
{
|
|
||||||
AddIdentityServer(configureOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo - hack because we add this earlier so it always exists for some reason...investigate..
|
|
||||||
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
|
|
||||||
_services.Replace(descriptor);
|
|
||||||
return new OcelotAdministrationBuilder(_services, _configurationRoot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
public IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
||||||
where T : class, IDefinedAggregator
|
where T : class, IDefinedAggregator
|
||||||
{
|
{
|
||||||
_services.AddSingleton<IDefinedAggregator, T>();
|
Services.AddSingleton<IDefinedAggregator, T>();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotBuilder AddTransientDefinedAggregator<T>()
|
public IOcelotBuilder AddTransientDefinedAggregator<T>()
|
||||||
where T : class, IDefinedAggregator
|
where T : class, IDefinedAggregator
|
||||||
{
|
{
|
||||||
_services.AddTransient<IDefinedAggregator, T>();
|
Services.AddTransient<IDefinedAggregator, T>();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,142 +148,18 @@ namespace Ocelot.DependencyInjection
|
|||||||
{
|
{
|
||||||
if(global)
|
if(global)
|
||||||
{
|
{
|
||||||
_services.AddTransient<THandler>();
|
Services.AddTransient<THandler>();
|
||||||
_services.AddTransient<GlobalDelegatingHandler>(s => {
|
Services.AddTransient<GlobalDelegatingHandler>(s => {
|
||||||
var service = s.GetService<THandler>();
|
var service = s.GetService<THandler>();
|
||||||
return new GlobalDelegatingHandler(service);
|
return new GlobalDelegatingHandler(service);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_services.AddTransient<DelegatingHandler, THandler>();
|
Services.AddTransient<DelegatingHandler, THandler>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
|
|
||||||
{
|
|
||||||
// Earlier we add FakeServiceTracer and need to remove it here before we add butterfly
|
|
||||||
_services.RemoveAll<IServiceTracer>();
|
|
||||||
_services.AddButterfly(settings);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
|
||||||
{
|
|
||||||
_services.AddSingleton<ConsulFileConfigurationPoller>();
|
|
||||||
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings)
|
|
||||||
{
|
|
||||||
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
|
|
||||||
var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
|
|
||||||
|
|
||||||
_services.RemoveAll(typeof(ICacheManager<CachedResponse>));
|
|
||||||
_services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
|
|
||||||
_services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
|
|
||||||
_services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
|
|
||||||
|
|
||||||
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
|
|
||||||
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
|
|
||||||
_services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
|
|
||||||
_services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
|
|
||||||
_services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
|
|
||||||
_services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
|
|
||||||
|
|
||||||
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
|
|
||||||
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
|
|
||||||
_services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
|
|
||||||
_services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
|
|
||||||
_services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
|
|
||||||
_services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions)
|
|
||||||
{
|
|
||||||
_services
|
|
||||||
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
|
||||||
.AddIdentityServerAuthentication(configOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
|
|
||||||
{
|
|
||||||
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
|
||||||
var identityServerBuilder = _services
|
|
||||||
.AddIdentityServer(o => {
|
|
||||||
o.IssuerUri = "Ocelot";
|
|
||||||
})
|
|
||||||
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
|
||||||
.AddInMemoryClients(Client(identityServerConfiguration));
|
|
||||||
|
|
||||||
var urlFinder = new BaseUrlFinder(_configurationRoot);
|
|
||||||
var baseSchemeUrlAndPort = urlFinder.Find();
|
|
||||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
|
||||||
|
|
||||||
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
|
||||||
.AddIdentityServerAuthentication(o =>
|
|
||||||
{
|
|
||||||
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
|
|
||||||
o.ApiName = identityServerConfiguration.ApiName;
|
|
||||||
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
|
||||||
o.SupportedTokens = SupportedTokens.Both;
|
|
||||||
o.ApiSecret = identityServerConfiguration.ApiSecret;
|
|
||||||
});
|
|
||||||
|
|
||||||
//todo - refactor naming..
|
|
||||||
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
|
|
||||||
{
|
|
||||||
identityServerBuilder.AddDeveloperSigningCredential();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//todo - refactor so calls method?
|
|
||||||
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
|
|
||||||
identityServerBuilder.AddSigningCredential(cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
|
|
||||||
{
|
|
||||||
return new List<ApiResource>
|
|
||||||
{
|
|
||||||
new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
|
|
||||||
{
|
|
||||||
ApiSecrets = new List<Secret>
|
|
||||||
{
|
|
||||||
new Secret
|
|
||||||
{
|
|
||||||
Value = identityServerConfiguration.ApiSecret.Sha256()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
|
|
||||||
{
|
|
||||||
return new List<Client>
|
|
||||||
{
|
|
||||||
new Client
|
|
||||||
{
|
|
||||||
ClientId = identityServerConfiguration.ApiName,
|
|
||||||
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
|
||||||
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
|
||||||
AllowedScopes = { identityServerConfiguration.ApiName }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot)
|
|
||||||
{
|
|
||||||
var type = configurationRoot.GetValue<string>("GlobalConfiguration:ServiceDiscoveryProvider:Type",
|
|
||||||
string.Empty);
|
|
||||||
|
|
||||||
return type.ToLower() == "eureka";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Configuration;
|
using Configuration;
|
||||||
using Configuration.Builder;
|
using Configuration.Builder;
|
||||||
using Configuration.Creator;
|
using Configuration.Creator;
|
||||||
@ -43,7 +44,9 @@
|
|||||||
|
|
||||||
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
|
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
|
||||||
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build();
|
||||||
|
|
||||||
|
var downstreamReRouteBuilder = new DownstreamReRouteBuilder()
|
||||||
.WithServiceName(serviceName)
|
.WithServiceName(serviceName)
|
||||||
.WithLoadBalancerKey(loadBalancerKey)
|
.WithLoadBalancerKey(loadBalancerKey)
|
||||||
.WithDownstreamPathTemplate(downstreamPath)
|
.WithDownstreamPathTemplate(downstreamPath)
|
||||||
@ -52,11 +55,27 @@
|
|||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithDownstreamScheme(configuration.DownstreamScheme)
|
.WithDownstreamScheme(configuration.DownstreamScheme)
|
||||||
.WithLoadBalancerOptions(configuration.LoadBalancerOptions)
|
.WithLoadBalancerOptions(configuration.LoadBalancerOptions)
|
||||||
.Build();
|
.WithUpstreamPathTemplate(upstreamPathTemplate);
|
||||||
|
|
||||||
|
var rateLimitOptions = configuration.ReRoutes != null
|
||||||
|
? configuration.ReRoutes
|
||||||
|
.SelectMany(x => x.DownstreamReRoute)
|
||||||
|
.FirstOrDefault(x => x.ServiceName == serviceName)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if(rateLimitOptions != null)
|
||||||
|
{
|
||||||
|
downstreamReRouteBuilder
|
||||||
|
.WithRateLimitOptions(rateLimitOptions.RateLimitOptions)
|
||||||
|
.WithEnableRateLimiting(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var downstreamReRoute = downstreamReRouteBuilder.Build();
|
||||||
|
|
||||||
var reRoute = new ReRouteBuilder()
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
.WithUpstreamHttpMethod(new List<string>(){ upstreamHttpMethod })
|
.WithUpstreamHttpMethod(new List<string>(){ upstreamHttpMethod })
|
||||||
|
.WithUpstreamPathTemplate(upstreamPathTemplate)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
downstreamRoute = new OkResponse<DownstreamRoute>(new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute));
|
downstreamRoute = new OkResponse<DownstreamRoute>(new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute));
|
||||||
|
@ -55,7 +55,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
|
|
||||||
private DownstreamRoute GetPlaceholderNamesAndValues(string path, string query, ReRoute reRoute)
|
private DownstreamRoute GetPlaceholderNamesAndValues(string path, string query, ReRoute reRoute)
|
||||||
{
|
{
|
||||||
var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, reRoute.UpstreamPathTemplate.Value);
|
var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, reRoute.UpstreamTemplatePattern.OriginalValue);
|
||||||
|
|
||||||
return new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute);
|
return new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
public class DownstreamRouteProviderFactory : IDownstreamRouteProviderFactory
|
public class DownstreamRouteProviderFactory : IDownstreamRouteProviderFactory
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, IDownstreamRouteProvider> _providers;
|
private readonly Dictionary<string, IDownstreamRouteProvider> _providers;
|
||||||
private IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
public DownstreamRouteProviderFactory(IServiceProvider provider, IOcelotLoggerFactory factory)
|
public DownstreamRouteProviderFactory(IServiceProvider provider, IOcelotLoggerFactory factory)
|
||||||
{
|
{
|
||||||
@ -20,7 +20,9 @@
|
|||||||
|
|
||||||
public IDownstreamRouteProvider Get(IInternalConfiguration config)
|
public IDownstreamRouteProvider Get(IInternalConfiguration config)
|
||||||
{
|
{
|
||||||
if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration))
|
//todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have
|
||||||
|
//an upstream path template which means they are dyanmic and service discovery is on...
|
||||||
|
if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamTemplatePattern?.OriginalValue))) && IsServiceDiscovery(config.ServiceProviderConfiguration))
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request");
|
_logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request");
|
||||||
return _providers[nameof(DownstreamRouteCreator)];
|
return _providers[nameof(DownstreamRouteCreator)];
|
||||||
|
@ -51,7 +51,8 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
|
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamDownstreamPathTemplate.Value));
|
||||||
|
|
||||||
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
||||||
|
|
||||||
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
|
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
|
||||||
|
@ -14,7 +14,7 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
|||||||
return regex.IsMatch(upstreamUrlPath)
|
return regex.IsMatch(upstreamUrlPath)
|
||||||
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
||||||
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}")
|
return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}")
|
||||||
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
||||||
|
@ -1,150 +1,150 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
{
|
{
|
||||||
public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder
|
public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder
|
||||||
{
|
{
|
||||||
public Response<List<PlaceholderNameAndValue>> Find(string path, string query, string pathTemplate)
|
public Response<List<PlaceholderNameAndValue>> Find(string path, string query, string pathTemplate)
|
||||||
{
|
{
|
||||||
var placeHolderNameAndValues = new List<PlaceholderNameAndValue>();
|
var placeHolderNameAndValues = new List<PlaceholderNameAndValue>();
|
||||||
|
|
||||||
path = $"{path}{query}";
|
path = $"{path}{query}";
|
||||||
|
|
||||||
int counterForPath = 0;
|
int counterForPath = 0;
|
||||||
|
|
||||||
var delimiter = '/';
|
var delimiter = '/';
|
||||||
var nextDelimiter = '/';
|
var nextDelimiter = '/';
|
||||||
|
|
||||||
for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++)
|
for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++)
|
||||||
{
|
{
|
||||||
if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length))
|
if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length))
|
||||||
{
|
{
|
||||||
if (IsPlaceholder(pathTemplate[counterForTemplate]))
|
if (IsPlaceholder(pathTemplate[counterForTemplate]))
|
||||||
{
|
{
|
||||||
//should_find_multiple_query_string make test pass
|
//should_find_multiple_query_string make test pass
|
||||||
if (PassedQueryString(pathTemplate, counterForTemplate))
|
if (PassedQueryString(pathTemplate, counterForTemplate))
|
||||||
{
|
{
|
||||||
delimiter = '&';
|
delimiter = '&';
|
||||||
nextDelimiter = '&';
|
nextDelimiter = '&';
|
||||||
}
|
}
|
||||||
|
|
||||||
//should_find_multiple_query_string_and_path makes test pass
|
//should_find_multiple_query_string_and_path makes test pass
|
||||||
if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate))
|
if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate))
|
||||||
{
|
{
|
||||||
delimiter = '?';
|
delimiter = '?';
|
||||||
nextDelimiter = '?';
|
nextDelimiter = '?';
|
||||||
}
|
}
|
||||||
|
|
||||||
var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate);
|
var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate);
|
||||||
|
|
||||||
var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter);
|
var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter);
|
||||||
|
|
||||||
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue));
|
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue));
|
||||||
|
|
||||||
counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}');
|
counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}');
|
||||||
|
|
||||||
counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter);
|
counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<List<PlaceholderNameAndValue>>(placeHolderNameAndValues);
|
return new OkResponse<List<PlaceholderNameAndValue>>(placeHolderNameAndValues);
|
||||||
}
|
}
|
||||||
else if(IsCatchAll(path, counterForPath, pathTemplate))
|
else if(IsCatchAll(path, counterForPath, pathTemplate))
|
||||||
{
|
{
|
||||||
var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}');
|
var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}');
|
||||||
|
|
||||||
var placeholderName = GetPlaceholderName(pathTemplate, 1);
|
var placeholderName = GetPlaceholderName(pathTemplate, 1);
|
||||||
|
|
||||||
if(NothingAfterFirstForwardSlash(path))
|
if(NothingAfterFirstForwardSlash(path))
|
||||||
{
|
{
|
||||||
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, ""));
|
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, ""));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '/');
|
var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '?');
|
||||||
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue));
|
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
counterForTemplate = endOfPlaceholder;
|
counterForTemplate = endOfPlaceholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
counterForPath++;
|
counterForPath++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<List<PlaceholderNameAndValue>>(placeHolderNameAndValues);
|
return new OkResponse<List<PlaceholderNameAndValue>>(placeHolderNameAndValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate)
|
private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate)
|
||||||
{
|
{
|
||||||
return !pathTemplate.Substring(counterForTemplate).Contains("/");
|
return !pathTemplate.Substring(counterForTemplate).Contains("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate)
|
private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate)
|
||||||
{
|
{
|
||||||
return !pathTemplate.Substring(0, counterForTemplate).Contains("?");
|
return !pathTemplate.Substring(0, counterForTemplate).Contains("?");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PassedQueryString(string pathTemplate, int counterForTemplate)
|
private static bool PassedQueryString(string pathTemplate, int counterForTemplate)
|
||||||
{
|
{
|
||||||
return pathTemplate.Substring(0, counterForTemplate).Contains("?");
|
return pathTemplate.Substring(0, counterForTemplate).Contains("?");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsCatchAll(string path, int counterForPath, string pathTemplate)
|
private bool IsCatchAll(string path, int counterForPath, string pathTemplate)
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1
|
return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1
|
||||||
&& pathTemplate.Substring(0, 2) == "/{"
|
&& pathTemplate.Substring(0, 2) == "/{"
|
||||||
&& pathTemplate.IndexOf('}') == pathTemplate.Length - 1;
|
&& pathTemplate.IndexOf('}') == pathTemplate.Length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool NothingAfterFirstForwardSlash(string path)
|
private bool NothingAfterFirstForwardSlash(string path)
|
||||||
{
|
{
|
||||||
return path.Length == 1 || path.Length == 0;
|
return path.Length == 1 || path.Length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter)
|
private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter)
|
||||||
{
|
{
|
||||||
var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl);
|
var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl);
|
||||||
|
|
||||||
if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query)))
|
if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query)))
|
||||||
{
|
{
|
||||||
positionOfNextSlash = urlPath.Length;
|
positionOfNextSlash = urlPath.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl);
|
var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl);
|
||||||
|
|
||||||
return variableValue;
|
return variableValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate)
|
private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate)
|
||||||
{
|
{
|
||||||
var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1;
|
var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1;
|
||||||
|
|
||||||
var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate);
|
var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate);
|
||||||
|
|
||||||
return variableName;
|
return variableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter)
|
private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter)
|
||||||
{
|
{
|
||||||
var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate);
|
var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate);
|
||||||
return closingPlaceHolderPositionOnTemplate + 1;
|
return closingPlaceHolderPositionOnTemplate + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CharactersDontMatch(char characterOne, char characterTwo)
|
private bool CharactersDontMatch(char characterOne, char characterTwo)
|
||||||
{
|
{
|
||||||
return char.ToLower(characterOne) != char.ToLower(characterTwo);
|
return char.ToLower(characterOne) != char.ToLower(characterTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ContinueScanningUrl(int counterForUrl, int urlLength)
|
private bool ContinueScanningUrl(int counterForUrl, int urlLength)
|
||||||
{
|
{
|
||||||
return counterForUrl < urlLength;
|
return counterForUrl < urlLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPlaceholder(char character)
|
private bool IsPlaceholder(char character)
|
||||||
{
|
{
|
||||||
return character == '{';
|
return character == '{';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var response = _replacer
|
var response = _replacer
|
||||||
.Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
|
.Replace(context.DownstreamReRoute.DownstreamDownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
|
||||||
|
|
||||||
if (response.IsError)
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
@ -117,24 +117,12 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
{
|
{
|
||||||
var query = context.DownstreamRequest.Query;
|
var query = context.DownstreamRequest.Query;
|
||||||
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
|
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
|
||||||
|
return (serviceFabricPath, query);
|
||||||
if (RequestForStatefullService(query))
|
|
||||||
{
|
|
||||||
return (serviceFabricPath, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
var split = string.IsNullOrEmpty(query) ? "?" : "&";
|
|
||||||
return (serviceFabricPath, $"{query}{split}cmd=instance");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ServiceFabricRequest(DownstreamContext context)
|
private static bool ServiceFabricRequest(DownstreamContext context)
|
||||||
{
|
{
|
||||||
return context.Configuration.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery;
|
return context.Configuration.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && context.DownstreamReRoute.UseServiceDiscovery;
|
||||||
}
|
|
||||||
|
|
||||||
private static bool RequestForStatefullService(string query)
|
|
||||||
{
|
|
||||||
return query.Contains("PartitionKind") && query.Contains("PartitionKey");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,11 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
{
|
{
|
||||||
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
public Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
public Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
var downstreamPath = new StringBuilder();
|
var downstreamPath = new StringBuilder();
|
||||||
|
|
||||||
downstreamPath.Append(downstreamPathTemplate.Value);
|
downstreamPath.Append(downstreamDownstreamPathTemplate.Value);
|
||||||
|
|
||||||
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
@ -22,4 +22,4 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
return new OkResponse<DownstreamPath>(new DownstreamPath(downstreamPath.ToString()));
|
return new OkResponse<DownstreamPath>(new DownstreamPath(downstreamPath.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
{
|
{
|
||||||
public interface IDownstreamPathPlaceholderReplacer
|
public interface IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
|
Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ocelot.Configuration.Repository;
|
|
||||||
using Ocelot.Infrastructure.Extensions;
|
|
||||||
using Ocelot.Infrastructure.RequestData;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
|
|
||||||
namespace Ocelot.Errors.Middleware
|
namespace Ocelot.Errors.Middleware
|
||||||
{
|
{
|
||||||
using Configuration;
|
using Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration.Repository;
|
||||||
|
using Ocelot.Infrastructure.Extensions;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.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>
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
UnableToFindLoadBalancerError,
|
UnableToFindLoadBalancerError,
|
||||||
RequestTimedOutError,
|
RequestTimedOutError,
|
||||||
UnableToFindQoSProviderError,
|
UnableToFindQoSProviderError,
|
||||||
UnableToSetConfigInConsulError,
|
|
||||||
UnmappableRequestError,
|
UnmappableRequestError,
|
||||||
RateLimitOptionsError,
|
RateLimitOptionsError,
|
||||||
PathTemplateDoesntStartWithForwardSlash,
|
PathTemplateDoesntStartWithForwardSlash,
|
||||||
|
@ -26,7 +26,7 @@ namespace Ocelot.Headers.Middleware
|
|||||||
{
|
{
|
||||||
if (context.DownstreamReRoute.ClaimsToHeaders.Any())
|
if (context.DownstreamReRoute.ClaimsToHeaders.Any())
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers");
|
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to headers");
|
||||||
|
|
||||||
var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest);
|
var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest);
|
||||||
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Consul;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure.Consul
|
|
||||||
{
|
|
||||||
public class ConsulClientFactory : IConsulClientFactory
|
|
||||||
{
|
|
||||||
public IConsulClient Get(ConsulRegistryConfiguration config)
|
|
||||||
{
|
|
||||||
return new ConsulClient(c =>
|
|
||||||
{
|
|
||||||
c.Address = new Uri($"http://{config.Host}:{config.Port}");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(config?.Token))
|
|
||||||
{
|
|
||||||
c.Token = config.Token;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using Consul;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure.Consul
|
|
||||||
{
|
|
||||||
public interface IConsulClientFactory
|
|
||||||
{
|
|
||||||
IConsulClient Get(ConsulRegistryConfiguration config);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Ocelot.Infrastructure.Extensions
|
namespace Ocelot.Infrastructure.Extensions
|
||||||
{
|
{
|
||||||
internal static class StringValuesExtensions
|
public static class StringValuesExtensions
|
||||||
{
|
{
|
||||||
public static string GetValue(this StringValues stringValues)
|
public static string GetValue(this StringValues stringValues)
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
using System.Threading.Tasks;
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
using Ocelot.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
public interface ILoadBalancerFactory
|
public interface ILoadBalancerFactory
|
||||||
{
|
{
|
||||||
Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Infrastructure;
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Responses;
|
||||||
using Ocelot.ServiceDiscovery;
|
using Ocelot.ServiceDiscovery;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
@ -14,22 +15,29 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
_serviceProviderFactory = serviceProviderFactory;
|
_serviceProviderFactory = serviceProviderFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
public async Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
||||||
{
|
{
|
||||||
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
|
var response = _serviceProviderFactory.Get(config, reRoute);
|
||||||
|
|
||||||
|
if(response.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ILoadBalancer>(response.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceProvider = response.Data;
|
||||||
|
|
||||||
switch (reRoute.LoadBalancerOptions?.Type)
|
switch (reRoute.LoadBalancerOptions?.Type)
|
||||||
{
|
{
|
||||||
case nameof(RoundRobin):
|
case nameof(RoundRobin):
|
||||||
return new RoundRobin(async () => await serviceProvider.Get());
|
return new OkResponse<ILoadBalancer>(new RoundRobin(async () => await serviceProvider.Get()));
|
||||||
case nameof(LeastConnection):
|
case nameof(LeastConnection):
|
||||||
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
return new OkResponse<ILoadBalancer>(new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName));
|
||||||
case nameof(CookieStickySessions):
|
case nameof(CookieStickySessions):
|
||||||
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
|
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
|
||||||
var bus = new InMemoryBus<StickySession>();
|
var bus = new InMemoryBus<StickySession>();
|
||||||
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus);
|
return new OkResponse<ILoadBalancer>(new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus));
|
||||||
default:
|
default:
|
||||||
return new NoLoadBalancer(async () => await serviceProvider.Get());
|
return new OkResponse<ILoadBalancer>(new NoLoadBalancer(async () => await serviceProvider.Get()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,24 +22,36 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if(_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer))
|
Response<ILoadBalancer> result;
|
||||||
|
|
||||||
|
if (_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer))
|
||||||
{
|
{
|
||||||
loadBalancer = _loadBalancers[reRoute.LoadBalancerKey];
|
loadBalancer = _loadBalancers[reRoute.LoadBalancerKey];
|
||||||
|
|
||||||
if(reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name)
|
if (reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name)
|
||||||
{
|
{
|
||||||
loadBalancer = await _factory.Get(reRoute, config);
|
result = await _factory.Get(reRoute, config);
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ILoadBalancer>(result.Errors);
|
||||||
|
}
|
||||||
|
loadBalancer = result.Data;
|
||||||
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBalancer = await _factory.Get(reRoute, config);
|
result = await _factory.Get(reRoute, config);
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ILoadBalancer>(result.Errors);
|
||||||
|
}
|
||||||
|
loadBalancer = result.Data;
|
||||||
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||||
{
|
{
|
||||||
|
19
src/Ocelot/Logging/ITracer.cs
Normal file
19
src/Ocelot/Logging/ITracer.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace Ocelot.Logging
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
public interface ITracer
|
||||||
|
{
|
||||||
|
void Event(HttpContext httpContext, string @event);
|
||||||
|
|
||||||
|
Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
Action<string> addTraceIdToRepo,
|
||||||
|
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync);
|
||||||
|
}
|
||||||
|
}
|
@ -1,96 +1,67 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DiagnosticAdapter;
|
using Microsoft.Extensions.DiagnosticAdapter;
|
||||||
using Butterfly.Client.AspNetCore;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Butterfly.OpenTracing;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Middleware;
|
|
||||||
using Butterfly.Client.Tracing;
|
namespace Ocelot.Logging
|
||||||
using System.Linq;
|
{
|
||||||
using System.Collections.Generic;
|
public class OcelotDiagnosticListener
|
||||||
using Ocelot.Infrastructure.Extensions;
|
{
|
||||||
using Ocelot.Requester;
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly ITracer _tracer;
|
||||||
namespace Ocelot.Logging
|
|
||||||
{
|
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider serviceProvider)
|
||||||
public class OcelotDiagnosticListener
|
{
|
||||||
{
|
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
|
||||||
private readonly IServiceTracer _tracer;
|
_tracer = serviceProvider.GetService<ITracer>();
|
||||||
private readonly IOcelotLogger _logger;
|
|
||||||
|
}
|
||||||
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer)
|
|
||||||
{
|
[DiagnosticName("Ocelot.MiddlewareException")]
|
||||||
_tracer = tracer;
|
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
|
||||||
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
|
{
|
||||||
}
|
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};");
|
||||||
|
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||||
[DiagnosticName("Ocelot.MiddlewareException")]
|
}
|
||||||
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
|
|
||||||
{
|
[DiagnosticName("Ocelot.MiddlewareStarted")]
|
||||||
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};");
|
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
|
||||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
{
|
||||||
}
|
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||||
|
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
||||||
[DiagnosticName("Ocelot.MiddlewareStarted")]
|
}
|
||||||
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
|
|
||||||
{
|
[DiagnosticName("Ocelot.MiddlewareFinished")]
|
||||||
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
|
||||||
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
|
{
|
||||||
}
|
_logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
||||||
|
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
||||||
[DiagnosticName("Ocelot.MiddlewareFinished")]
|
}
|
||||||
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
|
|
||||||
{
|
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
|
||||||
_logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
|
||||||
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
|
{
|
||||||
}
|
_logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
||||||
|
Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
||||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
|
}
|
||||||
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
|
|
||||||
{
|
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")]
|
||||||
_logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
public virtual void OnMiddlewareException(Exception exception, string name)
|
||||||
Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}");
|
{
|
||||||
}
|
_logger.LogTrace($"MiddlewareException: {name}; {exception.Message};");
|
||||||
|
}
|
||||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")]
|
|
||||||
public virtual void OnMiddlewareException(Exception exception, string name)
|
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")]
|
||||||
{
|
public virtual void OnMiddlewareFinished(HttpContext httpContext, string name)
|
||||||
_logger.LogTrace($"MiddlewareException: {name}; {exception.Message};");
|
{
|
||||||
}
|
_logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
||||||
|
Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
||||||
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")]
|
}
|
||||||
public virtual void OnMiddlewareFinished(HttpContext httpContext, string name)
|
|
||||||
{
|
private void Event(HttpContext httpContext, string @event)
|
||||||
_logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
{
|
||||||
Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}");
|
_tracer?.Event(httpContext, @event);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void Event(HttpContext httpContext, string @event)
|
}
|
||||||
{
|
|
||||||
// todo - if the user isnt using tracing the code gets here and will blow up on
|
|
||||||
// _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario
|
|
||||||
// so sticking it here as well..I guess we need a factory for this but cba to do it at
|
|
||||||
// the moment
|
|
||||||
if(_tracer.GetType() == typeof(FakeServiceTracer))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var span = httpContext.GetSpan();
|
|
||||||
|
|
||||||
if(span == null)
|
|
||||||
{
|
|
||||||
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
|
|
||||||
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
|
|
||||||
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
|
|
||||||
{
|
|
||||||
spanBuilder.AsChildOf(spanContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
span = _tracer.Start(spanBuilder);
|
|
||||||
httpContext.SetSpan(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
span?.Log(LogField.CreateNew().Event(@event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Middleware
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
public delegate Task OcelotMiddlewareConfigurationDelegate(IApplicationBuilder builder);
|
||||||
|
}
|
@ -1,266 +1,162 @@
|
|||||||
namespace Ocelot.Middleware
|
namespace Ocelot.Middleware
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using DependencyInjection;
|
using DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Rafty.Concensus;
|
using Ocelot.Middleware.Pipeline;
|
||||||
using Rafty.Infrastructure;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Middleware.Pipeline;
|
|
||||||
using Pivotal.Discovery.Client;
|
public static class OcelotMiddlewareExtensions
|
||||||
using Rafty.Concensus.Node;
|
{
|
||||||
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
||||||
public static class OcelotMiddlewareExtensions
|
{
|
||||||
{
|
await builder.UseOcelot(new OcelotPipelineConfiguration());
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
return builder;
|
||||||
{
|
}
|
||||||
await builder.UseOcelot(new OcelotPipelineConfiguration());
|
|
||||||
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration)
|
||||||
return builder;
|
{
|
||||||
}
|
var config = new OcelotPipelineConfiguration();
|
||||||
|
pipelineConfiguration?.Invoke(config);
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration)
|
return await builder.UseOcelot(config);
|
||||||
{
|
}
|
||||||
var config = new OcelotPipelineConfiguration();
|
|
||||||
pipelineConfiguration?.Invoke(config);
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
||||||
return await builder.UseOcelot(config);
|
{
|
||||||
}
|
var configuration = await CreateConfiguration(builder);
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
|
||||||
{
|
ConfigureDiagnosticListener(builder);
|
||||||
var configuration = await CreateConfiguration(builder);
|
|
||||||
|
return CreateOcelotPipeline(builder, pipelineConfiguration);
|
||||||
CreateAdministrationArea(builder, configuration);
|
}
|
||||||
|
|
||||||
if (UsingRafty(builder))
|
private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
||||||
{
|
{
|
||||||
SetUpRafty(builder);
|
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
|
||||||
}
|
|
||||||
|
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
|
||||||
if (UsingEurekaServiceDiscoveryProvider(configuration))
|
|
||||||
{
|
var firstDelegate = pipelineBuilder.Build();
|
||||||
builder.UseDiscoveryClient();
|
|
||||||
}
|
/*
|
||||||
|
inject first delegate into first piece of asp.net middleware..maybe not like this
|
||||||
ConfigureDiagnosticListener(builder);
|
then because we are updating the http context in ocelot it comes out correct for
|
||||||
|
rest of asp.net..
|
||||||
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
|
*/
|
||||||
|
|
||||||
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
|
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
|
||||||
|
|
||||||
var firstDelegate = pipelineBuilder.Build();
|
builder.Use(async (context, task) =>
|
||||||
|
|
||||||
/*
|
|
||||||
inject first delegate into first piece of asp.net middleware..maybe not like this
|
|
||||||
then because we are updating the http context in ocelot it comes out correct for
|
|
||||||
rest of asp.net..
|
|
||||||
*/
|
|
||||||
|
|
||||||
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
|
|
||||||
|
|
||||||
builder.Use(async (context, task) =>
|
|
||||||
{
|
|
||||||
var downstreamContext = new DownstreamContext(context);
|
|
||||||
await firstDelegate.Invoke(downstreamContext);
|
|
||||||
});
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
|
|
||||||
{
|
|
||||||
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool UsingRafty(IApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode;
|
|
||||||
if (possible != null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetUpRafty(IApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime));
|
|
||||||
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
|
||||||
var node = (INode)builder.ApplicationServices.GetService(typeof(INode));
|
|
||||||
var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId));
|
|
||||||
node.Start(nodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
// make configuration from file system?
|
|
||||||
// earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
|
|
||||||
var fileConfig = (IOptions<FileConfiguration>)builder.ApplicationServices.GetService(typeof(IOptions<FileConfiguration>));
|
|
||||||
|
|
||||||
// now create the config
|
|
||||||
var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator));
|
|
||||||
var internalConfig = await internalConfigCreator.Create(fileConfig.Value);
|
|
||||||
|
|
||||||
// now save it in memory
|
|
||||||
var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository));
|
|
||||||
internalConfigRepo.AddOrReplace(internalConfig.Data);
|
|
||||||
|
|
||||||
var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository));
|
|
||||||
|
|
||||||
var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath));
|
|
||||||
|
|
||||||
if (UsingConsul(fileConfigRepo))
|
|
||||||
{
|
{
|
||||||
//Lots of jazz happens in here..check it out if you are using consul to store your config.
|
var downstreamContext = new DownstreamContext(context);
|
||||||
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
|
await firstDelegate.Invoke(downstreamContext);
|
||||||
}
|
});
|
||||||
else if(AdministrationApiInUse(adminPath))
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
// make configuration from file system?
|
||||||
|
// earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
|
||||||
|
var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
|
||||||
|
|
||||||
|
// now create the config
|
||||||
|
var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
|
||||||
|
var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);
|
||||||
|
//Configuration error, throw error message
|
||||||
|
if (internalConfig.IsError)
|
||||||
|
{
|
||||||
|
ThrowToStopOcelotStarting(internalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now save it in memory
|
||||||
|
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||||
|
internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||||
|
|
||||||
|
fileConfig.OnChange(async (config) =>
|
||||||
|
{
|
||||||
|
var newInternalConfig = await internalConfigCreator.Create(config);
|
||||||
|
internalConfigRepo.AddOrReplace(newInternalConfig.Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>();
|
||||||
|
|
||||||
|
var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>();
|
||||||
|
|
||||||
|
// Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern?
|
||||||
|
foreach (var configuration in configurations)
|
||||||
|
{
|
||||||
|
await configuration(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(AdministrationApiInUse(adminPath))
|
||||||
{
|
{
|
||||||
//We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the
|
//We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the
|
||||||
//admin api it works...boy this is getting a spit spags boll.
|
//admin api it works...boy this is getting a spit spags boll.
|
||||||
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
|
var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>();
|
||||||
|
|
||||||
await SetFileConfig(fileConfigSetter, fileConfig);
|
await SetFileConfig(fileConfigSetter, fileConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetOcelotConfigAndReturn(internalConfigRepo);
|
return GetOcelotConfigAndReturn(internalConfigRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
|
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
|
||||||
{
|
{
|
||||||
return adminPath.GetType() != typeof(NullAdministrationPath);
|
return adminPath != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
|
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor<FileConfiguration> fileConfig)
|
||||||
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
|
{
|
||||||
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
|
var response = await fileConfigSetter.Set(fileConfig.CurrentValue);
|
||||||
{
|
|
||||||
// get the config from consul.
|
if (IsError(response))
|
||||||
var fileConfigFromConsul = await fileConfigRepo.Get();
|
{
|
||||||
|
ThrowToStopOcelotStarting(response);
|
||||||
if (IsError(fileConfigFromConsul))
|
}
|
||||||
{
|
}
|
||||||
ThrowToStopOcelotStarting(fileConfigFromConsul);
|
|
||||||
}
|
private static bool IsError(Response response)
|
||||||
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
|
{
|
||||||
{
|
return response == null || response.IsError;
|
||||||
//there was no config in consul set the file in config in consul
|
}
|
||||||
await fileConfigRepo.Set(fileConfig.Value);
|
|
||||||
}
|
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
|
||||||
else
|
{
|
||||||
{
|
var ocelotConfiguration = provider.Get();
|
||||||
// create the internal config from consul data
|
|
||||||
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
|
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
|
||||||
|
{
|
||||||
if (IsError(internalConfig))
|
ThrowToStopOcelotStarting(ocelotConfiguration);
|
||||||
{
|
}
|
||||||
ThrowToStopOcelotStarting(internalConfig);
|
|
||||||
}
|
return ocelotConfiguration.Data;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
// add the internal config to the internal repo
|
private static void ThrowToStopOcelotStarting(Response config)
|
||||||
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
|
{
|
||||||
|
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
|
||||||
if (IsError(response))
|
}
|
||||||
{
|
|
||||||
ThrowToStopOcelotStarting(response);
|
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
||||||
}
|
{
|
||||||
}
|
var env = builder.ApplicationServices.GetService<IHostingEnvironment>();
|
||||||
|
var listener = builder.ApplicationServices.GetService<OcelotDiagnosticListener>();
|
||||||
if (IsError(internalConfig))
|
var diagnosticListener = builder.ApplicationServices.GetService<DiagnosticListener>();
|
||||||
{
|
diagnosticListener.SubscribeWithAdapter(listener);
|
||||||
ThrowToStopOcelotStarting(internalConfig);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo - this starts the poller if it has been registered...please this is so bad.
|
|
||||||
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig)
|
|
||||||
{
|
|
||||||
var response = await fileConfigSetter.Set(fileConfig.Value);
|
|
||||||
|
|
||||||
if (IsError(response))
|
|
||||||
{
|
|
||||||
ThrowToStopOcelotStarting(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ConfigNotStoredInConsul(Responses.Response<FileConfiguration> fileConfigFromConsul)
|
|
||||||
{
|
|
||||||
return fileConfigFromConsul.Data == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsError(Response response)
|
|
||||||
{
|
|
||||||
return response == null || response.IsError;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
|
|
||||||
{
|
|
||||||
var ocelotConfiguration = provider.Get();
|
|
||||||
|
|
||||||
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
|
|
||||||
{
|
|
||||||
ThrowToStopOcelotStarting(ocelotConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ocelotConfiguration.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ThrowToStopOcelotStarting(Response config)
|
|
||||||
{
|
|
||||||
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
|
||||||
{
|
|
||||||
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(configuration.AdministrationPath))
|
|
||||||
{
|
|
||||||
builder.Map(configuration.AdministrationPath, app =>
|
|
||||||
{
|
|
||||||
//todo - hack so we know that we are using internal identity server
|
|
||||||
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
|
|
||||||
if (identityServerConfiguration != null)
|
|
||||||
{
|
|
||||||
app.UseIdentityServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseAuthentication();
|
|
||||||
app.UseMvc();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
|
||||||
var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener));
|
|
||||||
var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener));
|
|
||||||
diagnosticListener.SubscribeWithAdapter(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnShutdown(IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
var node = (INode)app.ApplicationServices.GetService(typeof(INode));
|
|
||||||
node.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -172,7 +172,7 @@ namespace Ocelot.Middleware.Pipeline
|
|||||||
throw new ArgumentNullException(nameof(pipelineBuilderFunc));
|
throw new ArgumentNullException(nameof(pipelineBuilderFunc));
|
||||||
}
|
}
|
||||||
var branchBuilder = app.New();
|
var branchBuilder = app.New();
|
||||||
var predicate = pipelineBuilderFunc.Invoke(app);
|
var predicate = pipelineBuilderFunc.Invoke(branchBuilder);
|
||||||
var branch = branchBuilder.Build();
|
var branch = branchBuilder.Build();
|
||||||
|
|
||||||
var options = new MapWhenOptions
|
var options = new MapWhenOptions
|
||||||
|
@ -28,15 +28,6 @@ namespace Ocelot.Middleware.Pipeline
|
|||||||
// It also sets the Request Id if anything is set globally
|
// It also sets the Request Id if anything is set globally
|
||||||
builder.UseExceptionHandlerMiddleware();
|
builder.UseExceptionHandlerMiddleware();
|
||||||
|
|
||||||
//Expand other branch pipes
|
|
||||||
if (pipelineConfiguration.MapWhenOcelotPipeline != null)
|
|
||||||
{
|
|
||||||
foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
|
|
||||||
{
|
|
||||||
builder.MapWhen(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the request is for websockets upgrade we fork into a different pipeline
|
// If the request is for websockets upgrade we fork into a different pipeline
|
||||||
builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
|
builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
|
||||||
app =>
|
app =>
|
||||||
@ -57,6 +48,15 @@ namespace Ocelot.Middleware.Pipeline
|
|||||||
// Then we get the downstream route information
|
// Then we get the downstream route information
|
||||||
builder.UseDownstreamRouteFinderMiddleware();
|
builder.UseDownstreamRouteFinderMiddleware();
|
||||||
|
|
||||||
|
//Expand other branch pipes
|
||||||
|
if (pipelineConfiguration.MapWhenOcelotPipeline != null)
|
||||||
|
{
|
||||||
|
foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
|
||||||
|
{
|
||||||
|
builder.MapWhen(pipeline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now we have the ds route we can transform headers and stuff?
|
// Now we have the ds route we can transform headers and stuff?
|
||||||
builder.UseHttpHeadersTransformationMiddleware();
|
builder.UseHttpHeadersTransformationMiddleware();
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
<Description>This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.</Description>
|
<Description>Ocelot is an API Gateway. The project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. reference tokens. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.</Description>
|
||||||
<AssemblyTitle>Ocelot</AssemblyTitle>
|
<AssemblyTitle>Ocelot</AssemblyTitle>
|
||||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
<AssemblyName>Ocelot</AssemblyName>
|
<AssemblyName>Ocelot</AssemblyName>
|
||||||
@ -12,6 +12,7 @@
|
|||||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
@ -25,37 +26,15 @@
|
|||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
|
|
||||||
<NoWarn>NU1701</NoWarn>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="FluentValidation" Version="7.6.104" />
|
<PackageReference Include="FluentValidation" Version="7.6.104" />
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.1.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.1.0">
|
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.1.0">
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
|
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
|
||||||
<PackageReference Include="CacheManager.Core" Version="1.1.2" />
|
|
||||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
|
|
||||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Consul" Version="0.7.2.5" />
|
|
||||||
<PackageReference Include="Polly" Version="6.0.1" />
|
|
||||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
|
||||||
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
|
|
||||||
<PackageReference Include="Rafty" Version="0.4.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -26,7 +26,7 @@ namespace Ocelot.QueryStrings.Middleware
|
|||||||
{
|
{
|
||||||
if (context.DownstreamReRoute.ClaimsToQueries.Any())
|
if (context.DownstreamReRoute.ClaimsToQueries.Any())
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries");
|
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to queries");
|
||||||
|
|
||||||
var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest);
|
var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest);
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
internal class BearerToken
|
|
||||||
{
|
|
||||||
[JsonProperty("access_token")]
|
|
||||||
public string AccessToken { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("expires_in")]
|
|
||||||
public int ExpiresIn { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("token_type")]
|
|
||||||
public string TokenType { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)]
|
|
||||||
public class ExcludeFromCoverageAttribute : Attribute{}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
using Rafty.FiniteStateMachine;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class FakeCommand : ICommand
|
|
||||||
{
|
|
||||||
public FakeCommand(string value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Value { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class FilePeer
|
|
||||||
{
|
|
||||||
public string HostAndPort { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class FilePeers
|
|
||||||
{
|
|
||||||
public FilePeers()
|
|
||||||
{
|
|
||||||
Peers = new List<FilePeer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FilePeer> Peers {get; set;}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Configuration.Repository;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using Rafty.Concensus;
|
|
||||||
using Rafty.Infrastructure;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
using Rafty.Concensus.Peers;
|
|
||||||
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class FilePeersProvider : IPeersProvider
|
|
||||||
{
|
|
||||||
private readonly IOptions<FilePeers> _options;
|
|
||||||
private readonly List<IPeer> _peers;
|
|
||||||
private IBaseUrlFinder _finder;
|
|
||||||
private IInternalConfigurationRepository _repo;
|
|
||||||
private IIdentityServerConfiguration _identityServerConfig;
|
|
||||||
|
|
||||||
public FilePeersProvider(IOptions<FilePeers> options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig)
|
|
||||||
{
|
|
||||||
_identityServerConfig = identityServerConfig;
|
|
||||||
_repo = repo;
|
|
||||||
_finder = finder;
|
|
||||||
_options = options;
|
|
||||||
_peers = new List<IPeer>();
|
|
||||||
|
|
||||||
var config = _repo.Get();
|
|
||||||
foreach (var item in _options.Value.Peers)
|
|
||||||
{
|
|
||||||
var httpClient = new HttpClient();
|
|
||||||
|
|
||||||
//todo what if this errors?
|
|
||||||
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig);
|
|
||||||
_peers.Add(httpPeer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IPeer> Get()
|
|
||||||
{
|
|
||||||
return _peers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using Rafty.Concensus;
|
|
||||||
using Rafty.FiniteStateMachine;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
using Rafty.Concensus.Messages;
|
|
||||||
using Rafty.Concensus.Peers;
|
|
||||||
using Rafty.Infrastructure;
|
|
||||||
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class HttpPeer : IPeer
|
|
||||||
{
|
|
||||||
private readonly string _hostAndPort;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
|
||||||
private readonly string _baseSchemeUrlAndPort;
|
|
||||||
private BearerToken _token;
|
|
||||||
private readonly IInternalConfiguration _config;
|
|
||||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
|
||||||
|
|
||||||
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
|
||||||
{
|
|
||||||
_identityServerConfiguration = identityServerConfiguration;
|
|
||||||
_config = config;
|
|
||||||
Id = hostAndPort;
|
|
||||||
_hostAndPort = hostAndPort;
|
|
||||||
_httpClient = httpClient;
|
|
||||||
_jsonSerializerSettings = new JsonSerializerSettings() {
|
|
||||||
TypeNameHandling = TypeNameHandling.All
|
|
||||||
};
|
|
||||||
_baseSchemeUrlAndPort = finder.Find();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Id { get; }
|
|
||||||
|
|
||||||
public async Task<RequestVoteResponse> Request(RequestVote requestVote)
|
|
||||||
{
|
|
||||||
if(_token == null)
|
|
||||||
{
|
|
||||||
await SetToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
|
|
||||||
var content = new StringContent(json);
|
|
||||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
|
||||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
|
|
||||||
if(response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RequestVoteResponse(false, requestVote.Term);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(_token == null)
|
|
||||||
{
|
|
||||||
await SetToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
|
|
||||||
var content = new StringContent(json);
|
|
||||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
|
||||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
|
|
||||||
if(response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex);
|
|
||||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Response<T>> Request<T>(T command)
|
|
||||||
where T : ICommand
|
|
||||||
{
|
|
||||||
Console.WriteLine("SENDING REQUEST....");
|
|
||||||
if(_token == null)
|
|
||||||
{
|
|
||||||
await SetToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
|
|
||||||
var content = new StringContent(json);
|
|
||||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
|
||||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
|
|
||||||
if(response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Console.WriteLine("REQUEST OK....");
|
|
||||||
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
|
||||||
return new OkResponse<T>((T)okResponse.Command);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("REQUEST NOT OK....");
|
|
||||||
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SetToken()
|
|
||||||
{
|
|
||||||
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
|
||||||
var formData = new List<KeyValuePair<string, string>>
|
|
||||||
{
|
|
||||||
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
|
||||||
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
|
||||||
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
|
||||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
|
||||||
};
|
|
||||||
var content = new FormUrlEncodedContent(formData);
|
|
||||||
var response = await _httpClient.PostAsync(tokenUrl, content);
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync();
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
|
||||||
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Ocelot.Configuration.Setter;
|
|
||||||
using Rafty.FiniteStateMachine;
|
|
||||||
using Rafty.Log;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
|
||||||
{
|
|
||||||
private readonly IFileConfigurationSetter _setter;
|
|
||||||
|
|
||||||
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
|
||||||
{
|
|
||||||
_setter = setter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(LogEntry log)
|
|
||||||
{
|
|
||||||
//todo - handle an error
|
|
||||||
//hack it to just cast as at the moment we know this is the only command :P
|
|
||||||
var hack = (UpdateFileConfiguration)log.CommandData;
|
|
||||||
await _setter.Set(hack.Configuration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using Ocelot.Raft;
|
|
||||||
using Rafty.Concensus;
|
|
||||||
using Rafty.FiniteStateMachine;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
using Rafty.Concensus.Messages;
|
|
||||||
using Rafty.Concensus.Node;
|
|
||||||
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
[Authorize]
|
|
||||||
[Route("raft")]
|
|
||||||
public class RaftController : Controller
|
|
||||||
{
|
|
||||||
private readonly INode _node;
|
|
||||||
private readonly IOcelotLogger _logger;
|
|
||||||
private readonly string _baseSchemeUrlAndPort;
|
|
||||||
private readonly JsonSerializerSettings _jsonSerialiserSettings;
|
|
||||||
|
|
||||||
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
|
|
||||||
{
|
|
||||||
_jsonSerialiserSettings = new JsonSerializerSettings {
|
|
||||||
TypeNameHandling = TypeNameHandling.All
|
|
||||||
};
|
|
||||||
_baseSchemeUrlAndPort = finder.Find();
|
|
||||||
_logger = loggerFactory.CreateLogger<RaftController>();
|
|
||||||
_node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("appendentries")]
|
|
||||||
public async Task<IActionResult> AppendEntries()
|
|
||||||
{
|
|
||||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
|
||||||
{
|
|
||||||
var json = await reader.ReadToEndAsync();
|
|
||||||
|
|
||||||
var appendEntries = JsonConvert.DeserializeObject<AppendEntries>(json, _jsonSerialiserSettings);
|
|
||||||
|
|
||||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
|
|
||||||
|
|
||||||
var appendEntriesResponse = await _node.Handle(appendEntries);
|
|
||||||
|
|
||||||
return new OkObjectResult(appendEntriesResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("requestvote")]
|
|
||||||
public async Task<IActionResult> RequestVote()
|
|
||||||
{
|
|
||||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
|
||||||
{
|
|
||||||
var json = await reader.ReadToEndAsync();
|
|
||||||
|
|
||||||
var requestVote = JsonConvert.DeserializeObject<RequestVote>(json, _jsonSerialiserSettings);
|
|
||||||
|
|
||||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
|
|
||||||
|
|
||||||
var requestVoteResponse = await _node.Handle(requestVote);
|
|
||||||
|
|
||||||
return new OkObjectResult(requestVoteResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("command")]
|
|
||||||
public async Task<IActionResult> Command()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
|
||||||
{
|
|
||||||
var json = await reader.ReadToEndAsync();
|
|
||||||
|
|
||||||
var command = JsonConvert.DeserializeObject<ICommand>(json, _jsonSerialiserSettings);
|
|
||||||
|
|
||||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
|
|
||||||
|
|
||||||
var commandResponse = await _node.Accept(command);
|
|
||||||
|
|
||||||
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
|
|
||||||
|
|
||||||
return StatusCode(200, json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
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