mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 02:22:50 +08:00
commit
cc33d9f86e
14
README.md
14
README.md
@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
# Ocelot
|
# Ocelot
|
||||||
|
|
||||||
Ocelot is a .NET Api Gateway. This project is aimed at people using .NET running
|
Ocelot is a .NET API Gateway. This project is aimed at people using .NET running
|
||||||
a micro services / service orientated architecture
|
a micro services / service orientated architecture
|
||||||
that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that asp.net core supports.
|
that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports.
|
||||||
|
|
||||||
In particular I want easy integration with
|
In particular I want easy integration with
|
||||||
IdentityServer reference and bearer tokens.
|
IdentityServer reference and bearer tokens.
|
||||||
@ -61,6 +61,10 @@ Install Ocelot and it's dependencies using NuGet.
|
|||||||
|
|
||||||
`Install-Package Ocelot`
|
`Install-Package Ocelot`
|
||||||
|
|
||||||
|
Or via the .NET Core CLI:
|
||||||
|
|
||||||
|
`dotnet add package ocelot`
|
||||||
|
|
||||||
All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
|
All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
@ -83,6 +87,12 @@ advice on the easiest way to do things :)
|
|||||||
|
|
||||||
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contriute for the first time I suggest looking at a help wanted & small effort issue :)
|
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contriute for the first time I suggest looking at a help wanted & small effort issue :)
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
If you think this project is worth supporting financially please make a contribution using the button below!
|
||||||
|
|
||||||
|
[](https://www.paypal.me/ThreeMammals/)
|
||||||
|
|
||||||
## Things that are currently annoying me
|
## Things that are currently annoying me
|
||||||
|
|
||||||
[ Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
|
[ Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
|
||||||
|
1028
build.cake
1028
build.cake
File diff suppressed because it is too large
Load Diff
@ -93,7 +93,7 @@ Then map the authentication provider key to a ReRoute in your configuration e.g.
|
|||||||
Identity Server Bearer Tokens
|
Identity Server Bearer Tokens
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In order to use IdentityServer bearer tokens register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consul the IdentityServer documentation.
|
In order to use IdentityServer bearer tokens register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
@ -141,4 +141,4 @@ Allowed Scopes
|
|||||||
|
|
||||||
If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list.
|
If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list.
|
||||||
|
|
||||||
This is a way to restrict access to a ReRoute on a per scope basis.
|
This is a way to restrict access to a ReRoute on a per scope basis.
|
||||||
|
@ -42,4 +42,4 @@ In addition to this you must add a file called peers.json to your main project a
|
|||||||
|
|
||||||
Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty.
|
Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty.
|
||||||
|
|
||||||
Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting there configuration.
|
Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration.
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
Rate Limiting
|
Rate Limiting
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
|
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
|
||||||
|
|
||||||
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
|
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
|
||||||
|
|
||||||
OK so to get rate limiting working for a ReRoute you need to add the following json to it.
|
OK so to get rate limiting working for a ReRoute you need to add the following json to it.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RateLimitOptions": {
|
"RateLimitOptions": {
|
||||||
"ClientWhitelist": [],
|
"ClientWhitelist": [],
|
||||||
"EnableRateLimiting": true,
|
"EnableRateLimiting": true,
|
||||||
"Period": "1s",
|
"Period": "1s",
|
||||||
"PeriodTimespan": 1,
|
"PeriodTimespan": 1,
|
||||||
"Limit": 1
|
"Limit": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
|
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
|
||||||
EnableRateLimiting - This value specifies enable endpoint rate limiting.
|
EnableRateLimiting - This value specifies enable endpoint rate limiting.
|
||||||
Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on.
|
Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request.
|
||||||
PeriodTimespan - This value specifies that we can retry after a certain number of seconds.
|
PeriodTimespan - This value specifies that we can retry after a certain number of seconds.
|
||||||
Limit - This value specifies the maximum number of requests that a client can make in a defined period.
|
Limit - This value specifies the maximum number of requests that a client can make in a defined period.
|
||||||
|
|
||||||
You can also set the following in the GlobalConfiguration part of ocelot.json
|
You can also set the following in the GlobalConfiguration part of ocelot.json
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RateLimitOptions": {
|
"RateLimitOptions": {
|
||||||
"DisableRateLimitHeaders": false,
|
"DisableRateLimitHeaders": false,
|
||||||
"QuotaExceededMessage": "Customize Tips!",
|
"QuotaExceededMessage": "Customize Tips!",
|
||||||
"HttpStatusCode": 999,
|
"HttpStatusCode": 999,
|
||||||
"ClientIdHeader" : "Test"
|
"ClientIdHeader" : "Test"
|
||||||
}
|
}
|
||||||
|
|
||||||
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled.
|
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled.
|
||||||
QuotaExceededMessage - This value specifies the exceeded message.
|
QuotaExceededMessage - This value specifies the exceeded message.
|
||||||
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
|
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
|
||||||
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"
|
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"
|
||||||
|
@ -1,154 +1,171 @@
|
|||||||
.. service-discovery:
|
.. service-discovery:
|
||||||
|
|
||||||
Service Discovery
|
Service Discovery
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Ocelot allows you to specify a service discovery provider and will use this to find the host and port
|
Ocelot allows you to specify a service discovery provider and will use this to find the host and port
|
||||||
for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the
|
for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the
|
||||||
GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes
|
GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes
|
||||||
you specify a ServiceName for at ReRoute level.
|
you specify a ServiceName for at ReRoute level.
|
||||||
|
|
||||||
Consul
|
Consul
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"ServiceDiscoveryProvider": {
|
"ServiceDiscoveryProvider": {
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 9500
|
"Port": 8500
|
||||||
}
|
}
|
||||||
|
|
||||||
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, UseServiceDiscovery 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
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
"ServiceName": "product",
|
"ServiceName": "product",
|
||||||
"LoadBalancer": "LeastConnection",
|
"LoadBalancerOptions": {
|
||||||
"UseServiceDiscovery": true
|
"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.
|
}
|
||||||
|
|
||||||
ACL Token
|
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services.
|
||||||
---------
|
|
||||||
|
A lot of people have asked me to implement a feature where Ocelot polls consul for latest service information rather than per request. If you want to poll consul for the latest services rather than per request (default behaviour) then you need to set the following configuration.
|
||||||
If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below.
|
|
||||||
|
.. code-block:: json
|
||||||
.. code-block:: json
|
|
||||||
|
"ServiceDiscoveryProvider": {
|
||||||
"ServiceDiscoveryProvider": {
|
"Host": "localhost",
|
||||||
"Host": "localhost",
|
"Port": 8500,
|
||||||
"Port": 9500,
|
"Type": "PollConsul",
|
||||||
"Token": "footoken"
|
"PollingInterval": 100
|
||||||
}
|
}
|
||||||
|
|
||||||
Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request.
|
The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration.
|
||||||
|
|
||||||
Eureka
|
Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volitile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling consul per request (as sidecar agent). If you are calling a remote consul agent then polling will be a good performance improvement.
|
||||||
^^^^^^
|
|
||||||
|
ACL Token
|
||||||
This feature was requested as part of `Issue 262 <https://github.com/TomPallister/Ocelot/issue/262>`_ . to add support for Netflix's
|
---------
|
||||||
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
|
|
||||||
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
|
If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below.
|
||||||
|
|
||||||
In order to get this working add the following to ocelot.json..
|
.. code-block:: json
|
||||||
|
|
||||||
.. code-block:: json
|
"ServiceDiscoveryProvider": {
|
||||||
|
"Host": "localhost",
|
||||||
"ServiceDiscoveryProvider": {
|
"Port": 8500,
|
||||||
"Type": "Eureka"
|
"Token": "footoken"
|
||||||
}
|
}
|
||||||
|
|
||||||
And following the guide `Here <https://steeltoe.io/docs/steeltoe-discovery/>`_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it.
|
Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request.
|
||||||
|
|
||||||
.. code-block:: json
|
Eureka
|
||||||
|
^^^^^^
|
||||||
"eureka": {
|
|
||||||
"client": {
|
This feature was requested as part of `Issue 262 <https://github.com/TomPallister/Ocelot/issue/262>`_ . to add support for Netflix's
|
||||||
"serviceUrl": "http://localhost:8761/eureka/",
|
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
|
||||||
"shouldRegisterWithEureka": false,
|
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
|
||||||
"shouldFetchRegistry": true
|
|
||||||
}
|
In order to get this working add the following to ocelot.json..
|
||||||
}
|
|
||||||
|
.. code-block:: json
|
||||||
I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there.
|
|
||||||
|
"ServiceDiscoveryProvider": {
|
||||||
Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with
|
"Type": "Eureka"
|
||||||
Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory.
|
}
|
||||||
When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code
|
|
||||||
is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work.
|
And following the guide `Here <https://steeltoe.io/docs/steeltoe-discovery/>`_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it.
|
||||||
|
|
||||||
Dynamic Routing
|
.. code-block:: json
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
"eureka": {
|
||||||
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using
|
"client": {
|
||||||
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
|
"serviceUrl": "http://localhost:8761/eureka/",
|
||||||
downstream service with the service discovery provider.
|
"shouldRegisterWithEureka": false,
|
||||||
|
"shouldFetchRegistry": true
|
||||||
An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
|
}
|
||||||
the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and
|
}
|
||||||
port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products.
|
|
||||||
Ocelot will apprend any query string to the downstream url as normal.
|
I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there.
|
||||||
|
|
||||||
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
|
Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with
|
||||||
need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
|
Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory.
|
||||||
|
When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code
|
||||||
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but
|
is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work.
|
||||||
talk to private services over http) that will be applied to all of the dynamic ReRoutes.
|
|
||||||
|
Dynamic Routing
|
||||||
The config might look something like
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. code-block:: json
|
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 segmentof the upstream path to lookup the
|
||||||
{
|
downstream service with the service discovery provider.
|
||||||
"ReRoutes": [],
|
|
||||||
"Aggregates": [],
|
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
|
||||||
"GlobalConfiguration": {
|
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
|
||||||
"RequestIdKey": null,
|
port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products.
|
||||||
"ServiceDiscoveryProvider": {
|
Ocelot will apprend any query string to the downstream url as normal.
|
||||||
"Host": "localhost",
|
|
||||||
"Port": 8510,
|
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
|
||||||
"Type": null,
|
need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
|
||||||
"Token": null,
|
|
||||||
"ConfigurationKey": null
|
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but
|
||||||
},
|
talk to private services over http) that will be applied to all of the dynamic ReRoutes.
|
||||||
"RateLimitOptions": {
|
|
||||||
"ClientIdHeader": "ClientId",
|
The config might look something like
|
||||||
"QuotaExceededMessage": null,
|
|
||||||
"RateLimitCounterPrefix": "ocelot",
|
.. code-block:: json
|
||||||
"DisableRateLimitHeaders": false,
|
|
||||||
"HttpStatusCode": 429
|
{
|
||||||
},
|
"ReRoutes": [],
|
||||||
"QoSOptions": {
|
"Aggregates": [],
|
||||||
"ExceptionsAllowedBeforeBreaking": 0,
|
"GlobalConfiguration": {
|
||||||
"DurationOfBreak": 0,
|
"RequestIdKey": null,
|
||||||
"TimeoutValue": 0
|
"ServiceDiscoveryProvider": {
|
||||||
},
|
"Host": "localhost",
|
||||||
"BaseUrl": null,
|
"Port": 8510,
|
||||||
"LoadBalancerOptions": {
|
"Type": null,
|
||||||
"Type": "LeastConnection",
|
"Token": null,
|
||||||
"Key": null,
|
"ConfigurationKey": null
|
||||||
"Expiry": 0
|
},
|
||||||
},
|
"RateLimitOptions": {
|
||||||
"DownstreamScheme": "http",
|
"ClientIdHeader": "ClientId",
|
||||||
"HttpHandlerOptions": {
|
"QuotaExceededMessage": null,
|
||||||
"AllowAutoRedirect": false,
|
"RateLimitCounterPrefix": "ocelot",
|
||||||
"UseCookieContainer": false,
|
"DisableRateLimitHeaders": false,
|
||||||
"UseTracing": false
|
"HttpStatusCode": 429
|
||||||
}
|
},
|
||||||
}
|
"QoSOptions": {
|
||||||
}
|
"ExceptionsAllowedBeforeBreaking": 0,
|
||||||
|
"DurationOfBreak": 0,
|
||||||
Please take a look through all of the docs to understand these options.
|
"TimeoutValue": 0
|
||||||
|
},
|
||||||
|
"BaseUrl": null,
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection",
|
||||||
|
"Key": null,
|
||||||
|
"Expiry": 0
|
||||||
|
},
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"HttpHandlerOptions": {
|
||||||
|
"AllowAutoRedirect": false,
|
||||||
|
"UseCookieContainer": false,
|
||||||
|
"UseTracing": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Please take a look through all of the docs to understand these options.
|
||||||
|
@ -1,120 +1,120 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
|
||||||
namespace Ocelot.Cache.Middleware
|
namespace Ocelot.Cache.Middleware
|
||||||
{
|
{
|
||||||
public class OutputCacheMiddleware : OcelotMiddleware
|
public class OutputCacheMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly IOcelotCache<CachedResponse> _outputCache;
|
private readonly IOcelotCache<CachedResponse> _outputCache;
|
||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IRegionCreator _regionCreator;
|
||||||
|
|
||||||
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IOcelotCache<CachedResponse> outputCache,
|
IOcelotCache<CachedResponse> outputCache,
|
||||||
IRegionCreator regionCreator)
|
IRegionCreator regionCreator)
|
||||||
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_outputCache = outputCache;
|
_outputCache = outputCache;
|
||||||
_regionCreator = regionCreator;
|
_regionCreator = regionCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
if (!context.DownstreamReRoute.IsCached)
|
if (!context.DownstreamReRoute.IsCached)
|
||||||
{
|
{
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
|
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
|
||||||
|
|
||||||
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
|
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
|
||||||
|
|
||||||
var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region);
|
var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region);
|
||||||
|
|
||||||
if (cached != null)
|
if (cached != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
|
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
|
||||||
|
|
||||||
var response = CreateHttpResponseMessage(cached);
|
var response = CreateHttpResponseMessage(cached);
|
||||||
SetHttpResponseMessageThisRequest(context, response);
|
SetHttpResponseMessageThisRequest(context, response);
|
||||||
|
|
||||||
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
|
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
|
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
|
|
||||||
if (context.IsError)
|
if (context.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
|
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cached = await CreateCachedResponse(context.DownstreamResponse);
|
cached = await CreateCachedResponse(context.DownstreamResponse);
|
||||||
|
|
||||||
_outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
|
_outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
|
||||||
|
|
||||||
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
|
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response)
|
private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response)
|
||||||
{
|
{
|
||||||
context.DownstreamResponse = response;
|
context.DownstreamResponse = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
|
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
|
||||||
{
|
{
|
||||||
if (cached == null)
|
if (cached == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
|
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
|
||||||
|
|
||||||
var streamContent = new StreamContent(content);
|
var streamContent = new StreamContent(content);
|
||||||
|
|
||||||
foreach (var header in cached.ContentHeaders)
|
foreach (var header in cached.ContentHeaders)
|
||||||
{
|
{
|
||||||
streamContent.Headers.Add(header.Key, header.Value);
|
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList());
|
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
|
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
|
||||||
{
|
{
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusCode = response.StatusCode;
|
var statusCode = response.StatusCode;
|
||||||
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
|
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
|
||||||
string body = null;
|
string body = null;
|
||||||
|
|
||||||
if (response.Content != null)
|
if (response.Content != null)
|
||||||
{
|
{
|
||||||
var content = await response.Content.ReadAsByteArrayAsync();
|
var content = await response.Content.ReadAsByteArrayAsync();
|
||||||
body = Convert.ToBase64String(content);
|
body = Convert.ToBase64String(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
|
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
|
||||||
|
|
||||||
var cached = new CachedResponse(statusCode, headers, body, contentHeaders);
|
var cached = new CachedResponse(statusCode, headers, body, contentHeaders);
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private string _type;
|
private string _type;
|
||||||
private string _token;
|
private string _token;
|
||||||
private string _configurationKey;
|
private string _configurationKey;
|
||||||
|
private int _pollingInterval;
|
||||||
|
|
||||||
public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
|
public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
|
||||||
{
|
{
|
||||||
@ -38,9 +39,15 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceProviderConfigurationBuilder WithPollingInterval(int pollingInterval)
|
||||||
|
{
|
||||||
|
_pollingInterval = pollingInterval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ServiceProviderConfiguration Build()
|
public ServiceProviderConfiguration Build()
|
||||||
{
|
{
|
||||||
return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey);
|
return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey, _pollingInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}";
|
return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}";
|
return $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}|{string.Join(",", fileReRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
||||||
{
|
{
|
||||||
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing;
|
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing;
|
||||||
|
|
||||||
return new HttpHandlerOptions(options.AllowAutoRedirect,
|
return new HttpHandlerOptions(options.AllowAutoRedirect,
|
||||||
options.UseCookieContainer, useTracing);
|
options.UseCookieContainer, useTracing, options.UseProxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
var port = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
var port = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||||
var host = globalConfiguration?.ServiceDiscoveryProvider?.Host ?? "consul";
|
var host = globalConfiguration?.ServiceDiscoveryProvider?.Host ?? "consul";
|
||||||
|
var pollingInterval = globalConfiguration?.ServiceDiscoveryProvider?.PollingInterval ?? 0;
|
||||||
|
|
||||||
return new ServiceProviderConfigurationBuilder()
|
return new ServiceProviderConfigurationBuilder()
|
||||||
.WithHost(host)
|
.WithHost(host)
|
||||||
@ -16,6 +17,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
.WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type)
|
.WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type)
|
||||||
.WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token)
|
.WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token)
|
||||||
.WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey)
|
.WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey)
|
||||||
|
.WithPollingInterval(pollingInterval)
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
|
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
|
||||||
{
|
{
|
||||||
private const string RegExMatchEverything = "[0-9a-zA-Z].*";
|
private const string RegExMatchOneOrMoreOfEverything = ".+";
|
||||||
private const string RegExMatchEndString = "$";
|
private const string RegExMatchEndString = "$";
|
||||||
private const string RegExIgnoreCase = "(?i)";
|
private const string RegExIgnoreCase = "(?i)";
|
||||||
private const string RegExForwardSlashOnly = "^/$";
|
private const string RegExForwardSlashOnly = "^/$";
|
||||||
@ -37,7 +37,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
foreach (var placeholder in placeholders)
|
foreach (var placeholder in placeholders)
|
||||||
{
|
{
|
||||||
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
|
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchOneOrMoreOfEverything);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upstreamTemplate == "/")
|
if (upstreamTemplate == "/")
|
||||||
|
@ -5,13 +5,16 @@
|
|||||||
public FileHttpHandlerOptions()
|
public FileHttpHandlerOptions()
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false;
|
AllowAutoRedirect = false;
|
||||||
UseCookieContainer = false;
|
UseCookieContainer = false;
|
||||||
|
UseProxy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AllowAutoRedirect { get; set; }
|
public bool AllowAutoRedirect { get; set; }
|
||||||
|
|
||||||
public bool UseCookieContainer { get; set; }
|
public bool UseCookieContainer { get; set; }
|
||||||
|
|
||||||
public bool UseTracing { get; set; }
|
public bool UseTracing { get; set; }
|
||||||
|
|
||||||
|
public bool UseProxy { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,6 @@ namespace Ocelot.Configuration.File
|
|||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
public string ConfigurationKey { get; set; }
|
public string ConfigurationKey { get; set; }
|
||||||
|
public int PollingInterval { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Raft;
|
using Ocelot.Raft;
|
||||||
using Rafty.Concensus;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
|
using Rafty.Concensus.Node;
|
||||||
using Repository;
|
using Repository;
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@ -50,7 +50,7 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
var node = (INode)test;
|
var node = (INode)test;
|
||||||
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||||
if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>))
|
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 BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HttpHandlerOptions
|
public class HttpHandlerOptions
|
||||||
{
|
{
|
||||||
public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer, bool useTracing)
|
public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer, bool useTracing, bool useProxy)
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = allowAutoRedirect;
|
AllowAutoRedirect = allowAutoRedirect;
|
||||||
UseCookieContainer = useCookieContainer;
|
UseCookieContainer = useCookieContainer;
|
||||||
UseTracing = useTracing;
|
UseTracing = useTracing;
|
||||||
|
UseProxy = useProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -23,9 +24,14 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseCookieContainer { get; private set; }
|
public bool UseCookieContainer { get; private set; }
|
||||||
|
|
||||||
// <summary>
|
/// <summary>
|
||||||
/// Specify is handler has to use a opentracing
|
/// Specify is handler has to use a opentracing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseTracing { get; private set; }
|
public bool UseTracing { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify if handler has to use a proxy
|
||||||
|
/// </summary>
|
||||||
|
public bool UseProxy { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
private bool _allowAutoRedirect;
|
private bool _allowAutoRedirect;
|
||||||
private bool _useCookieContainer;
|
private bool _useCookieContainer;
|
||||||
private bool _useTracing;
|
private bool _useTracing;
|
||||||
|
private bool _useProxy;
|
||||||
|
|
||||||
public HttpHandlerOptionsBuilder WithAllowAutoRedirect(bool input)
|
public HttpHandlerOptionsBuilder WithAllowAutoRedirect(bool input)
|
||||||
{
|
{
|
||||||
@ -24,9 +25,15 @@
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpHandlerOptionsBuilder WithUseProxy(bool useProxy)
|
||||||
|
{
|
||||||
|
_useProxy = useProxy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public HttpHandlerOptions Build()
|
public HttpHandlerOptions Build()
|
||||||
{
|
{
|
||||||
return new HttpHandlerOptions(_allowAutoRedirect, _useCookieContainer, _useTracing);
|
return new HttpHandlerOptions(_allowAutoRedirect, _useCookieContainer, _useTracing, _useProxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
{
|
{
|
||||||
public class ServiceProviderConfiguration
|
public class ServiceProviderConfiguration
|
||||||
{
|
{
|
||||||
public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey)
|
public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey, int pollingInterval)
|
||||||
{
|
{
|
||||||
ConfigurationKey = configurationKey;
|
ConfigurationKey = configurationKey;
|
||||||
Host = host;
|
Host = host;
|
||||||
Port = port;
|
Port = port;
|
||||||
Token = token;
|
Token = token;
|
||||||
Type = type;
|
Type = type;
|
||||||
|
PollingInterval = pollingInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Host { get; }
|
public string Host { get; }
|
||||||
@ -16,5 +17,6 @@
|
|||||||
public string Type { get; }
|
public string Type { get; }
|
||||||
public string Token { get; }
|
public string Token { get; }
|
||||||
public string ConfigurationKey { get; }
|
public string ConfigurationKey { get; }
|
||||||
|
public int PollingInterval { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Setter
|
namespace Ocelot.Configuration.Setter
|
||||||
{
|
{
|
||||||
public interface IFileConfigurationSetter
|
public interface IFileConfigurationSetter
|
||||||
{
|
{
|
||||||
Task<Response> Set(FileConfiguration config);
|
Task<Response> Set(FileConfiguration config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder)
|
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder)
|
||||||
{
|
{
|
||||||
const string pattern = "(?i)ocelot.([a-zA-Z0-9]*).json";
|
const string pattern = "(?i)ocelot\\.([a-zA-Z0-9]*)(\\.json)$";
|
||||||
|
|
||||||
var reg = new Regex(pattern);
|
var reg = new Regex(pattern);
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ using Rafty.Log;
|
|||||||
|
|
||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
|
using Rafty.Concensus.Node;
|
||||||
|
|
||||||
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
||||||
{
|
{
|
||||||
private readonly IServiceCollection _services;
|
private readonly IServiceCollection _services;
|
||||||
@ -21,7 +23,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
public IOcelotAdministrationBuilder AddRafty()
|
public IOcelotAdministrationBuilder AddRafty()
|
||||||
{
|
{
|
||||||
var settings = new InMemorySettings(4000, 5000, 100, 5000);
|
var settings = new InMemorySettings(4000, 6000, 100, 10000);
|
||||||
_services.AddSingleton<ILog, SqlLiteLog>();
|
_services.AddSingleton<ILog, SqlLiteLog>();
|
||||||
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
||||||
_services.AddSingleton<ISettings>(settings);
|
_services.AddSingleton<ISettings>(settings);
|
||||||
|
@ -45,8 +45,10 @@ namespace Ocelot.DependencyInjection
|
|||||||
using Ocelot.Infrastructure.Consul;
|
using Ocelot.Infrastructure.Consul;
|
||||||
using Butterfly.Client.Tracing;
|
using Butterfly.Client.Tracing;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
using Pivotal.Discovery.Client;
|
|
||||||
using ServiceDiscovery.Providers;
|
using ServiceDiscovery.Providers;
|
||||||
|
using Steeltoe.Common.Discovery;
|
||||||
|
using Pivotal.Discovery.Client;
|
||||||
|
using Ocelot.Request.Creator;
|
||||||
|
|
||||||
public class OcelotBuilder : IOcelotBuilder
|
public class OcelotBuilder : IOcelotBuilder
|
||||||
{
|
{
|
||||||
@ -160,6 +162,8 @@ namespace Ocelot.DependencyInjection
|
|||||||
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
||||||
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
||||||
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
||||||
|
_services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
||||||
|
_services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||||
|
@ -49,7 +49,8 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
|
|
||||||
private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost)
|
private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost)
|
||||||
{
|
{
|
||||||
return reRoute.UpstreamHttpMethod.Count == 0 || reRoute.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower()) && !(!string.IsNullOrEmpty(reRoute.UpstreamHost) && reRoute.UpstreamHost != upstreamHost);
|
return (reRoute.UpstreamHttpMethod.Count == 0 || reRoute.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())) &&
|
||||||
|
(string.IsNullOrEmpty(reRoute.UpstreamHost) || reRoute.UpstreamHost == upstreamHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DownstreamRoute GetPlaceholderNamesAndValues(string path, ReRoute reRoute)
|
private DownstreamRoute GetPlaceholderNamesAndValues(string path, ReRoute reRoute)
|
||||||
|
@ -1,55 +1,55 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Infrastructure;
|
using Ocelot.Infrastructure;
|
||||||
using Ocelot.Infrastructure.Extensions;
|
using Ocelot.Infrastructure.Extensions;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
using Ocelot.Request.Middleware;
|
using Ocelot.Request.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Headers
|
namespace Ocelot.Headers
|
||||||
{
|
{
|
||||||
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
||||||
{
|
{
|
||||||
private readonly IPlaceholders _placeholders;
|
private readonly IPlaceholders _placeholders;
|
||||||
|
|
||||||
public HttpResponseHeaderReplacer(IPlaceholders placeholders)
|
public HttpResponseHeaderReplacer(IPlaceholders placeholders)
|
||||||
{
|
{
|
||||||
_placeholders = placeholders;
|
_placeholders = placeholders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request)
|
public Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request)
|
||||||
{
|
{
|
||||||
foreach (var f in fAndRs)
|
foreach (var f in fAndRs)
|
||||||
{
|
{
|
||||||
var dict = response.Headers.ToDictionary(x => x.Key);
|
var dict = response.Headers.ToDictionary(x => x.Key);
|
||||||
|
|
||||||
//if the response headers contain a matching find and replace
|
//if the response headers contain a matching find and replace
|
||||||
if(dict.TryGetValue(f.Key, out var values))
|
if(dict.TryGetValue(f.Key, out var values))
|
||||||
{
|
{
|
||||||
//check to see if it is a placeholder in the find...
|
//check to see if it is a placeholder in the find...
|
||||||
var placeholderValue = _placeholders.Get(f.Find, request);
|
var placeholderValue = _placeholders.Get(f.Find, request);
|
||||||
|
|
||||||
if(!placeholderValue.IsError)
|
if(!placeholderValue.IsError)
|
||||||
{
|
{
|
||||||
//if it is we need to get the value of the placeholder
|
//if it is we need to get the value of the placeholder
|
||||||
var replaced = values.Values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
|
var replaced = values.Values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
|
||||||
|
|
||||||
response.Headers.Remove(response.Headers.First(item => item.Key == f.Key));
|
response.Headers.Remove(response.Headers.First(item => item.Key == f.Key));
|
||||||
response.Headers.Add(new Header(f.Key, new List<string> { replaced }));
|
response.Headers.Add(new Header(f.Key, new List<string> { replaced }));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var replaced = values.Values.ToList()[f.Index].Replace(f.Find, f.Replace);
|
var replaced = values.Values.ToList()[f.Index].Replace(f.Find, f.Replace);
|
||||||
|
|
||||||
response.Headers.Remove(response.Headers.First(item => item.Key == f.Key));
|
response.Headers.Remove(response.Headers.First(item => item.Key == f.Key));
|
||||||
response.Headers.Add(new Header(f.Key, new List<string> { replaced }));
|
response.Headers.Add(new Header(f.Key, new List<string> { replaced }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse();
|
return new OkResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,48 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.Headers.Middleware
|
namespace Ocelot.Headers.Middleware
|
||||||
{
|
{
|
||||||
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
|
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
||||||
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
||||||
private readonly IAddHeadersToResponse _addHeadersToResponse;
|
private readonly IAddHeadersToResponse _addHeadersToResponse;
|
||||||
private readonly IAddHeadersToRequest _addHeadersToRequest;
|
private readonly IAddHeadersToRequest _addHeadersToRequest;
|
||||||
|
|
||||||
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
|
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IHttpContextRequestHeaderReplacer preReplacer,
|
IHttpContextRequestHeaderReplacer preReplacer,
|
||||||
IHttpResponseHeaderReplacer postReplacer,
|
IHttpResponseHeaderReplacer postReplacer,
|
||||||
IAddHeadersToResponse addHeadersToResponse,
|
IAddHeadersToResponse addHeadersToResponse,
|
||||||
IAddHeadersToRequest addHeadersToRequest)
|
IAddHeadersToRequest addHeadersToRequest)
|
||||||
:base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>())
|
:base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>())
|
||||||
{
|
{
|
||||||
_addHeadersToResponse = addHeadersToResponse;
|
_addHeadersToResponse = addHeadersToResponse;
|
||||||
_addHeadersToRequest = addHeadersToRequest;
|
_addHeadersToRequest = addHeadersToRequest;
|
||||||
_next = next;
|
_next = next;
|
||||||
_postReplacer = postReplacer;
|
_postReplacer = postReplacer;
|
||||||
_preReplacer = preReplacer;
|
_preReplacer = preReplacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var preFAndRs = context.DownstreamReRoute.UpstreamHeadersFindAndReplace;
|
var preFAndRs = context.DownstreamReRoute.UpstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
//todo - this should be on httprequestmessage not httpcontext?
|
//todo - this should be on httprequestmessage not httpcontext?
|
||||||
_preReplacer.Replace(context.HttpContext, preFAndRs);
|
_preReplacer.Replace(context.HttpContext, preFAndRs);
|
||||||
|
|
||||||
_addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext);
|
_addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext);
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
|
|
||||||
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
|
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
|
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
|
||||||
|
|
||||||
_addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
|
_addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/Ocelot/Infrastructure/FrameworkDescription.cs
Normal file
12
src/Ocelot/Infrastructure/FrameworkDescription.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ocelot.Infrastructure
|
||||||
|
{
|
||||||
|
public class FrameworkDescription : IFrameworkDescription
|
||||||
|
{
|
||||||
|
public string Get()
|
||||||
|
{
|
||||||
|
return RuntimeInformation.FrameworkDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/Ocelot/Infrastructure/IFrameworkDescription.cs
Normal file
7
src/Ocelot/Infrastructure/IFrameworkDescription.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IFrameworkDescription
|
||||||
|
{
|
||||||
|
string Get();
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
public class RoundRobin : ILoadBalancer
|
public class RoundRobin : ILoadBalancer
|
||||||
{
|
{
|
||||||
private readonly Func<Task<List<Service>>> _services;
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
private int _last;
|
private int _last;
|
||||||
|
|
||||||
@ -21,14 +22,17 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
{
|
{
|
||||||
var services = await _services();
|
var services = await _services();
|
||||||
if (_last >= services.Count)
|
lock(_lock)
|
||||||
{
|
{
|
||||||
_last = 0;
|
if (_last >= services.Count)
|
||||||
}
|
{
|
||||||
|
_last = 0;
|
||||||
|
}
|
||||||
|
|
||||||
var next = services[_last];
|
var next = services[_last];
|
||||||
_last++;
|
_last++;
|
||||||
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
|
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
using Rafty.Infrastructure;
|
using Rafty.Infrastructure;
|
||||||
using Ocelot.Middleware.Pipeline;
|
using Ocelot.Middleware.Pipeline;
|
||||||
using Pivotal.Discovery.Client;
|
using Pivotal.Discovery.Client;
|
||||||
|
using Rafty.Concensus.Node;
|
||||||
|
|
||||||
public static class OcelotMiddlewareExtensions
|
public static class OcelotMiddlewareExtensions
|
||||||
{
|
{
|
||||||
@ -91,7 +92,7 @@
|
|||||||
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
||||||
var node = (INode)builder.ApplicationServices.GetService(typeof(INode));
|
var node = (INode)builder.ApplicationServices.GetService(typeof(INode));
|
||||||
var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId));
|
var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId));
|
||||||
node.Start(nodeId.Id);
|
node.Start(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
||||||
|
@ -25,37 +25,37 @@
|
|||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
<PackageReference Include="Butterfly.Client" Version="0.0.8"/>
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
|
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="FluentValidation" Version="7.5.2" />
|
<PackageReference Include="FluentValidation" Version="7.5.2"/>
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.4"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.0.3"/>
|
||||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
|
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1">
|
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1">
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2"/>
|
||||||
<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="System.Text.RegularExpressions" Version="4.3.0"/>
|
||||||
<PackageReference Include="CacheManager.Core" Version="1.1.2" />
|
<PackageReference Include="CacheManager.Core" Version="1.1.2"/>
|
||||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" 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="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2"/>
|
||||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
<PackageReference Include="Consul" Version="0.7.2.4"/>
|
||||||
<PackageReference Include="Polly" Version="6.0.1" />
|
<PackageReference Include="Polly" Version="6.0.1"/>
|
||||||
<PackageReference Include="Pivotal.Discovery.Client" Version="1.1.0" />
|
<PackageReference Include="IdentityServer4" Version="2.2.0"/>
|
||||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1"/>
|
||||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
<PackageReference Include="Rafty" Version="0.4.4"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Rafty.FiniteStateMachine;
|
|
||||||
using Rafty.Infrastructure;
|
|
||||||
using Rafty.Log;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
|
||||||
[ExcludeFromCoverage]
|
|
||||||
public class FileFsm : IFiniteStateMachine
|
|
||||||
{
|
|
||||||
private string _id;
|
|
||||||
|
|
||||||
public FileFsm(NodeId nodeId)
|
|
||||||
{
|
|
||||||
_id = nodeId.Id.Replace("/","").Replace(":","");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Handle(LogEntry log)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(log.CommandData);
|
|
||||||
File.AppendAllText(_id, json);
|
|
||||||
}
|
|
||||||
catch(Exception exception)
|
|
||||||
{
|
|
||||||
Console.WriteLine(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,6 +9,8 @@ using Rafty.Infrastructure;
|
|||||||
|
|
||||||
namespace Ocelot.Raft
|
namespace Ocelot.Raft
|
||||||
{
|
{
|
||||||
|
using Rafty.Concensus.Peers;
|
||||||
|
|
||||||
[ExcludeFromCoverage]
|
[ExcludeFromCoverage]
|
||||||
public class FilePeersProvider : IPeersProvider
|
public class FilePeersProvider : IPeersProvider
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,10 @@ using Rafty.FiniteStateMachine;
|
|||||||
|
|
||||||
namespace Ocelot.Raft
|
namespace Ocelot.Raft
|
||||||
{
|
{
|
||||||
|
using Rafty.Concensus.Messages;
|
||||||
|
using Rafty.Concensus.Peers;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
|
||||||
[ExcludeFromCoverage]
|
[ExcludeFromCoverage]
|
||||||
public class HttpPeer : IPeer
|
public class HttpPeer : IPeer
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ namespace Ocelot.Raft
|
|||||||
[ExcludeFromCoverage]
|
[ExcludeFromCoverage]
|
||||||
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
||||||
{
|
{
|
||||||
private IFileConfigurationSetter _setter;
|
private readonly IFileConfigurationSetter _setter;
|
||||||
|
|
||||||
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,9 @@ using Rafty.FiniteStateMachine;
|
|||||||
|
|
||||||
namespace Ocelot.Raft
|
namespace Ocelot.Raft
|
||||||
{
|
{
|
||||||
|
using Rafty.Concensus.Messages;
|
||||||
|
using Rafty.Concensus.Node;
|
||||||
|
|
||||||
[ExcludeFromCoverage]
|
[ExcludeFromCoverage]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("raft")]
|
[Route("raft")]
|
||||||
|
@ -1,286 +1,322 @@
|
|||||||
using System.IO;
|
namespace Ocelot.Raft
|
||||||
using Rafty.Log;
|
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using Rafty.Infrastructure;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ocelot.Raft
|
|
||||||
{
|
{
|
||||||
//todo - use async await
|
using System;
|
||||||
[ExcludeFromCoverage]
|
using System.Collections.Generic;
|
||||||
public class SqlLiteLog : ILog
|
using System.IO;
|
||||||
{
|
using System.Threading;
|
||||||
private string _path;
|
using System.Threading.Tasks;
|
||||||
private readonly object _lock = new object();
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
public SqlLiteLog(NodeId nodeId)
|
using Newtonsoft.Json;
|
||||||
{
|
using Rafty.Infrastructure;
|
||||||
_path = $"{nodeId.Id.Replace("/","").Replace(":","")}.db";
|
using Rafty.Log;
|
||||||
if(!File.Exists(_path))
|
|
||||||
{
|
[ExcludeFromCoverage]
|
||||||
lock(_lock)
|
public class SqlLiteLog : ILog
|
||||||
{
|
{
|
||||||
FileStream fs = File.Create(_path);
|
private readonly string _path;
|
||||||
fs.Dispose();
|
private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1);
|
||||||
}
|
private readonly ILogger _logger;
|
||||||
|
private readonly NodeId _nodeId;
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
|
||||||
{
|
public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory)
|
||||||
connection.Open();
|
{
|
||||||
var sql = @"create table logs (
|
_logger = loggerFactory.CreateLogger<SqlLiteLog>();
|
||||||
id integer primary key,
|
_nodeId = nodeId;
|
||||||
data text not null
|
_path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db";
|
||||||
)";
|
_sempaphore.Wait();
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
|
||||||
{
|
if (!File.Exists(_path))
|
||||||
var result = command.ExecuteNonQuery();
|
{
|
||||||
}
|
var fs = File.Create(_path);
|
||||||
}
|
|
||||||
}
|
fs.Dispose();
|
||||||
}
|
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
public Task<int> LastLogIndex()
|
{
|
||||||
{
|
connection.Open();
|
||||||
lock(_lock)
|
|
||||||
{
|
const string sql = @"create table logs (
|
||||||
var result = 1;
|
id integer primary key,
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
data text not null
|
||||||
{
|
)";
|
||||||
connection.Open();
|
|
||||||
var sql = @"select id from logs order by id desc limit 1";
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
{
|
||||||
{
|
var result = command.ExecuteNonQuery();
|
||||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
|
||||||
if(index > result)
|
_logger.LogInformation(result == 0
|
||||||
{
|
? $"id: {_nodeId.Id} create database, result: {result}"
|
||||||
result = index;
|
: $"id: {_nodeId.Id} did not create database., result: {result}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(result);
|
_sempaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public async Task<int> LastLogIndex()
|
||||||
public Task<long> LastLogTerm ()
|
{
|
||||||
{
|
_sempaphore.Wait();
|
||||||
lock(_lock)
|
var result = 1;
|
||||||
{
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
long result = 0;
|
{
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
connection.Open();
|
||||||
{
|
var sql = @"select id from logs order by id desc limit 1";
|
||||||
connection.Open();
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
var sql = @"select data from logs order by id desc limit 1";
|
{
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
var index = Convert.ToInt32(await command.ExecuteScalarAsync());
|
||||||
{
|
if (index > result)
|
||||||
var data = Convert.ToString(command.ExecuteScalar());
|
{
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
result = index;
|
||||||
TypeNameHandling = TypeNameHandling.All
|
}
|
||||||
};
|
}
|
||||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
}
|
||||||
if(log != null && log.Term > result)
|
|
||||||
{
|
_sempaphore.Release();
|
||||||
result = log.Term;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
public async Task<long> LastLogTerm()
|
||||||
|
{
|
||||||
return Task.FromResult(result);
|
_sempaphore.Wait();
|
||||||
}
|
long result = 0;
|
||||||
}
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
public Task<int> Count ()
|
connection.Open();
|
||||||
{
|
var sql = @"select data from logs order by id desc limit 1";
|
||||||
lock(_lock)
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
{
|
{
|
||||||
var result = 0;
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
{
|
{
|
||||||
connection.Open();
|
TypeNameHandling = TypeNameHandling.All
|
||||||
var sql = @"select count(id) from logs";
|
};
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
{
|
if (log != null && log.Term > result)
|
||||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
{
|
||||||
if(index > result)
|
result = log.Term;
|
||||||
{
|
}
|
||||||
result = index;
|
}
|
||||||
}
|
}
|
||||||
}
|
_sempaphore.Release();
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
return Task.FromResult(result);
|
|
||||||
}
|
public async Task<int> Count()
|
||||||
}
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
public Task<int> Apply(LogEntry log)
|
var result = 0;
|
||||||
{
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
lock(_lock)
|
{
|
||||||
{
|
connection.Open();
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
var sql = @"select count(id) from logs";
|
||||||
{
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
connection.Open();
|
{
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
var index = Convert.ToInt32(await command.ExecuteScalarAsync());
|
||||||
TypeNameHandling = TypeNameHandling.All
|
if (index > result)
|
||||||
};
|
{
|
||||||
var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
|
result = index;
|
||||||
|
}
|
||||||
//todo - sql injection dont copy this..
|
}
|
||||||
var sql = $"insert into logs (data) values ('{data}')";
|
}
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
_sempaphore.Release();
|
||||||
{
|
return result;
|
||||||
var result = command.ExecuteNonQuery();
|
}
|
||||||
}
|
|
||||||
|
public async Task<int> Apply(LogEntry log)
|
||||||
sql = "select last_insert_rowid()";
|
{
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
_sempaphore.Wait();
|
||||||
{
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
var result = command.ExecuteScalar();
|
{
|
||||||
return Task.FromResult(Convert.ToInt32(result));
|
connection.Open();
|
||||||
}
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
}
|
{
|
||||||
}
|
TypeNameHandling = TypeNameHandling.All
|
||||||
}
|
};
|
||||||
|
var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
|
||||||
public Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
//todo - sql injection dont copy this..
|
||||||
{
|
var sql = $"insert into logs (data) values ('{data}')";
|
||||||
lock(_lock)
|
_logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}");
|
||||||
{
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
{
|
||||||
{
|
var result = await command.ExecuteNonQueryAsync();
|
||||||
connection.Open();
|
_logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}");
|
||||||
|
}
|
||||||
//todo - sql injection dont copy this..
|
|
||||||
var sql = $"select data from logs where id = {index};";
|
sql = "select last_insert_rowid()";
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
{
|
{
|
||||||
var data = Convert.ToString(command.ExecuteScalar());
|
var result = await command.ExecuteScalarAsync();
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
_logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore");
|
||||||
TypeNameHandling = TypeNameHandling.All
|
_sempaphore.Release();
|
||||||
};
|
_logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite");
|
||||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
return Convert.ToInt32(result);
|
||||||
if(logEntry != null && log != null && logEntry.Term != log.Term)
|
}
|
||||||
{
|
}
|
||||||
//todo - sql injection dont copy this..
|
}
|
||||||
var deleteSql = $"delete from logs where id >= {index};";
|
|
||||||
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||||
{
|
{
|
||||||
var result = deleteCommand.ExecuteNonQuery();
|
_sempaphore.Wait();
|
||||||
}
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
}
|
{
|
||||||
}
|
connection.Open();
|
||||||
}
|
//todo - sql injection dont copy this..
|
||||||
}
|
var sql = $"select data from logs where id = {index};";
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id} sql: {sql}");
|
||||||
return Task.CompletedTask;
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
}
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
public Task<LogEntry> Get(int index)
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
{
|
{
|
||||||
lock(_lock)
|
TypeNameHandling = TypeNameHandling.All
|
||||||
{
|
};
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
|
||||||
{
|
_logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}");
|
||||||
connection.Open();
|
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
//todo - sql injection dont copy this..
|
if (logEntry != null && log != null && logEntry.Term != log.Term)
|
||||||
var sql = $"select data from logs where id = {index}";
|
{
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
//todo - sql injection dont copy this..
|
||||||
{
|
var deleteSql = $"delete from logs where id >= {index};";
|
||||||
var data = Convert.ToString(command.ExecuteScalar());
|
_logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}");
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||||
TypeNameHandling = TypeNameHandling.All
|
{
|
||||||
};
|
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
}
|
||||||
return Task.FromResult(log);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
_sempaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
|
public async Task<bool> IsDuplicate(int index, LogEntry logEntry)
|
||||||
{
|
{
|
||||||
lock(_lock)
|
_sempaphore.Wait();
|
||||||
{
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
var logsToReturn = new List<(int, LogEntry)>();
|
{
|
||||||
|
connection.Open();
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
//todo - sql injection dont copy this..
|
||||||
{
|
var sql = $"select data from logs where id = {index};";
|
||||||
connection.Open();
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
//todo - sql injection dont copy this..
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
var sql = $"select id, data from logs where id >= {index}";
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
{
|
||||||
{
|
TypeNameHandling = TypeNameHandling.All
|
||||||
using(var reader = command.ExecuteReader())
|
};
|
||||||
{
|
|
||||||
while(reader.Read())
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
{
|
|
||||||
var id = Convert.ToInt32(reader[0]);
|
if (logEntry != null && log != null && logEntry.Term == log.Term)
|
||||||
var data = (string)reader[1];
|
{
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
_sempaphore.Release();
|
||||||
TypeNameHandling = TypeNameHandling.All
|
return true;
|
||||||
};
|
}
|
||||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
}
|
||||||
logsToReturn.Add((id, log));
|
}
|
||||||
}
|
|
||||||
}
|
_sempaphore.Release();
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(logsToReturn);
|
public async Task<LogEntry> Get(int index)
|
||||||
}
|
{
|
||||||
}
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
public Task<long> GetTermAtIndex(int index)
|
{
|
||||||
{
|
connection.Open();
|
||||||
lock(_lock)
|
//todo - sql injection dont copy this..
|
||||||
{
|
var sql = $"select data from logs where id = {index}";
|
||||||
long result = 0;
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
{
|
||||||
{
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
connection.Open();
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
//todo - sql injection dont copy this..
|
TypeNameHandling = TypeNameHandling.All
|
||||||
var sql = $"select data from logs where id = {index}";
|
};
|
||||||
using(var command = new SqliteCommand(sql, connection))
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
{
|
_sempaphore.Release();
|
||||||
var data = Convert.ToString(command.ExecuteScalar());
|
return log;
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
}
|
||||||
TypeNameHandling = TypeNameHandling.All
|
}
|
||||||
};
|
}
|
||||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
|
||||||
if(log != null && log.Term > result)
|
public async Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
|
||||||
{
|
{
|
||||||
result = log.Term;
|
_sempaphore.Wait();
|
||||||
}
|
var logsToReturn = new List<(int, LogEntry)>();
|
||||||
}
|
|
||||||
}
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
return Task.FromResult(result);
|
connection.Open();
|
||||||
}
|
//todo - sql injection dont copy this..
|
||||||
}
|
var sql = $"select id, data from logs where id >= {index}";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
public Task Remove(int indexOfCommand)
|
{
|
||||||
{
|
using (var reader = await command.ExecuteReaderAsync())
|
||||||
lock(_lock)
|
{
|
||||||
{
|
while (reader.Read())
|
||||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
{
|
||||||
{
|
var id = Convert.ToInt32(reader[0]);
|
||||||
connection.Open();
|
var data = (string)reader[1];
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
//todo - sql injection dont copy this..
|
{
|
||||||
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
TypeNameHandling = TypeNameHandling.All
|
||||||
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
};
|
||||||
{
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
var result = deleteCommand.ExecuteNonQuery();
|
logsToReturn.Add((id, log));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return Task.CompletedTask;
|
_sempaphore.Release();
|
||||||
}
|
return logsToReturn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetTermAtIndex(int index)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
long result = 0;
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index}";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if (log != null && log.Term > result)
|
||||||
|
{
|
||||||
|
result = log.Term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_sempaphore.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public async Task Remove(int indexOfCommand)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}");
|
||||||
|
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||||
|
{
|
||||||
|
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_sempaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,148 +1,148 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ocelot.RateLimit
|
namespace Ocelot.RateLimit
|
||||||
{
|
{
|
||||||
public class RateLimitCore
|
public class RateLimitCore
|
||||||
{
|
{
|
||||||
private readonly IRateLimitCounterHandler _counterHandler;
|
private readonly IRateLimitCounterHandler _counterHandler;
|
||||||
private static readonly object _processLocker = new object();
|
private static readonly object _processLocker = new object();
|
||||||
|
|
||||||
public RateLimitCore(IRateLimitCounterHandler counterStore)
|
public RateLimitCore(IRateLimitCounterHandler counterStore)
|
||||||
{
|
{
|
||||||
_counterHandler = counterStore;
|
_counterHandler = counterStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
||||||
{
|
{
|
||||||
RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1);
|
RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1);
|
||||||
var rule = option.RateLimitRule;
|
var rule = option.RateLimitRule;
|
||||||
|
|
||||||
var counterId = ComputeCounterKey(requestIdentity, option);
|
var counterId = ComputeCounterKey(requestIdentity, option);
|
||||||
|
|
||||||
// serial reads and writes
|
// serial reads and writes
|
||||||
lock (_processLocker)
|
lock (_processLocker)
|
||||||
{
|
{
|
||||||
var entry = _counterHandler.Get(counterId);
|
var entry = _counterHandler.Get(counterId);
|
||||||
if (entry.HasValue)
|
if (entry.HasValue)
|
||||||
{
|
{
|
||||||
// entry has not expired
|
// entry has not expired
|
||||||
if (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period) >= DateTime.UtcNow)
|
if (entry.Value.Timestamp + TimeSpan.FromSeconds(rule.PeriodTimespan) >= DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
// increment request count
|
// increment request count
|
||||||
var totalRequests = entry.Value.TotalRequests + 1;
|
var totalRequests = entry.Value.TotalRequests + 1;
|
||||||
|
|
||||||
// deep copy
|
// deep copy
|
||||||
counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests);
|
counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (counter.TotalRequests > rule.Limit)
|
if (counter.TotalRequests > rule.Limit)
|
||||||
{
|
{
|
||||||
var retryAfter = RetryAfterFrom(counter.Timestamp, rule);
|
var retryAfter = RetryAfterFrom(counter.Timestamp, rule);
|
||||||
if (retryAfter > 0)
|
if (retryAfter > 0)
|
||||||
{
|
{
|
||||||
var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan);
|
var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan);
|
||||||
_counterHandler.Set(counterId, counter, expirationTime);
|
_counterHandler.Set(counterId, counter, expirationTime);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_counterHandler.Remove(counterId);
|
_counterHandler.Remove(counterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var expirationTime = ConvertToTimeSpan(rule.Period);
|
var expirationTime = ConvertToTimeSpan(rule.Period);
|
||||||
_counterHandler.Set(counterId, counter, expirationTime);
|
_counterHandler.Set(counterId, counter, expirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return counter;
|
return counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime)
|
public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime)
|
||||||
{
|
{
|
||||||
var counterId = ComputeCounterKey(requestIdentity, option);
|
var counterId = ComputeCounterKey(requestIdentity, option);
|
||||||
var rule = option.RateLimitRule;
|
var rule = option.RateLimitRule;
|
||||||
|
|
||||||
// stores: id (string) - timestamp (datetime) - total_requests (long)
|
// stores: id (string) - timestamp (datetime) - total_requests (long)
|
||||||
_counterHandler.Set(counterId, counter, expirationTime);
|
_counterHandler.Set(counterId, counter, expirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
||||||
{
|
{
|
||||||
var rule = option.RateLimitRule;
|
var rule = option.RateLimitRule;
|
||||||
RateLimitHeaders headers = null;
|
RateLimitHeaders headers = null;
|
||||||
var counterId = ComputeCounterKey(requestIdentity, option);
|
var counterId = ComputeCounterKey(requestIdentity, option);
|
||||||
var entry = _counterHandler.Get(counterId);
|
var entry = _counterHandler.Get(counterId);
|
||||||
if (entry.HasValue)
|
if (entry.HasValue)
|
||||||
{
|
{
|
||||||
headers = new RateLimitHeaders(context, rule.Period,
|
headers = new RateLimitHeaders(context, rule.Period,
|
||||||
(rule.Limit - entry.Value.TotalRequests).ToString(),
|
(rule.Limit - entry.Value.TotalRequests).ToString(),
|
||||||
(entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)
|
(entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
headers = new RateLimitHeaders(context,
|
headers = new RateLimitHeaders(context,
|
||||||
rule.Period,
|
rule.Period,
|
||||||
rule.Limit.ToString(),
|
rule.Limit.ToString(),
|
||||||
(DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo));
|
(DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
||||||
{
|
{
|
||||||
var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}";
|
var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}";
|
||||||
|
|
||||||
var idBytes = Encoding.UTF8.GetBytes(key);
|
var idBytes = Encoding.UTF8.GetBytes(key);
|
||||||
|
|
||||||
byte[] hashBytes;
|
byte[] hashBytes;
|
||||||
|
|
||||||
using (var algorithm = SHA1.Create())
|
using (var algorithm = SHA1.Create())
|
||||||
{
|
{
|
||||||
hashBytes = algorithm.ComputeHash(idBytes);
|
hashBytes = algorithm.ComputeHash(idBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
|
return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
|
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
|
||||||
{
|
{
|
||||||
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
|
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
|
||||||
var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
|
var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
|
||||||
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
|
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
|
||||||
return retryAfter;
|
return retryAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan ConvertToTimeSpan(string timeSpan)
|
public TimeSpan ConvertToTimeSpan(string timeSpan)
|
||||||
{
|
{
|
||||||
var l = timeSpan.Length - 1;
|
var l = timeSpan.Length - 1;
|
||||||
var value = timeSpan.Substring(0, l);
|
var value = timeSpan.Substring(0, l);
|
||||||
var type = timeSpan.Substring(l, 1);
|
var type = timeSpan.Substring(l, 1);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case "d":
|
case "d":
|
||||||
return TimeSpan.FromDays(double.Parse(value));
|
return TimeSpan.FromDays(double.Parse(value));
|
||||||
case "h":
|
case "h":
|
||||||
return TimeSpan.FromHours(double.Parse(value));
|
return TimeSpan.FromHours(double.Parse(value));
|
||||||
case "m":
|
case "m":
|
||||||
return TimeSpan.FromMinutes(double.Parse(value));
|
return TimeSpan.FromMinutes(double.Parse(value));
|
||||||
case "s":
|
case "s":
|
||||||
return TimeSpan.FromSeconds(double.Parse(value));
|
return TimeSpan.FromSeconds(double.Parse(value));
|
||||||
default:
|
default:
|
||||||
throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}");
|
throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
src/Ocelot/Request/Creator/DownstreamRequestCreator.cs
Normal file
42
src/Ocelot/Request/Creator/DownstreamRequestCreator.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
namespace Ocelot.Request.Creator
|
||||||
|
{
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
|
||||||
|
public class DownstreamRequestCreator : IDownstreamRequestCreator
|
||||||
|
{
|
||||||
|
private readonly IFrameworkDescription _framework;
|
||||||
|
private const string dotNetFramework = ".NET Framework";
|
||||||
|
|
||||||
|
public DownstreamRequestCreator(IFrameworkDescription framework)
|
||||||
|
{
|
||||||
|
_framework = framework;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownstreamRequest Create(HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* According to https://tools.ietf.org/html/rfc7231
|
||||||
|
* GET,HEAD,DELETE,CONNECT,TRACE
|
||||||
|
* Can have body but server can reject the request.
|
||||||
|
* And MS HttpClient in Full Framework actually rejects it.
|
||||||
|
* see #366 issue
|
||||||
|
**/
|
||||||
|
|
||||||
|
if(_framework.Get().Contains(dotNetFramework))
|
||||||
|
{
|
||||||
|
if (request.Method == HttpMethod.Get ||
|
||||||
|
request.Method == HttpMethod.Head ||
|
||||||
|
request.Method == HttpMethod.Delete ||
|
||||||
|
request.Method == HttpMethod.Trace)
|
||||||
|
{
|
||||||
|
request.Content = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DownstreamRequest(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Ocelot/Request/Creator/IDownstreamRequestCreator.cs
Normal file
10
src/Ocelot/Request/Creator/IDownstreamRequestCreator.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Ocelot.Request.Creator
|
||||||
|
{
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
|
||||||
|
public interface IDownstreamRequestCreator
|
||||||
|
{
|
||||||
|
DownstreamRequest Create(HttpRequestMessage request);
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +1,42 @@
|
|||||||
namespace Ocelot.Request.Middleware
|
namespace Ocelot.Request.Middleware
|
||||||
{
|
{
|
||||||
using System.Net.Http;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
using Ocelot.DownstreamRouteFinder.Middleware;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Request.Creator;
|
||||||
|
|
||||||
public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware
|
public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly Mapper.IRequestMapper _requestMapper;
|
private readonly Mapper.IRequestMapper _requestMapper;
|
||||||
|
private readonly IDownstreamRequestCreator _creator;
|
||||||
public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next,
|
|
||||||
IOcelotLoggerFactory loggerFactory,
|
public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next,
|
||||||
Mapper.IRequestMapper requestMapper)
|
IOcelotLoggerFactory loggerFactory,
|
||||||
:base(loggerFactory.CreateLogger<DownstreamRequestInitialiserMiddleware>())
|
Mapper.IRequestMapper requestMapper,
|
||||||
{
|
IDownstreamRequestCreator creator)
|
||||||
_next = next;
|
:base(loggerFactory.CreateLogger<DownstreamRequestInitialiserMiddleware>())
|
||||||
_requestMapper = requestMapper;
|
{
|
||||||
}
|
_next = next;
|
||||||
|
_requestMapper = requestMapper;
|
||||||
public async Task Invoke(DownstreamContext context)
|
_creator = creator;
|
||||||
{
|
}
|
||||||
var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request);
|
|
||||||
if (downstreamRequest.IsError)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
SetPipelineError(context, downstreamRequest.Errors);
|
var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request);
|
||||||
return;
|
if (downstreamRequest.IsError)
|
||||||
}
|
{
|
||||||
|
SetPipelineError(context, downstreamRequest.Errors);
|
||||||
context.DownstreamRequest = new DownstreamRequest(downstreamRequest.Data);
|
return;
|
||||||
|
}
|
||||||
await _next.Invoke(context);
|
|
||||||
}
|
context.DownstreamRequest = _creator.Create(downstreamRequest.Data);
|
||||||
}
|
|
||||||
}
|
await _next.Invoke(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,16 +43,11 @@ namespace Ocelot.Requester
|
|||||||
return httpClient;
|
return httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpclientHandler = new HttpClientHandler
|
var handler = CreateHandler(context);
|
||||||
{
|
|
||||||
AllowAutoRedirect = context.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect,
|
|
||||||
UseCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer,
|
|
||||||
CookieContainer = new CookieContainer()
|
|
||||||
};
|
|
||||||
|
|
||||||
if(context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator)
|
if (context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator)
|
||||||
{
|
{
|
||||||
httpclientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
|
handler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
|
||||||
|
|
||||||
_logger
|
_logger
|
||||||
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}");
|
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}");
|
||||||
@ -62,7 +57,7 @@ namespace Ocelot.Requester
|
|||||||
? _defaultTimeout
|
? _defaultTimeout
|
||||||
: TimeSpan.FromMilliseconds(context.DownstreamReRoute.QosOptions.TimeoutValue);
|
: TimeSpan.FromMilliseconds(context.DownstreamReRoute.QosOptions.TimeoutValue);
|
||||||
|
|
||||||
_httpClient = new HttpClient(CreateHttpMessageHandler(httpclientHandler, context.DownstreamReRoute))
|
_httpClient = new HttpClient(CreateHttpMessageHandler(handler, context.DownstreamReRoute))
|
||||||
{
|
{
|
||||||
Timeout = timeout
|
Timeout = timeout
|
||||||
};
|
};
|
||||||
@ -72,6 +67,43 @@ namespace Ocelot.Requester
|
|||||||
return _client;
|
return _client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpClientHandler CreateHandler(DownstreamContext context)
|
||||||
|
{
|
||||||
|
// Dont' create the CookieContainer if UseCookies is not set or the HttpClient will complain
|
||||||
|
// under .Net Full Framework
|
||||||
|
bool useCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer;
|
||||||
|
|
||||||
|
if (useCookies)
|
||||||
|
{
|
||||||
|
return UseCookiesHandler(context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UseNonCookiesHandler(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClientHandler UseNonCookiesHandler(DownstreamContext context)
|
||||||
|
{
|
||||||
|
return new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = context.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect,
|
||||||
|
UseCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer,
|
||||||
|
UseProxy = context.DownstreamReRoute.HttpHandlerOptions.UseProxy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClientHandler UseCookiesHandler(DownstreamContext context)
|
||||||
|
{
|
||||||
|
return new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = context.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect,
|
||||||
|
UseCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer,
|
||||||
|
UseProxy = context.DownstreamReRoute.HttpHandlerOptions.UseProxy,
|
||||||
|
CookieContainer = new CookieContainer()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
_cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24));
|
_cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24));
|
||||||
|
@ -59,12 +59,8 @@ namespace Ocelot.Responder
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void SetErrorResponseOnContext(HttpContext context, int statusCode)
|
public void SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||||
{
|
{
|
||||||
context.Response.OnStarting(x =>
|
context.Response.StatusCode = statusCode;
|
||||||
{
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader)
|
private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Pivotal.Discovery.Client;
|
using Steeltoe.Common.Discovery;
|
||||||
using Values;
|
using Values;
|
||||||
|
|
||||||
public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider
|
public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Pivotal.Discovery.Client;
|
using Steeltoe.Common.Discovery;
|
||||||
|
|
||||||
public class FakeEurekaDiscoveryClient : IDiscoveryClient
|
public class FakeEurekaDiscoveryClient : IDiscoveryClient
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Consul;
|
||||||
|
using Ocelot.Infrastructure.Consul;
|
||||||
|
using Ocelot.Infrastructure.Extensions;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.ServiceDiscovery.Configuration;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.ServiceDiscovery.Providers
|
||||||
|
{
|
||||||
|
public class PollingConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
|
||||||
|
private readonly Timer _timer;
|
||||||
|
private bool _polling;
|
||||||
|
private List<Service> _services;
|
||||||
|
private string _keyOfServiceInConsul;
|
||||||
|
|
||||||
|
public PollingConsulServiceDiscoveryProvider(int pollingInterval, string keyOfServiceInConsul, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
|
||||||
|
{;
|
||||||
|
_logger = factory.CreateLogger<PollingConsulServiceDiscoveryProvider>();
|
||||||
|
_keyOfServiceInConsul = keyOfServiceInConsul;
|
||||||
|
_consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
|
||||||
|
_services = new List<Service>();
|
||||||
|
|
||||||
|
_timer = new Timer(async x =>
|
||||||
|
{
|
||||||
|
if(_polling)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_polling = true;
|
||||||
|
await Poll();
|
||||||
|
_polling = false;
|
||||||
|
|
||||||
|
}, null, pollingInterval, pollingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<Service>> Get()
|
||||||
|
{
|
||||||
|
return Task.FromResult(_services);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Poll()
|
||||||
|
{
|
||||||
|
_services = await _consulServiceDiscoveryProvider.Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,62 +1,70 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Infrastructure.Consul;
|
using Ocelot.Infrastructure.Consul;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
using Ocelot.ServiceDiscovery.Configuration;
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.ServiceDiscovery
|
namespace Ocelot.ServiceDiscovery
|
||||||
{
|
{
|
||||||
using Pivotal.Discovery.Client;
|
using Steeltoe.Common.Discovery;
|
||||||
|
|
||||||
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
||||||
{
|
{
|
||||||
private readonly IOcelotLoggerFactory _factory;
|
private readonly IOcelotLoggerFactory _factory;
|
||||||
private readonly IConsulClientFactory _consulFactory;
|
private readonly IConsulClientFactory _consulFactory;
|
||||||
private readonly IDiscoveryClient _eurekaClient;
|
private readonly IDiscoveryClient _eurekaClient;
|
||||||
|
|
||||||
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient)
|
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_consulFactory = consulFactory;
|
_consulFactory = consulFactory;
|
||||||
_eurekaClient = eurekaClient;
|
_eurekaClient = eurekaClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
||||||
{
|
{
|
||||||
if (reRoute.UseServiceDiscovery)
|
if (reRoute.UseServiceDiscovery)
|
||||||
{
|
{
|
||||||
return GetServiceDiscoveryProvider(serviceConfig, reRoute.ServiceName);
|
return GetServiceDiscoveryProvider(serviceConfig, reRoute.ServiceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var services = new List<Service>();
|
var services = new List<Service>();
|
||||||
|
|
||||||
foreach (var downstreamAddress in reRoute.DownstreamAddresses)
|
foreach (var downstreamAddress in reRoute.DownstreamAddresses)
|
||||||
{
|
{
|
||||||
var service = new Service(reRoute.ServiceName, new ServiceHostAndPort(downstreamAddress.Host, downstreamAddress.Port), string.Empty, string.Empty, new string[0]);
|
var service = new Service(reRoute.ServiceName, new ServiceHostAndPort(downstreamAddress.Host, downstreamAddress.Port), string.Empty, string.Empty, new string[0]);
|
||||||
|
|
||||||
services.Add(service);
|
services.Add(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ConfigurationServiceProvider(services);
|
return new ConfigurationServiceProvider(services);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName)
|
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName)
|
||||||
{
|
{
|
||||||
if (serviceConfig.Type?.ToLower() == "servicefabric")
|
if (serviceConfig.Type?.ToLower() == "servicefabric")
|
||||||
{
|
{
|
||||||
var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName);
|
var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName);
|
||||||
return new ServiceFabricServiceDiscoveryProvider(config);
|
return new ServiceFabricServiceDiscoveryProvider(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceConfig.Type?.ToLower() == "eureka")
|
if (serviceConfig.Type?.ToLower() == "eureka")
|
||||||
{
|
{
|
||||||
return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient);
|
return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token);
|
var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token);
|
||||||
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
|
|
||||||
}
|
var consulServiceDiscoveryProvider = new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
|
||||||
}
|
|
||||||
}
|
if (serviceConfig.Type?.ToLower() == "pollconsul")
|
||||||
|
{
|
||||||
|
return new PollingConsulServiceDiscoveryProvider(serviceConfig.PollingInterval, consulRegistryConfiguration.KeyOfServiceInConsul, _factory, consulServiceDiscoveryProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consulServiceDiscoveryProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,191 +1,239 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class CachingTests : IDisposable
|
public class CachingTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
|
|
||||||
public CachingTests()
|
public CachingTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_cached_response()
|
public void should_return_cached_response()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51899,
|
Port = 51899,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
FileCacheOptions = new FileCacheOptions
|
FileCacheOptions = new FileCacheOptions
|
||||||
{
|
{
|
||||||
TtlSeconds = 100
|
TtlSeconds = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.And(x => _steps.ThenTheContentLengthIs(16))
|
.And(x => _steps.ThenTheContentLengthIs(16))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_cached_response_when_using_jsonserialized_cache()
|
public void should_return_cached_response_with_expires_header()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51899,
|
Port = 52839,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
FileCacheOptions = new FileCacheOptions
|
FileCacheOptions = new FileCacheOptions
|
||||||
{
|
{
|
||||||
TtlSeconds = 100
|
TtlSeconds = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52839", 200, "Hello from Laura", "Expires", "-1"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:52839", 200, "Hello from Tom"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.BDDfy();
|
.And(x => _steps.ThenTheContentLengthIs(16))
|
||||||
}
|
.And(x => _steps.ThenTheResponseBodyHeaderIs("Expires", "-1"))
|
||||||
|
.BDDfy();
|
||||||
[Fact]
|
}
|
||||||
public void should_not_return_cached_response_as_ttl_expires()
|
|
||||||
{
|
[Fact]
|
||||||
var configuration = new FileConfiguration
|
public void should_return_cached_response_when_using_jsonserialized_cache()
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
new FileReRoute
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
new FileReRoute
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
{
|
||||||
{
|
DownstreamPathTemplate = "/",
|
||||||
new FileHostAndPort
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
new FileHostAndPort
|
||||||
Port = 51899,
|
{
|
||||||
}
|
Host = "localhost",
|
||||||
},
|
Port = 51899,
|
||||||
DownstreamScheme = "http",
|
}
|
||||||
UpstreamPathTemplate = "/",
|
},
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
DownstreamScheme = "http",
|
||||||
FileCacheOptions = new FileCacheOptions
|
UpstreamPathTemplate = "/",
|
||||||
{
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
TtlSeconds = 1
|
FileCacheOptions = new FileCacheOptions
|
||||||
}
|
{
|
||||||
}
|
TtlSeconds = 100
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura"))
|
};
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => x.GivenTheCacheExpires())
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.BDDfy();
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
}
|
.BDDfy();
|
||||||
|
}
|
||||||
private void GivenTheCacheExpires()
|
|
||||||
{
|
[Fact]
|
||||||
Thread.Sleep(1000);
|
public void should_not_return_cached_response_as_ttl_expires()
|
||||||
}
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody)
|
{
|
||||||
{
|
ReRoutes = new List<FileReRoute>
|
||||||
_builder.Dispose();
|
{
|
||||||
GivenThereIsAServiceRunningOn(url, statusCode, responseBody);
|
new FileReRoute
|
||||||
}
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
_builder = new WebHostBuilder()
|
new FileHostAndPort
|
||||||
.UseUrls(url)
|
{
|
||||||
.UseKestrel()
|
Host = "localhost",
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
Port = 51899,
|
||||||
.UseIISIntegration()
|
}
|
||||||
.UseUrls(url)
|
},
|
||||||
.Configure(app =>
|
DownstreamScheme = "http",
|
||||||
{
|
UpstreamPathTemplate = "/",
|
||||||
app.Run(async context =>
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
{
|
FileCacheOptions = new FileCacheOptions
|
||||||
context.Response.StatusCode = statusCode;
|
{
|
||||||
await context.Response.WriteAsync(responseBody);
|
TtlSeconds = 1
|
||||||
});
|
}
|
||||||
})
|
}
|
||||||
.Build();
|
}
|
||||||
|
};
|
||||||
_builder.Start();
|
|
||||||
}
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
public void Dispose()
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
{
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
_builder?.Dispose();
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
_steps.Dispose();
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
}
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
||||||
}
|
.And(x => x.GivenTheCacheExpires())
|
||||||
}
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheCacheExpires()
|
||||||
|
{
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody)
|
||||||
|
{
|
||||||
|
_builder.Dispose();
|
||||||
|
GivenThereIsAServiceRunningOn(url, statusCode, responseBody, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, string key, string value)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if(!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(key))
|
||||||
|
{
|
||||||
|
context.Response.Headers.Add(key, value);
|
||||||
|
}
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(responseBody);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builder?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,171 +1,236 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class ClientRateLimitTests : IDisposable
|
public class ClientRateLimitTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private int _counterOne;
|
private int _counterOne;
|
||||||
|
|
||||||
public ClientRateLimitTests()
|
public ClientRateLimitTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_builder?.Dispose();
|
_builder?.Dispose();
|
||||||
_steps.Dispose();
|
_steps.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_withratelimiting()
|
public void should_call_withratelimiting()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/api/ClientRateLimit",
|
DownstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51876,
|
Port = 51876,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/api/ClientRateLimit",
|
UpstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
RequestIdKey = _steps.RequestIdKey,
|
RequestIdKey = _steps.RequestIdKey,
|
||||||
|
|
||||||
RateLimitOptions = new FileRateLimitRule()
|
RateLimitOptions = new FileRateLimitRule()
|
||||||
{
|
{
|
||||||
EnableRateLimiting = true,
|
EnableRateLimiting = true,
|
||||||
ClientWhitelist = new List<string>(),
|
ClientWhitelist = new List<string>(),
|
||||||
Limit = 3,
|
Limit = 3,
|
||||||
Period = "1s",
|
Period = "1s",
|
||||||
PeriodTimespan = 1000
|
PeriodTimespan = 1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
RateLimitOptions = new FileRateLimitOptions()
|
RateLimitOptions = new FileRateLimitOptions()
|
||||||
{
|
{
|
||||||
ClientIdHeader = "ClientId",
|
ClientIdHeader = "ClientId",
|
||||||
DisableRateLimitHeaders = false,
|
DisableRateLimitHeaders = false,
|
||||||
QuotaExceededMessage = "",
|
QuotaExceededMessage = "",
|
||||||
RateLimitCounterPrefix = "",
|
RateLimitCounterPrefix = "",
|
||||||
HttpStatusCode = 428
|
HttpStatusCode = 428
|
||||||
},
|
},
|
||||||
RequestIdKey ="oceclientrequest"
|
RequestIdKey ="oceclientrequest"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_middleware_withWhitelistClient()
|
public void should_wait_for_period_timespan_to_elapse_before_making_next_request()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/api/ClientRateLimit",
|
DownstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51876,
|
Port = 51926,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/api/ClientRateLimit",
|
UpstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
RequestIdKey = _steps.RequestIdKey,
|
RequestIdKey = _steps.RequestIdKey,
|
||||||
|
|
||||||
RateLimitOptions = new FileRateLimitRule()
|
RateLimitOptions = new FileRateLimitRule()
|
||||||
{
|
{
|
||||||
EnableRateLimiting = true,
|
EnableRateLimiting = true,
|
||||||
ClientWhitelist = new List<string>() { "ocelotclient1"},
|
ClientWhitelist = new List<string>(),
|
||||||
Limit = 3,
|
Limit = 3,
|
||||||
Period = "1s",
|
Period = "1s",
|
||||||
PeriodTimespan = 100
|
PeriodTimespan = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
RateLimitOptions = new FileRateLimitOptions()
|
RateLimitOptions = new FileRateLimitOptions()
|
||||||
{
|
{
|
||||||
ClientIdHeader = "ClientId",
|
ClientIdHeader = "ClientId",
|
||||||
DisableRateLimitHeaders = false,
|
DisableRateLimitHeaders = false,
|
||||||
QuotaExceededMessage = "",
|
QuotaExceededMessage = "",
|
||||||
RateLimitCounterPrefix = ""
|
RateLimitCounterPrefix = "",
|
||||||
},
|
HttpStatusCode = 428
|
||||||
RequestIdKey = "oceclientrequest"
|
},
|
||||||
}
|
RequestIdKey ="oceclientrequest"
|
||||||
};
|
}
|
||||||
|
};
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51926", "/api/ClientRateLimit"))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4))
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.BDDfy();
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
}
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath)
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
{
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
||||||
_builder = new WebHostBuilder()
|
.And(x => _steps.GivenIWait(1000))
|
||||||
.UseUrls(baseUrl)
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.UseKestrel()
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.And(x => _steps.GivenIWait(1000))
|
||||||
.UseIISIntegration()
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.UseUrls(baseUrl)
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
.Configure(app =>
|
.BDDfy();
|
||||||
{
|
}
|
||||||
app.UsePathBase(basePath);
|
|
||||||
app.Run(context =>
|
[Fact]
|
||||||
{
|
public void should_call_middleware_withWhitelistClient()
|
||||||
_counterOne++;
|
{
|
||||||
context.Response.StatusCode = 200;
|
var configuration = new FileConfiguration
|
||||||
context.Response.WriteAsync(_counterOne.ToString());
|
{
|
||||||
return Task.CompletedTask;
|
ReRoutes = new List<FileReRoute>
|
||||||
});
|
{
|
||||||
})
|
new FileReRoute
|
||||||
.Build();
|
{
|
||||||
|
DownstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
_builder.Start();
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
}
|
{
|
||||||
}
|
new FileHostAndPort
|
||||||
}
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 51876,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
RequestIdKey = _steps.RequestIdKey,
|
||||||
|
|
||||||
|
RateLimitOptions = new FileRateLimitRule()
|
||||||
|
{
|
||||||
|
EnableRateLimiting = true,
|
||||||
|
ClientWhitelist = new List<string>() { "ocelotclient1"},
|
||||||
|
Limit = 3,
|
||||||
|
Period = "1s",
|
||||||
|
PeriodTimespan = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
RateLimitOptions = new FileRateLimitOptions()
|
||||||
|
{
|
||||||
|
ClientIdHeader = "ClientId",
|
||||||
|
DisableRateLimitHeaders = false,
|
||||||
|
QuotaExceededMessage = "",
|
||||||
|
RateLimitCounterPrefix = ""
|
||||||
|
},
|
||||||
|
RequestIdKey = "oceclientrequest"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
_counterOne++;
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
context.Response.WriteAsync(_counterOne.ToString());
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,365 +1,412 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class HeaderTests : IDisposable
|
public class HeaderTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
private int _count;
|
private int _count;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
|
|
||||||
public HeaderTests()
|
public HeaderTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_transform_upstream_header()
|
public void should_transform_upstream_header()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51871,
|
Port = 51871,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
UpstreamHeaderTransform = new Dictionary<string,string>
|
UpstreamHeaderTransform = new Dictionary<string,string>
|
||||||
{
|
{
|
||||||
{"Laz", "D, GP"}
|
{"Laz", "D, GP"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Laz"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Laz"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.And(x => _steps.GivenIAddAHeader("Laz", "D"))
|
.And(x => _steps.GivenIAddAHeader("Laz", "D"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("GP"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("GP"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_transform_downstream_header()
|
public void should_transform_downstream_header()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51871,
|
Port = 51871,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
DownstreamHeaderTransform = new Dictionary<string,string>
|
DownstreamHeaderTransform = new Dictionary<string,string>
|
||||||
{
|
{
|
||||||
{"Location", "http://www.bbc.co.uk/, http://ocelot.com/"}
|
{"Location", "http://www.bbc.co.uk/, http://ocelot.com/"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Location", "http://www.bbc.co.uk/"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Location", "http://www.bbc.co.uk/"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/"))
|
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_fix_issue_190()
|
public void should_fix_issue_190()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 6773,
|
Port = 6773,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
DownstreamHeaderTransform = new Dictionary<string,string>
|
DownstreamHeaderTransform = new Dictionary<string,string>
|
||||||
{
|
{
|
||||||
{"Location", "http://localhost:6773, {BaseUrl}"}
|
{"Location", "http://localhost:6773, {BaseUrl}"}
|
||||||
},
|
},
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
|
||||||
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
|
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_fix_issue_205()
|
public void should_fix_issue_205()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 6773,
|
Port = 6773,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
DownstreamHeaderTransform = new Dictionary<string,string>
|
DownstreamHeaderTransform = new Dictionary<string,string>
|
||||||
{
|
{
|
||||||
{"Location", "{DownstreamBaseUrl}, {BaseUrl}"}
|
{"Location", "{DownstreamBaseUrl}, {BaseUrl}"}
|
||||||
},
|
},
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
|
||||||
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
|
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void request_should_reuse_cookies_with_cookie_container()
|
public void should_fix_issue_417()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/sso/{everything}",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 6774,
|
Port = 6773,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/sso/{everything}",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
DownstreamHeaderTransform = new Dictionary<string,string>
|
||||||
{
|
{
|
||||||
UseCookieContainer = true
|
{"Location", "{DownstreamBaseUrl}, {BaseUrl}"}
|
||||||
}
|
},
|
||||||
}
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
}
|
{
|
||||||
};
|
AllowAutoRedirect = false
|
||||||
|
}
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200))
|
}
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
},
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
{
|
||||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
BaseUrl = "http://anotherapp.azurewebsites.net"
|
||||||
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
}
|
||||||
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
};
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
|
||||||
.BDDfy();
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
}
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
[Fact]
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
|
||||||
public void request_should_have_own_cookies_no_cookie_container()
|
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://anotherapp.azurewebsites.net/pay/Receive"))
|
||||||
{
|
.BDDfy();
|
||||||
var configuration = new FileConfiguration
|
}
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
[Fact]
|
||||||
{
|
public void request_should_reuse_cookies_with_cookie_container()
|
||||||
new FileReRoute
|
{
|
||||||
{
|
var configuration = new FileConfiguration
|
||||||
DownstreamPathTemplate = "/sso/{everything}",
|
{
|
||||||
DownstreamScheme = "http",
|
ReRoutes = new List<FileReRoute>
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
{
|
||||||
{
|
new FileReRoute
|
||||||
new FileHostAndPort
|
{
|
||||||
{
|
DownstreamPathTemplate = "/sso/{everything}",
|
||||||
Host = "localhost",
|
DownstreamScheme = "http",
|
||||||
Port = 6775,
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
}
|
{
|
||||||
},
|
new FileHostAndPort
|
||||||
UpstreamPathTemplate = "/sso/{everything}",
|
{
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
Host = "localhost",
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
Port = 6774,
|
||||||
{
|
}
|
||||||
UseCookieContainer = false
|
},
|
||||||
}
|
UpstreamPathTemplate = "/sso/{everything}",
|
||||||
}
|
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
||||||
}
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
};
|
{
|
||||||
|
UseCookieContainer = true
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200))
|
}
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
}
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
}
|
||||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
};
|
||||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200))
|
||||||
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
.BDDfy();
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
}
|
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
||||||
|
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode)
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
{
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
_builder = new WebHostBuilder()
|
.BDDfy();
|
||||||
.UseUrls(baseUrl)
|
}
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
[Fact]
|
||||||
.UseIISIntegration()
|
public void request_should_have_own_cookies_no_cookie_container()
|
||||||
.Configure(app =>
|
{
|
||||||
{
|
var configuration = new FileConfiguration
|
||||||
app.UsePathBase(basePath);
|
{
|
||||||
app.Run(context =>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
if (_count == 0)
|
new FileReRoute
|
||||||
{
|
{
|
||||||
context.Response.Cookies.Append("test", "0");
|
DownstreamPathTemplate = "/sso/{everything}",
|
||||||
_count++;
|
DownstreamScheme = "http",
|
||||||
context.Response.StatusCode = statusCode;
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
return Task.CompletedTask;
|
{
|
||||||
}
|
new FileHostAndPort
|
||||||
|
{
|
||||||
if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
|
Host = "localhost",
|
||||||
{
|
Port = 6775,
|
||||||
if (cookieValue == "0" || headerValue == "test=1; path=/")
|
}
|
||||||
{
|
},
|
||||||
context.Response.StatusCode = statusCode;
|
UpstreamPathTemplate = "/sso/{everything}",
|
||||||
return Task.CompletedTask;
|
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
||||||
}
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
}
|
{
|
||||||
|
UseCookieContainer = false
|
||||||
context.Response.StatusCode = 500;
|
}
|
||||||
return Task.CompletedTask;
|
}
|
||||||
});
|
}
|
||||||
})
|
};
|
||||||
.Build();
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200))
|
||||||
_builder.Start();
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
}
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
{
|
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
||||||
_builder = new WebHostBuilder()
|
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
||||||
.UseUrls(baseUrl)
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
.UseKestrel()
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.BDDfy();
|
||||||
.UseIISIntegration()
|
}
|
||||||
.Configure(app =>
|
|
||||||
{
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode)
|
||||||
app.UsePathBase(basePath);
|
{
|
||||||
app.Run(async context =>
|
_builder = new WebHostBuilder()
|
||||||
{
|
.UseUrls(baseUrl)
|
||||||
if(context.Request.Headers.TryGetValue(headerKey, out var values))
|
.UseKestrel()
|
||||||
{
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
var result = values.First();
|
.UseIISIntegration()
|
||||||
context.Response.StatusCode = statusCode;
|
.Configure(app =>
|
||||||
await context.Response.WriteAsync(result);
|
{
|
||||||
}
|
app.UsePathBase(basePath);
|
||||||
});
|
app.Run(context =>
|
||||||
})
|
{
|
||||||
.Build();
|
if (_count == 0)
|
||||||
|
{
|
||||||
_builder.Start();
|
context.Response.Cookies.Append("test", "0");
|
||||||
}
|
_count++;
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue)
|
return Task.CompletedTask;
|
||||||
{
|
}
|
||||||
_builder = new WebHostBuilder()
|
|
||||||
.UseUrls(baseUrl)
|
if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
|
||||||
.UseKestrel()
|
{
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
if (cookieValue == "0" || headerValue == "test=1; path=/")
|
||||||
.UseIISIntegration()
|
{
|
||||||
.Configure(app =>
|
context.Response.StatusCode = statusCode;
|
||||||
{
|
return Task.CompletedTask;
|
||||||
app.UsePathBase(basePath);
|
}
|
||||||
app.Run(context =>
|
}
|
||||||
{
|
|
||||||
context.Response.OnStarting(() => {
|
context.Response.StatusCode = 500;
|
||||||
context.Response.Headers.Add(headerKey, headerValue);
|
return Task.CompletedTask;
|
||||||
context.Response.StatusCode = statusCode;
|
});
|
||||||
return Task.CompletedTask;
|
})
|
||||||
});
|
.Build();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
_builder.Start();
|
||||||
});
|
}
|
||||||
})
|
|
||||||
.Build();
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
||||||
|
{
|
||||||
_builder.Start();
|
_builder = new WebHostBuilder()
|
||||||
}
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
public void Dispose()
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
{
|
.UseIISIntegration()
|
||||||
_builder?.Dispose();
|
.Configure(app =>
|
||||||
_steps.Dispose();
|
{
|
||||||
}
|
app.UsePathBase(basePath);
|
||||||
}
|
app.Run(async context =>
|
||||||
}
|
{
|
||||||
|
if (context.Request.Headers.TryGetValue(headerKey, out var values))
|
||||||
|
{
|
||||||
|
var result = values.First();
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
context.Response.OnStarting(() =>
|
||||||
|
{
|
||||||
|
context.Response.Headers.Add(headerKey, headerValue);
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builder?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -26,7 +27,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_load_balance_request()
|
public void should_load_balance_request_with_least_connection()
|
||||||
{
|
{
|
||||||
var downstreamServiceOneUrl = "http://localhost:50881";
|
var downstreamServiceOneUrl = "http://localhost:50881";
|
||||||
var downstreamServiceTwoUrl = "http://localhost:50892";
|
var downstreamServiceTwoUrl = "http://localhost:50892";
|
||||||
@ -41,7 +42,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = nameof(LeastConnection) },
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
@ -72,6 +73,55 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_load_balance_request_with_round_robin()
|
||||||
|
{
|
||||||
|
var downstreamPortOne = 51881;
|
||||||
|
var downstreamPortTwo = 51892;
|
||||||
|
var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}";
|
||||||
|
var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}";
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = nameof(RoundRobin) },
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||||
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
||||||
|
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
||||||
|
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
|
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
|
||||||
{
|
{
|
||||||
_counterOne.ShouldBeInRange(bottom, top);
|
_counterOne.ShouldBeInRange(bottom, top);
|
||||||
|
@ -1,60 +1,60 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
<AssemblyName>Ocelot.AcceptanceTests</AssemblyName>
|
<AssemblyName>Ocelot.AcceptanceTests</AssemblyName>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<PackageId>Ocelot.AcceptanceTests</PackageId>
|
<PackageId>Ocelot.AcceptanceTests</PackageId>
|
||||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||||
<RuntimeIdentifiers>osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64</RuntimeIdentifiers>
|
||||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="appsettings.product.json">
|
<None Update="appsettings.product.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="appsettings.json">
|
<None Update="appsettings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="idsrv3test.pfx">
|
<None Update="idsrv3test.pfx">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
||||||
<ProjectReference Include="..\Ocelot.ManualTest\Ocelot.ManualTest.csproj" />
|
<ProjectReference Include="..\Ocelot.ManualTest\Ocelot.ManualTest.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CacheManager.Serialization.Json" Version="1.1.2" />
|
<PackageReference Include="CacheManager.Serialization.Json" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||||
<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="xunit.runner.visualstudio" Version="2.3.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
<PackageReference Include="Consul" Version="0.7.2.4" />
|
||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="xunit" Version="2.3.1" />
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
<PackageReference Include="Rafty" Version="0.4.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -14,7 +14,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Pivotal.Discovery.Client;
|
using Steeltoe.Common.Discovery;
|
||||||
|
|
||||||
public class ServiceDiscoveryTests : IDisposable
|
public class ServiceDiscoveryTests : IDisposable
|
||||||
{
|
{
|
||||||
@ -458,6 +458,64 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_handle_request_to_poll_consul_for_downstream_service_and_make_request()
|
||||||
|
{
|
||||||
|
const int consulPort = 8518;
|
||||||
|
const string serviceName = "web";
|
||||||
|
const int downstreamServicePort = 8082;
|
||||||
|
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
|
||||||
|
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
||||||
|
var serviceEntryOne = new ServiceEntry()
|
||||||
|
{
|
||||||
|
Service = new AgentService()
|
||||||
|
{
|
||||||
|
Service = serviceName,
|
||||||
|
Address = "localhost",
|
||||||
|
Port = downstreamServicePort,
|
||||||
|
ID = $"web_90_0_2_224_{downstreamServicePort}",
|
||||||
|
Tags = new[] {"version-v1"}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/home",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/home",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
||||||
|
ServiceName = serviceName,
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
|
UseServiceDiscovery = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = consulPort,
|
||||||
|
Type = "PollConsul",
|
||||||
|
PollingInterval = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
|
||||||
|
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
||||||
|
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk("/home"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo)
|
private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo)
|
||||||
{
|
{
|
||||||
_consulServices.Add(serviceEntryTwo);
|
_consulServices.Add(serviceEntryTwo);
|
||||||
|
@ -27,6 +27,7 @@ using System.Text;
|
|||||||
using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests;
|
using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests;
|
||||||
using Ocelot.Requester;
|
using Ocelot.Requester;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
@ -182,6 +183,11 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void GivenIWait(int wait)
|
||||||
|
{
|
||||||
|
Thread.Sleep(wait);
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
|
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
|
||||||
{
|
{
|
||||||
_webHostBuilder = new WebHostBuilder();
|
_webHostBuilder = new WebHostBuilder();
|
||||||
@ -386,6 +392,12 @@ namespace Ocelot.AcceptanceTests
|
|||||||
header.First().ShouldBe(value);
|
header.First().ShouldBe(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ThenTheResponseBodyHeaderIs(string key, string value)
|
||||||
|
{
|
||||||
|
var header = _response.Content.Headers.GetValues(key);
|
||||||
|
header.First().ShouldBe(value);
|
||||||
|
}
|
||||||
|
|
||||||
public void ThenTheTraceHeaderIsSet(string key)
|
public void ThenTheTraceHeaderIsSet(string key)
|
||||||
{
|
{
|
||||||
var header = _response.Headers.GetValues(key);
|
var header = _response.Headers.GetValues(key);
|
||||||
@ -675,6 +687,24 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url)
|
||||||
|
{
|
||||||
|
var result = WaitFor(2000).Until(() => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
|
_response.EnsureSuccessStatusCode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch(Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
|
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
|
||||||
{
|
{
|
||||||
var request = _ocelotServer.CreateRequest(url);
|
var request = _ocelotServer.CreateRequest(url);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +1,50 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
<AssemblyName>Ocelot.IntegrationTests</AssemblyName>
|
<AssemblyName>Ocelot.IntegrationTests</AssemblyName>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<PackageId>Ocelot.IntegrationTests</PackageId>
|
<PackageId>Ocelot.IntegrationTests</PackageId>
|
||||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||||
<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>
|
||||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="peers.json;appsettings.json;idsrv3test.pfx">
|
<None Update="peers.json;appsettings.json;idsrv3test.pfx">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||||
<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="xunit.runner.visualstudio" Version="2.3.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="xunit" Version="2.3.1" />
|
||||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
<PackageReference Include="Consul" Version="0.7.2.4" />
|
||||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
<PackageReference Include="Rafty" Version="0.4.4" />
|
||||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
|
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -23,6 +23,7 @@ using Ocelot.Middleware;
|
|||||||
|
|
||||||
namespace Ocelot.IntegrationTests
|
namespace Ocelot.IntegrationTests
|
||||||
{
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
public class RaftTests : IDisposable
|
public class RaftTests : IDisposable
|
||||||
@ -31,7 +32,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
private readonly List<IWebHostBuilder> _webHostBuilders;
|
private readonly List<IWebHostBuilder> _webHostBuilders;
|
||||||
private readonly List<Thread> _threads;
|
private readonly List<Thread> _threads;
|
||||||
private FilePeers _peers;
|
private FilePeers _peers;
|
||||||
private readonly HttpClient _httpClient;
|
private HttpClient _httpClient;
|
||||||
private readonly HttpClient _httpClientForAssertions;
|
private readonly HttpClient _httpClientForAssertions;
|
||||||
private BearerToken _token;
|
private BearerToken _token;
|
||||||
private HttpResponseMessage _response;
|
private HttpResponseMessage _response;
|
||||||
@ -42,18 +43,28 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
_output = output;
|
_output = output;
|
||||||
_httpClientForAssertions = new HttpClient();
|
_httpClientForAssertions = new HttpClient();
|
||||||
_httpClient = new HttpClient();
|
|
||||||
var ocelotBaseUrl = "http://localhost:5000";
|
|
||||||
_httpClient.BaseAddress = new Uri(ocelotBaseUrl);
|
|
||||||
_webHostBuilders = new List<IWebHostBuilder>();
|
_webHostBuilders = new List<IWebHostBuilder>();
|
||||||
_builders = new List<IWebHost>();
|
_builders = new List<IWebHost>();
|
||||||
_threads = new List<Thread>();
|
_threads = new List<Thread>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "still broken waiting for work in rafty")]
|
[Fact(Skip = "Still not stable, more work required in rafty..")]
|
||||||
public void should_persist_command_to_five_servers()
|
public async Task should_persist_command_to_five_servers()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var peers = new List<FilePeer>
|
||||||
|
{
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5000"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5001"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5002"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5003"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5004"}
|
||||||
|
};
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
@ -101,20 +112,34 @@ namespace Ocelot.IntegrationTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
var command = new UpdateFileConfiguration(updatedConfiguration);
|
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||||
|
GivenThePeersAre(peers);
|
||||||
GivenThereIsAConfiguration(configuration);
|
GivenThereIsAConfiguration(configuration);
|
||||||
GivenFiveServersAreRunning();
|
GivenFiveServersAreRunning();
|
||||||
GivenIHaveAnOcelotToken("/administration");
|
await GivenIHaveAnOcelotToken("/administration");
|
||||||
WhenISendACommandIntoTheCluster(command);
|
await WhenISendACommandIntoTheCluster(command);
|
||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
await ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "still broken waiting for work in rafty")]
|
[Fact(Skip = "Still not stable, more work required in rafty..")]
|
||||||
public void should_persist_command_to_five_servers_when_using_administration_api()
|
public async Task should_persist_command_to_five_servers_when_using_administration_api()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var peers = new List<FilePeer>
|
||||||
{
|
{
|
||||||
};
|
new FilePeer {HostAndPort = "http://localhost:5005"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5006"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5007"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5008"},
|
||||||
|
|
||||||
|
new FilePeer {HostAndPort = "http://localhost:5009"}
|
||||||
|
};
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
var updatedConfiguration = new FileConfiguration
|
var updatedConfiguration = new FileConfiguration
|
||||||
{
|
{
|
||||||
@ -154,17 +179,29 @@ namespace Ocelot.IntegrationTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
var command = new UpdateFileConfiguration(updatedConfiguration);
|
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||||
|
GivenThePeersAre(peers);
|
||||||
GivenThereIsAConfiguration(configuration);
|
GivenThereIsAConfiguration(configuration);
|
||||||
GivenFiveServersAreRunning();
|
GivenFiveServersAreRunning();
|
||||||
GivenIHaveAnOcelotToken("/administration");
|
await GivenIHaveAnOcelotToken("/administration");
|
||||||
GivenIHaveAddedATokenToMyRequest();
|
GivenIHaveAddedATokenToMyRequest();
|
||||||
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
|
await WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
|
||||||
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
await ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
|
private void GivenThePeersAre(List<FilePeer> peers)
|
||||||
{
|
{
|
||||||
bool SendCommand()
|
FilePeers filePeers = new FilePeers();
|
||||||
|
filePeers.Peers.AddRange(peers);
|
||||||
|
var json = JsonConvert.SerializeObject(filePeers);
|
||||||
|
File.WriteAllText("peers.json", json);
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
var ocelotBaseUrl = peers[0].HostAndPort;
|
||||||
|
_httpClient.BaseAddress = new Uri(ocelotBaseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
|
||||||
|
{
|
||||||
|
async Task<bool> SendCommand()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -174,13 +211,13 @@ namespace Ocelot.IntegrationTests
|
|||||||
TypeNameHandling = TypeNameHandling.All
|
TypeNameHandling = TypeNameHandling.All
|
||||||
});
|
});
|
||||||
var httpContent = new StringContent(json);
|
var httpContent = new StringContent(json);
|
||||||
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||||
using (var httpClient = new HttpClient())
|
using (var httpClient = new HttpClient())
|
||||||
{
|
{
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
|
var response = await httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
var errorResult = JsonConvert.DeserializeObject<ErrorResponse<UpdateFileConfiguration>>(content);
|
var errorResult = JsonConvert.DeserializeObject<ErrorResponse<UpdateFileConfiguration>>(content);
|
||||||
|
|
||||||
@ -206,13 +243,19 @@ namespace Ocelot.IntegrationTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandSent = WaitFor(20000).Until(() => SendCommand());
|
var commandSent = await WaitFor(40000).Until(async () =>
|
||||||
|
{
|
||||||
|
var result = await SendCommand();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
commandSent.ShouldBeTrue();
|
commandSent.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
|
private async Task ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
|
||||||
{
|
{
|
||||||
bool CommandCalledOnAllStateMachines()
|
async Task<bool> CommandCalledOnAllStateMachines()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -232,8 +275,8 @@ namespace Ocelot.IntegrationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result;
|
var result = await _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration");
|
||||||
var json = result.Content.ReadAsStringAsync().Result;
|
var json = await result.Content.ReadAsStringAsync();
|
||||||
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
|
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
|
||||||
response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey);
|
response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey);
|
||||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||||
@ -268,19 +311,29 @@ namespace Ocelot.IntegrationTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
|
var commandOnAllStateMachines = await WaitFor(40000).Until(async () =>
|
||||||
|
{
|
||||||
|
var result = await CommandCalledOnAllStateMachines();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
commandOnAllStateMachines.ShouldBeTrue();
|
commandOnAllStateMachines.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
|
private async Task WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
|
||||||
{
|
{
|
||||||
bool SendCommand()
|
async Task<bool> SendCommand()
|
||||||
{
|
{
|
||||||
var json = JsonConvert.SerializeObject(updatedConfiguration);
|
var json = JsonConvert.SerializeObject(updatedConfiguration);
|
||||||
|
|
||||||
var content = new StringContent(json);
|
var content = new StringContent(json);
|
||||||
|
|
||||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||||
_response = _httpClient.PostAsync(url, content).Result;
|
|
||||||
var responseContent = _response.Content.ReadAsStringAsync().Result;
|
_response = await _httpClient.PostAsync(url, content);
|
||||||
|
|
||||||
|
var responseContent = await _response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.")
|
if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.")
|
||||||
{
|
{
|
||||||
@ -295,8 +348,14 @@ namespace Ocelot.IntegrationTests
|
|||||||
return _response.IsSuccessStatusCode;
|
return _response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandSent = WaitFor(20000).Until(() => SendCommand());
|
var commandSent = await WaitFor(40000).Until(async () =>
|
||||||
commandSent.ShouldBeTrue();
|
{
|
||||||
|
var result = await SendCommand();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
commandSent.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenIHaveAddedATokenToMyRequest()
|
private void GivenIHaveAddedATokenToMyRequest()
|
||||||
@ -304,9 +363,9 @@ namespace Ocelot.IntegrationTests
|
|||||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenIHaveAnOcelotToken(string adminPath)
|
private async Task GivenIHaveAnOcelotToken(string adminPath)
|
||||||
{
|
{
|
||||||
bool AddToken()
|
async Task<bool> AddToken()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -320,8 +379,8 @@ namespace Ocelot.IntegrationTests
|
|||||||
};
|
};
|
||||||
var content = new FormUrlEncodedContent(formData);
|
var content = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
var response = _httpClient.PostAsync(tokenUrl, content).Result;
|
var response = await _httpClient.PostAsync(tokenUrl, content);
|
||||||
var responseContent = response.Content.ReadAsStringAsync().Result;
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
if(!response.IsSuccessStatusCode)
|
if(!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -329,7 +388,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
|
|
||||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||||
var configPath = $"{adminPath}/.well-known/openid-configuration";
|
var configPath = $"{adminPath}/.well-known/openid-configuration";
|
||||||
response = _httpClient.GetAsync(configPath).Result;
|
response = await _httpClient.GetAsync(configPath);
|
||||||
return response.IsSuccessStatusCode;
|
return response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch(Exception)
|
||||||
@ -338,7 +397,13 @@ namespace Ocelot.IntegrationTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var addToken = WaitFor(20000).Until(() => AddToken());
|
var addToken = await WaitFor(40000).Until(async () =>
|
||||||
|
{
|
||||||
|
var result = await AddToken();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
addToken.ShouldBeTrue();
|
addToken.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,8 @@
|
|||||||
"HttpHandlerOptions": {
|
"HttpHandlerOptions": {
|
||||||
"AllowAutoRedirect": true,
|
"AllowAutoRedirect": true,
|
||||||
"UseCookieContainer": true,
|
"UseCookieContainer": true,
|
||||||
"UseTracing": true
|
"UseTracing": true,
|
||||||
|
"UseProxy": true
|
||||||
},
|
},
|
||||||
"QoSOptions": {
|
"QoSOptions": {
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
@ -1,124 +1,140 @@
|
|||||||
namespace Ocelot.UnitTests.Cache
|
namespace Ocelot.UnitTests.Cache
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Cache;
|
using Ocelot.Cache;
|
||||||
using Ocelot.Cache.Middleware;
|
using Ocelot.Cache.Middleware;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.DownstreamRouteFinder;
|
using Ocelot.DownstreamRouteFinder;
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
|
||||||
public class OutputCacheMiddlewareTests
|
public class OutputCacheMiddlewareTests
|
||||||
{
|
{
|
||||||
private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager;
|
private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager;
|
||||||
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
private Mock<IOcelotLogger> _logger;
|
private Mock<IOcelotLogger> _logger;
|
||||||
private OutputCacheMiddleware _middleware;
|
private OutputCacheMiddleware _middleware;
|
||||||
private readonly DownstreamContext _downstreamContext;
|
private readonly DownstreamContext _downstreamContext;
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private CachedResponse _response;
|
private CachedResponse _response;
|
||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IRegionCreator _regionCreator;
|
||||||
|
|
||||||
public OutputCacheMiddlewareTests()
|
public OutputCacheMiddlewareTests()
|
||||||
{
|
{
|
||||||
_cacheManager = new Mock<IOcelotCache<CachedResponse>>();
|
_cacheManager = new Mock<IOcelotCache<CachedResponse>>();
|
||||||
_regionCreator = new RegionCreator();
|
_regionCreator = new RegionCreator();
|
||||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
_logger = new Mock<IOcelotLogger>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object);
|
_loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object);
|
||||||
_next = context => Task.CompletedTask;
|
_next = context => Task.CompletedTask;
|
||||||
_downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"));
|
_downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_returned_cached_item_when_it_is_in_cache()
|
public void should_returned_cached_item_when_it_is_in_cache()
|
||||||
{
|
{
|
||||||
var headers = new Dictionary<string, IEnumerable<string>>
|
var headers = new Dictionary<string, IEnumerable<string>>
|
||||||
{
|
{
|
||||||
{ "test", new List<string> { "test" } }
|
{ "test", new List<string> { "test" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var contentHeaders = new Dictionary<string, IEnumerable<string>>
|
var contentHeaders = new Dictionary<string, IEnumerable<string>>
|
||||||
{
|
{
|
||||||
{ "content-type", new List<string> { "application/json" } }
|
{ "content-type", new List<string> { "application/json" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders);
|
var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders);
|
||||||
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
|
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
|
||||||
.And(x => x.GivenTheDownstreamRouteIs())
|
.And(x => x.GivenTheDownstreamRouteIs())
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenTheCacheGetIsCalledCorrectly())
|
.Then(x => x.ThenTheCacheGetIsCalledCorrectly())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_continue_with_pipeline_and_cache_response()
|
public void should_returned_cached_item_when_it_is_in_cache_expires_header()
|
||||||
{
|
{
|
||||||
this.Given(x => x.GivenResponseIsNotCached())
|
var contentHeaders = new Dictionary<string, IEnumerable<string>>
|
||||||
.And(x => x.GivenTheDownstreamRouteIs())
|
{
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
{ "Expires", new List<string> { "-1" } }
|
||||||
.Then(x => x.ThenTheCacheAddIsCalledCorrectly())
|
};
|
||||||
.BDDfy();
|
|
||||||
}
|
var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary<string, IEnumerable<string>>(), "", contentHeaders);
|
||||||
|
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
|
||||||
private void WhenICallTheMiddleware()
|
.And(x => x.GivenTheDownstreamRouteIs())
|
||||||
{
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator);
|
.Then(x => x.ThenTheCacheGetIsCalledCorrectly())
|
||||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenThereIsACachedResponse(CachedResponse response)
|
[Fact]
|
||||||
{
|
public void should_continue_with_pipeline_and_cache_response()
|
||||||
_response = response;
|
{
|
||||||
_cacheManager
|
this.Given(x => x.GivenResponseIsNotCached(new HttpResponseMessage()))
|
||||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
|
.And(x => x.GivenTheDownstreamRouteIs())
|
||||||
.Returns(_response);
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
}
|
.Then(x => x.ThenTheCacheAddIsCalledCorrectly())
|
||||||
|
.BDDfy();
|
||||||
private void GivenResponseIsNotCached()
|
}
|
||||||
{
|
|
||||||
_downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage());
|
private void WhenICallTheMiddleware()
|
||||||
}
|
{
|
||||||
|
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator);
|
||||||
private void GivenTheDownstreamRouteIs()
|
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
var reRoute = new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
private void GivenThereIsACachedResponse(CachedResponse response)
|
||||||
.WithIsCached(true)
|
{
|
||||||
.WithCacheOptions(new CacheOptions(100, "kanken"))
|
_response = response;
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
_cacheManager
|
||||||
.Build())
|
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.Returns(_response);
|
||||||
.Build();
|
}
|
||||||
|
|
||||||
var downstreamRoute = new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute);
|
private void GivenResponseIsNotCached(HttpResponseMessage responseMessage)
|
||||||
|
{
|
||||||
_downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues;
|
_downstreamContext.DownstreamResponse = new DownstreamResponse(responseMessage);
|
||||||
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
|
}
|
||||||
}
|
|
||||||
|
private void GivenTheDownstreamRouteIs()
|
||||||
private void ThenTheCacheGetIsCalledCorrectly()
|
{
|
||||||
{
|
var reRoute = new ReRouteBuilder()
|
||||||
_cacheManager
|
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
||||||
.Verify(x => x.Get(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
.WithIsCached(true)
|
||||||
}
|
.WithCacheOptions(new CacheOptions(100, "kanken"))
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
private void ThenTheCacheAddIsCalledCorrectly()
|
.Build())
|
||||||
{
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
_cacheManager
|
.Build();
|
||||||
.Verify(x => x.Add(It.IsAny<string>(), It.IsAny<CachedResponse>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once);
|
|
||||||
}
|
var downstreamRoute = new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute);
|
||||||
}
|
|
||||||
}
|
_downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues;
|
||||||
|
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCacheGetIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_cacheManager
|
||||||
|
.Verify(x => x.Get(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCacheAddIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_cacheManager
|
||||||
|
.Verify(x => x.Add(It.IsAny<string>(), It.IsAny<CachedResponse>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -199,7 +199,7 @@
|
|||||||
.WithDownstreamScheme("http")
|
.WithDownstreamScheme("http")
|
||||||
.WithUpstreamHttpMethod(new List<string>() {"Get"})
|
.WithUpstreamHttpMethod(new List<string>() {"Get"})
|
||||||
.WithDownstreamAddresses(new List<DownstreamHostAndPort>() {new DownstreamHostAndPort("localhost", 51878)})
|
.WithDownstreamAddresses(new List<DownstreamHostAndPort>() {new DownstreamHostAndPort("localhost", 51878)})
|
||||||
.WithLoadBalancerKey("/laura|Get")
|
.WithLoadBalancerKey("/laura|Get|localhost:51878")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var lauraReRoute = new ReRouteBuilder()
|
var lauraReRoute = new ReRouteBuilder()
|
||||||
@ -218,7 +218,7 @@
|
|||||||
.WithDownstreamScheme("http")
|
.WithDownstreamScheme("http")
|
||||||
.WithUpstreamHttpMethod(new List<string>() { "Get" })
|
.WithUpstreamHttpMethod(new List<string>() { "Get" })
|
||||||
.WithDownstreamAddresses(new List<DownstreamHostAndPort>() { new DownstreamHostAndPort("localhost", 51878) })
|
.WithDownstreamAddresses(new List<DownstreamHostAndPort>() { new DownstreamHostAndPort("localhost", 51878) })
|
||||||
.WithLoadBalancerKey("/tom|Get")
|
.WithLoadBalancerKey("/tom|Get|localhost:51880")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var tomReRoute = new ReRouteBuilder()
|
var tomReRoute = new ReRouteBuilder()
|
||||||
@ -409,7 +409,7 @@
|
|||||||
.WithDownstreamPathTemplate("/products/{productId}")
|
.WithDownstreamPathTemplate("/products/{productId}")
|
||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|127.0.0.1:0")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -461,7 +461,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithDelegatingHandlers(handlers)
|
.WithDelegatingHandlers(handlers)
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -506,7 +506,7 @@
|
|||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithUseServiceDiscovery(true)
|
.WithUseServiceDiscovery(true)
|
||||||
.WithServiceName("ProductService")
|
.WithServiceName("ProductService")
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -557,7 +557,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithUseServiceDiscovery(false)
|
.WithUseServiceDiscovery(false)
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -600,7 +600,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1))
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1))
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -645,7 +645,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithRequestIdKey("blahhhh")
|
.WithRequestIdKey("blahhhh")
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -688,7 +688,7 @@
|
|||||||
{
|
{
|
||||||
var reRouteOptions = new ReRouteOptionsBuilder()
|
var reRouteOptions = new ReRouteOptionsBuilder()
|
||||||
.Build();
|
.Build();
|
||||||
var httpHandlerOptions = new HttpHandlerOptions(true, true,false);
|
var httpHandlerOptions = new HttpHandlerOptions(true, true,false, true);
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
{
|
{
|
||||||
@ -740,7 +740,7 @@
|
|||||||
{
|
{
|
||||||
new ClaimToThing("CustomerId", "CustomerId", "", 0),
|
new ClaimToThing("CustomerId", "CustomerId", "", 0),
|
||||||
})
|
})
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var expected = new List<ReRoute>
|
var expected = new List<ReRoute>
|
||||||
@ -783,7 +783,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithAuthenticationOptions(authenticationOptions)
|
.WithAuthenticationOptions(authenticationOptions)
|
||||||
.WithLoadBalancerKey("/api/products/{productId}|Get")
|
.WithLoadBalancerKey("/api/products/{productId}|Get|")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var expected = new List<ReRoute>
|
var expected = new List<ReRoute>
|
||||||
|
@ -35,7 +35,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedOptions = new HttpHandlerOptions(false, false, false);
|
var expectedOptions = new HttpHandlerOptions(false, false, false, true);
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
.When(x => WhenICreateHttpHandlerOptions())
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedOptions = new HttpHandlerOptions(false, false, true);
|
var expectedOptions = new HttpHandlerOptions(false, false, true, true);
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
.And(x => GivenARealTracer())
|
.And(x => GivenARealTracer())
|
||||||
@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default()
|
public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default()
|
||||||
{
|
{
|
||||||
var fileReRoute = new FileReRoute();
|
var fileReRoute = new FileReRoute();
|
||||||
var expectedOptions = new HttpHandlerOptions(false, false, false);
|
var expectedOptions = new HttpHandlerOptions(false, false, false, true);
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
.When(x => WhenICreateHttpHandlerOptions())
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
@ -88,7 +88,42 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedOptions = new HttpHandlerOptions(false, false, false);
|
var expectedOptions = new HttpHandlerOptions(false, false, false, true);
|
||||||
|
|
||||||
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
|
.Then(x => ThenTheFollowingOptionsReturned(expectedOptions))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create_options_with_useproxy_true_as_default()
|
||||||
|
{
|
||||||
|
var fileReRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions()
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedOptions = new HttpHandlerOptions(false, false, false, true);
|
||||||
|
|
||||||
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
|
.Then(x => ThenTheFollowingOptionsReturned(expectedOptions))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create_options_with_specified_useproxy()
|
||||||
|
{
|
||||||
|
var fileReRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
UseProxy = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedOptions = new HttpHandlerOptions(false, false, false, false);
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
.When(x => WhenICreateHttpHandlerOptions())
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
@ -111,7 +146,8 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_httpHandlerOptions.ShouldNotBeNull();
|
_httpHandlerOptions.ShouldNotBeNull();
|
||||||
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect);
|
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect);
|
||||||
_httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer);
|
_httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer);
|
||||||
_httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing);
|
_httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing);
|
||||||
|
_httpHandlerOptions.UseProxy.ShouldBe(expected.UseProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenARealTracer()
|
private void GivenARealTracer()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
@ -30,7 +29,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/[0-9a-zA-Z].*$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/.+$"))
|
||||||
.And(x => ThenThePriorityIs(0))
|
.And(x => ThenThePriorityIs(0))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
@ -62,7 +61,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/[0-9a-zA-Z].*$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/.+$"))
|
||||||
.And(x => ThenThePriorityIs(1))
|
.And(x => ThenThePriorityIs(1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
@ -93,7 +92,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
};
|
};
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/[0-9a-zA-Z].*$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/.+$"))
|
||||||
.And(x => ThenThePriorityIs(1))
|
.And(x => ThenThePriorityIs(1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
@ -109,7 +108,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+$"))
|
||||||
.And(x => ThenThePriorityIs(1))
|
.And(x => ThenThePriorityIs(1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
@ -125,7 +124,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+/variants/.+$"))
|
||||||
.And(x => ThenThePriorityIs(1))
|
.And(x => ThenThePriorityIs(1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
@ -141,7 +140,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*(/|)$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+/variants/.+(/|)$"))
|
||||||
.And(x => ThenThePriorityIs(1))
|
.And(x => ThenThePriorityIs(1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
@ -187,7 +186,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
|
||||||
.When(x => x.WhenICreateTheTemplatePattern())
|
.When(x => x.WhenICreateTheTemplatePattern())
|
||||||
.Then(x => x.ThenTheFollowingIsReturned("^/[0-9a-zA-Z].*/products/variants/[0-9a-zA-Z].*(/|)$"))
|
.Then(x => x.ThenTheFollowingIsReturned("^/.+/products/variants/.+(/|)$"))
|
||||||
.And(x => ThenThePriorityIs(1))
|
.And(x => ThenThePriorityIs(1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using Ocelot.Configuration;
|
|||||||
namespace Ocelot.UnitTests.Controllers
|
namespace Ocelot.UnitTests.Controllers
|
||||||
{
|
{
|
||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
|
using Rafty.Concensus.Node;
|
||||||
|
|
||||||
public class FileConfigurationControllerTests
|
public class FileConfigurationControllerTests
|
||||||
{
|
{
|
||||||
@ -126,14 +127,14 @@ namespace Ocelot.UnitTests.Controllers
|
|||||||
{
|
{
|
||||||
_node
|
_node
|
||||||
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
|
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
|
||||||
.ReturnsAsync(new Rafty.Concensus.OkResponse<UpdateFileConfiguration>(new UpdateFileConfiguration(new FileConfiguration())));
|
.ReturnsAsync(new Rafty.Infrastructure.OkResponse<UpdateFileConfiguration>(new UpdateFileConfiguration(new FileConfiguration())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheNodeReturnsError()
|
private void GivenTheNodeReturnsError()
|
||||||
{
|
{
|
||||||
_node
|
_node
|
||||||
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
|
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
|
||||||
.ReturnsAsync(new Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>("error", new UpdateFileConfiguration(new FileConfiguration())));
|
.ReturnsAsync(new Rafty.Infrastructure.ErrorResponse<UpdateFileConfiguration>("error", new UpdateFileConfiguration(new FileConfiguration())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheConfigSetterReturns(Response response)
|
private void GivenTheConfigSetterReturns(Response response)
|
||||||
|
@ -583,6 +583,19 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
.WithUpstreamHost("MATCH")
|
.WithUpstreamHost("MATCH")
|
||||||
|
.Build(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamPathTemplate("someDownstreamPath")
|
||||||
|
.WithUpstreamPathTemplate("someUpstreamPath")
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { }) // empty list of methods
|
||||||
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
|
.WithUpstreamHost("MATCH")
|
||||||
|
.Build())
|
||||||
|
.WithUpstreamPathTemplate("someUpstreamPath")
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { }) // empty list of methods
|
||||||
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
|
.WithUpstreamHost("MATCH")
|
||||||
.Build()
|
.Build()
|
||||||
}, string.Empty, serviceProviderConfig
|
}, string.Empty, serviceProviderConfig
|
||||||
))
|
))
|
||||||
@ -594,6 +607,71 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_http_method()
|
||||||
|
{
|
||||||
|
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"))
|
||||||
|
.And(x => GivenTheUpstreamHostIs("DONTMATCH"))
|
||||||
|
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<PlaceholderNameAndValue>>(new List<PlaceholderNameAndValue>())))
|
||||||
|
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
|
||||||
|
{
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamPathTemplate("someDownstreamPath")
|
||||||
|
.WithUpstreamPathTemplate("someUpstreamPath")
|
||||||
|
.WithUpstreamHttpMethod(new List<string>())
|
||||||
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
|
.WithUpstreamHost("MATCH")
|
||||||
|
.Build())
|
||||||
|
.WithUpstreamPathTemplate("someUpstreamPath")
|
||||||
|
.WithUpstreamHttpMethod(new List<string>())
|
||||||
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
|
.WithUpstreamHost("MATCH")
|
||||||
|
.Build()
|
||||||
|
}, string.Empty, serviceProviderConfig
|
||||||
|
))
|
||||||
|
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
|
||||||
|
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
|
||||||
|
.When(x => x.WhenICallTheFinder())
|
||||||
|
.Then(x => x.ThenAnErrorResponseIsReturned())
|
||||||
|
.And(x => x.ThenTheUrlMatcherIsNotCalled())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_route_when_host_does_match_with_empty_upstream_http_method()
|
||||||
|
{
|
||||||
|
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"))
|
||||||
|
.And(x => GivenTheUpstreamHostIs("MATCH"))
|
||||||
|
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<PlaceholderNameAndValue>>(new List<PlaceholderNameAndValue>())))
|
||||||
|
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
|
||||||
|
{
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamPathTemplate("someDownstreamPath")
|
||||||
|
.WithUpstreamPathTemplate("someUpstreamPath")
|
||||||
|
.WithUpstreamHttpMethod(new List<string>())
|
||||||
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
|
.WithUpstreamHost("MATCH")
|
||||||
|
.Build())
|
||||||
|
.WithUpstreamPathTemplate("someUpstreamPath")
|
||||||
|
.WithUpstreamHttpMethod(new List<string>())
|
||||||
|
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
|
||||||
|
.WithUpstreamHost("MATCH")
|
||||||
|
.Build()
|
||||||
|
}, string.Empty, serviceProviderConfig
|
||||||
|
))
|
||||||
|
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
|
||||||
|
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
|
||||||
|
.When(x => x.WhenICallTheFinder())
|
||||||
|
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_route_when_host_matches_but_null_host_on_same_path_first()
|
public void should_return_route_when_host_matches_but_null_host_on_same_path_first()
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
{
|
{
|
||||||
_factory = new Mock<ILoadBalancerFactory>();
|
_factory = new Mock<ILoadBalancerFactory>();
|
||||||
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
||||||
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty, "configKey");
|
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty, "configKey", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -7,6 +7,8 @@ namespace Ocelot.UnitTests.Middleware
|
|||||||
using Ocelot.Middleware.Pipeline;
|
using Ocelot.Middleware.Pipeline;
|
||||||
using Pivotal.Discovery.Client;
|
using Pivotal.Discovery.Client;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
using Steeltoe.Common.Discovery;
|
||||||
|
using Steeltoe.Discovery.Eureka;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@ -40,7 +42,16 @@ namespace Ocelot.UnitTests.Middleware
|
|||||||
var root = test.Build();
|
var root = test.Build();
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<IConfiguration>(root);
|
services.AddSingleton<IConfiguration>(root);
|
||||||
services.AddDiscoveryClient(new DiscoveryOptions {ClientType = DiscoveryClientType.EUREKA});
|
services.AddDiscoveryClient(new DiscoveryOptions
|
||||||
|
{
|
||||||
|
ClientType = DiscoveryClientType.EUREKA,
|
||||||
|
//options can not be null
|
||||||
|
ClientOptions = new EurekaClientOptions()
|
||||||
|
{
|
||||||
|
ShouldFetchRegistry = false,
|
||||||
|
ShouldRegisterWithEureka = false
|
||||||
|
}
|
||||||
|
});
|
||||||
services.AddOcelot();
|
services.AddOcelot();
|
||||||
var provider = services.BuildServiceProvider();
|
var provider = services.BuildServiceProvider();
|
||||||
_builder = new OcelotPipelineBuilder(provider);
|
_builder = new OcelotPipelineBuilder(provider);
|
||||||
|
@ -1,58 +1,67 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
<AssemblyName>Ocelot.UnitTests</AssemblyName>
|
<AssemblyName>Ocelot.UnitTests</AssemblyName>
|
||||||
<PackageId>Ocelot.UnitTests</PackageId>
|
<PackageId>Ocelot.UnitTests</PackageId>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||||
<RuntimeIdentifiers>osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64</RuntimeIdentifiers>
|
||||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
<None Update="appsettings.json">
|
||||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
</None>
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
<None Update="idsrv3test.pfx">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</PackageReference>
|
</None>
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
</ItemGroup>
|
||||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
</PackageReference>
|
||||||
<PackageReference Include="Moq" Version="4.8.2" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||||
<ItemGroup>
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||||
<Folder Include="WebSockets\" />
|
<PackageReference Include="Moq" Version="4.8.2" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||||
</Project>
|
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||||
|
<PackageReference Include="xunit" Version="2.3.1" />
|
||||||
|
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||||
|
<PackageReference Include="Rafty" Version="0.4.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="WebSockets\" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Request.Creator;
|
||||||
|
using Ocelot.Request.Middleware;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Request.Creator
|
||||||
|
{
|
||||||
|
public class DownstreamRequestCreatorTests
|
||||||
|
{
|
||||||
|
private Mock<IFrameworkDescription> _framework;
|
||||||
|
private DownstreamRequestCreator _downstreamRequestCreator;
|
||||||
|
private HttpRequestMessage _request;
|
||||||
|
private DownstreamRequest _result;
|
||||||
|
|
||||||
|
public DownstreamRequestCreatorTests()
|
||||||
|
{
|
||||||
|
_framework = new Mock<IFrameworkDescription>();
|
||||||
|
_downstreamRequestCreator = new DownstreamRequestCreator(_framework.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create_downstream_request()
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "http://www.test.com");
|
||||||
|
var content = new StringContent("test");
|
||||||
|
request.Content = content;
|
||||||
|
|
||||||
|
this.Given(_ => GivenTheFrameworkIs(""))
|
||||||
|
.And(_ => GivenTheRequestIs(request))
|
||||||
|
.When(_ => WhenICreate())
|
||||||
|
.Then(_ => ThenTheDownstreamRequestHasABody())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_remove_body_for_http_methods()
|
||||||
|
{
|
||||||
|
var methods = new List<HttpMethod> { HttpMethod.Get, HttpMethod.Head, HttpMethod.Delete, HttpMethod.Trace };
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "http://www.test.com");
|
||||||
|
var content = new StringContent("test");
|
||||||
|
request.Content = content;
|
||||||
|
|
||||||
|
methods.ForEach(m => {
|
||||||
|
this.Given(_ => GivenTheFrameworkIs(".NET Framework"))
|
||||||
|
.And(_ => GivenTheRequestIs(request))
|
||||||
|
.When(_ => WhenICreate())
|
||||||
|
.Then(_ => ThenTheDownstreamRequestDoesNotHaveABody())
|
||||||
|
.BDDfy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheFrameworkIs(string framework)
|
||||||
|
{
|
||||||
|
_framework.Setup(x => x.Get()).Returns(framework);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheRequestIs(HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
_request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICreate()
|
||||||
|
{
|
||||||
|
_result = _downstreamRequestCreator.Create(_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ThenTheDownstreamRequestHasABody()
|
||||||
|
{
|
||||||
|
_result.ShouldNotBeNull();
|
||||||
|
_result.Method.ToLower().ShouldBe("get");
|
||||||
|
_result.Scheme.ToLower().ShouldBe("http");
|
||||||
|
_result.Host.ToLower().ShouldBe("www.test.com");
|
||||||
|
var resultContent = await _result.ToHttpRequestMessage().Content.ReadAsStringAsync();
|
||||||
|
resultContent.ShouldBe("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheDownstreamRequestDoesNotHaveABody()
|
||||||
|
{
|
||||||
|
_result.ShouldNotBeNull();
|
||||||
|
_result.Method.ToLower().ShouldBe("get");
|
||||||
|
_result.Scheme.ToLower().ShouldBe("http");
|
||||||
|
_result.Host.ToLower().ShouldBe("www.test.com");
|
||||||
|
_result.ToHttpRequestMessage().Content.ShouldBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,143 +1,146 @@
|
|||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.Request
|
namespace Ocelot.UnitTests.Request
|
||||||
{
|
{
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Request.Mapper;
|
using Ocelot.Request.Mapper;
|
||||||
using Ocelot.Request.Middleware;
|
using Ocelot.Request.Middleware;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.DownstreamRouteFinder.Middleware;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
using Ocelot.Request.Creator;
|
||||||
public class DownstreamRequestInitialiserMiddlewareTests
|
using Ocelot.Infrastructure;
|
||||||
{
|
|
||||||
readonly DownstreamRequestInitialiserMiddleware _middleware;
|
public class DownstreamRequestInitialiserMiddlewareTests
|
||||||
|
{
|
||||||
readonly Mock<HttpContext> _httpContext;
|
readonly DownstreamRequestInitialiserMiddleware _middleware;
|
||||||
|
|
||||||
readonly Mock<HttpRequest> _httpRequest;
|
readonly Mock<HttpContext> _httpContext;
|
||||||
|
|
||||||
readonly Mock<OcelotRequestDelegate> _next;
|
readonly Mock<HttpRequest> _httpRequest;
|
||||||
|
|
||||||
readonly Mock<IRequestMapper> _requestMapper;
|
readonly Mock<OcelotRequestDelegate> _next;
|
||||||
|
|
||||||
readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
readonly Mock<IRequestMapper> _requestMapper;
|
||||||
|
|
||||||
readonly Mock<IOcelotLogger> _logger;
|
readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
|
|
||||||
Response<HttpRequestMessage> _mappedRequest;
|
readonly Mock<IOcelotLogger> _logger;
|
||||||
private DownstreamContext _downstreamContext;
|
|
||||||
|
Response<HttpRequestMessage> _mappedRequest;
|
||||||
public DownstreamRequestInitialiserMiddlewareTests()
|
private DownstreamContext _downstreamContext;
|
||||||
{
|
|
||||||
_httpContext = new Mock<HttpContext>();
|
public DownstreamRequestInitialiserMiddlewareTests()
|
||||||
_httpRequest = new Mock<HttpRequest>();
|
{
|
||||||
_requestMapper = new Mock<IRequestMapper>();
|
_httpContext = new Mock<HttpContext>();
|
||||||
_next = new Mock<OcelotRequestDelegate>();
|
_httpRequest = new Mock<HttpRequest>();
|
||||||
_logger = new Mock<IOcelotLogger>();
|
_requestMapper = new Mock<IRequestMapper>();
|
||||||
|
_next = new Mock<OcelotRequestDelegate>();
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_loggerFactory
|
|
||||||
.Setup(lf => lf.CreateLogger<DownstreamRequestInitialiserMiddleware>())
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
.Returns(_logger.Object);
|
_loggerFactory
|
||||||
|
.Setup(lf => lf.CreateLogger<DownstreamRequestInitialiserMiddleware>())
|
||||||
_middleware = new DownstreamRequestInitialiserMiddleware(
|
.Returns(_logger.Object);
|
||||||
_next.Object,
|
|
||||||
_loggerFactory.Object,
|
_middleware = new DownstreamRequestInitialiserMiddleware(
|
||||||
_requestMapper.Object);
|
_next.Object,
|
||||||
|
_loggerFactory.Object,
|
||||||
_downstreamContext = new DownstreamContext(_httpContext.Object);
|
_requestMapper.Object,
|
||||||
}
|
new DownstreamRequestCreator(new FrameworkDescription()));
|
||||||
|
|
||||||
[Fact]
|
_downstreamContext = new DownstreamContext(_httpContext.Object);
|
||||||
public void Should_handle_valid_httpRequest()
|
}
|
||||||
{
|
|
||||||
this.Given(_ => GivenTheHttpContextContainsARequest())
|
[Fact]
|
||||||
.And(_ => GivenTheMapperWillReturnAMappedRequest())
|
public void Should_handle_valid_httpRequest()
|
||||||
.When(_ => WhenTheMiddlewareIsInvoked())
|
{
|
||||||
.Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest())
|
this.Given(_ => GivenTheHttpContextContainsARequest())
|
||||||
.And(_ => ThenTheDownstreamRequestIsStored())
|
.And(_ => GivenTheMapperWillReturnAMappedRequest())
|
||||||
.And(_ => ThenTheNextMiddlewareIsInvoked())
|
.When(_ => WhenTheMiddlewareIsInvoked())
|
||||||
.BDDfy();
|
.Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest())
|
||||||
}
|
.And(_ => ThenTheDownstreamRequestIsStored())
|
||||||
|
.And(_ => ThenTheNextMiddlewareIsInvoked())
|
||||||
[Fact]
|
.BDDfy();
|
||||||
public void Should_handle_mapping_failure()
|
}
|
||||||
{
|
|
||||||
this.Given(_ => GivenTheHttpContextContainsARequest())
|
[Fact]
|
||||||
.And(_ => GivenTheMapperWillReturnAnError())
|
public void Should_handle_mapping_failure()
|
||||||
.When(_ => WhenTheMiddlewareIsInvoked())
|
{
|
||||||
.And(_ => ThenTheDownstreamRequestIsNotStored())
|
this.Given(_ => GivenTheHttpContextContainsARequest())
|
||||||
.And(_ => ThenAPipelineErrorIsStored())
|
.And(_ => GivenTheMapperWillReturnAnError())
|
||||||
.And(_ => ThenTheNextMiddlewareIsNotInvoked())
|
.When(_ => WhenTheMiddlewareIsInvoked())
|
||||||
.BDDfy();
|
.And(_ => ThenTheDownstreamRequestIsNotStored())
|
||||||
}
|
.And(_ => ThenAPipelineErrorIsStored())
|
||||||
|
.And(_ => ThenTheNextMiddlewareIsNotInvoked())
|
||||||
private void GivenTheHttpContextContainsARequest()
|
.BDDfy();
|
||||||
{
|
}
|
||||||
_httpContext
|
|
||||||
.Setup(hc => hc.Request)
|
private void GivenTheHttpContextContainsARequest()
|
||||||
.Returns(_httpRequest.Object);
|
{
|
||||||
}
|
_httpContext
|
||||||
|
.Setup(hc => hc.Request)
|
||||||
private void GivenTheMapperWillReturnAMappedRequest()
|
.Returns(_httpRequest.Object);
|
||||||
{
|
}
|
||||||
_mappedRequest = new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk"));
|
|
||||||
|
private void GivenTheMapperWillReturnAMappedRequest()
|
||||||
_requestMapper
|
{
|
||||||
.Setup(rm => rm.Map(It.IsAny<HttpRequest>()))
|
_mappedRequest = new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk"));
|
||||||
.ReturnsAsync(_mappedRequest);
|
|
||||||
}
|
_requestMapper
|
||||||
|
.Setup(rm => rm.Map(It.IsAny<HttpRequest>()))
|
||||||
private void GivenTheMapperWillReturnAnError()
|
.ReturnsAsync(_mappedRequest);
|
||||||
{
|
}
|
||||||
_mappedRequest = new ErrorResponse<HttpRequestMessage>(new UnmappableRequestError(new System.Exception("boooom!")));
|
|
||||||
|
private void GivenTheMapperWillReturnAnError()
|
||||||
_requestMapper
|
{
|
||||||
.Setup(rm => rm.Map(It.IsAny<HttpRequest>()))
|
_mappedRequest = new ErrorResponse<HttpRequestMessage>(new UnmappableRequestError(new System.Exception("boooom!")));
|
||||||
.ReturnsAsync(_mappedRequest);
|
|
||||||
}
|
_requestMapper
|
||||||
|
.Setup(rm => rm.Map(It.IsAny<HttpRequest>()))
|
||||||
private void WhenTheMiddlewareIsInvoked()
|
.ReturnsAsync(_mappedRequest);
|
||||||
{
|
}
|
||||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
|
||||||
}
|
private void WhenTheMiddlewareIsInvoked()
|
||||||
|
{
|
||||||
private void ThenTheContexRequestIsMappedToADownstreamRequest()
|
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
_requestMapper.Verify(rm => rm.Map(_httpRequest.Object), Times.Once);
|
|
||||||
}
|
private void ThenTheContexRequestIsMappedToADownstreamRequest()
|
||||||
|
{
|
||||||
private void ThenTheDownstreamRequestIsStored()
|
_requestMapper.Verify(rm => rm.Map(_httpRequest.Object), Times.Once);
|
||||||
{
|
}
|
||||||
_downstreamContext.DownstreamRequest.ShouldNotBeNull();
|
|
||||||
}
|
private void ThenTheDownstreamRequestIsStored()
|
||||||
|
{
|
||||||
private void ThenTheDownstreamRequestIsNotStored()
|
_downstreamContext.DownstreamRequest.ShouldNotBeNull();
|
||||||
{
|
}
|
||||||
_downstreamContext.DownstreamRequest.ShouldBeNull();
|
|
||||||
}
|
private void ThenTheDownstreamRequestIsNotStored()
|
||||||
|
{
|
||||||
private void ThenAPipelineErrorIsStored()
|
_downstreamContext.DownstreamRequest.ShouldBeNull();
|
||||||
{
|
}
|
||||||
_downstreamContext.IsError.ShouldBeTrue();
|
|
||||||
_downstreamContext.Errors.ShouldBe(_mappedRequest.Errors);
|
private void ThenAPipelineErrorIsStored()
|
||||||
}
|
{
|
||||||
|
_downstreamContext.IsError.ShouldBeTrue();
|
||||||
private void ThenTheNextMiddlewareIsInvoked()
|
_downstreamContext.Errors.ShouldBe(_mappedRequest.Errors);
|
||||||
{
|
}
|
||||||
_next.Verify(n => n(_downstreamContext), Times.Once);
|
|
||||||
}
|
private void ThenTheNextMiddlewareIsInvoked()
|
||||||
|
{
|
||||||
private void ThenTheNextMiddlewareIsNotInvoked()
|
_next.Verify(n => n(_downstreamContext), Times.Once);
|
||||||
{
|
}
|
||||||
_next.Verify(n => n(It.IsAny<DownstreamContext>()), Times.Never);
|
|
||||||
}
|
private void ThenTheNextMiddlewareIsNotInvoked()
|
||||||
}
|
{
|
||||||
}
|
_next.Verify(n => n(It.IsAny<DownstreamContext>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -46,7 +46,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true))
|
||||||
.WithDelegatingHandlers(new List<string>
|
.WithDelegatingHandlers(new List<string>
|
||||||
{
|
{
|
||||||
"FakeDelegatingHandler",
|
"FakeDelegatingHandler",
|
||||||
@ -82,7 +82,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true))
|
||||||
.WithDelegatingHandlers(new List<string>
|
.WithDelegatingHandlers(new List<string>
|
||||||
{
|
{
|
||||||
"FakeDelegatingHandlerTwo",
|
"FakeDelegatingHandlerTwo",
|
||||||
@ -119,7 +119,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true))
|
||||||
.WithDelegatingHandlers(new List<string>
|
.WithDelegatingHandlers(new List<string>
|
||||||
{
|
{
|
||||||
"FakeDelegatingHandlerTwo",
|
"FakeDelegatingHandlerTwo",
|
||||||
@ -155,7 +155,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true))
|
||||||
.WithDelegatingHandlers(new List<string>
|
.WithDelegatingHandlers(new List<string>
|
||||||
{
|
{
|
||||||
"FakeDelegatingHandler",
|
"FakeDelegatingHandler",
|
||||||
@ -189,7 +189,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true))
|
||||||
.WithDelegatingHandlers(new List<string>
|
.WithDelegatingHandlers(new List<string>
|
||||||
{
|
{
|
||||||
"FakeDelegatingHandler",
|
"FakeDelegatingHandler",
|
||||||
@ -244,7 +244,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithLoadBalancerKey("").Build();
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build();
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowingRequest(reRoute))
|
this.Given(x => GivenTheFollowingRequest(reRoute))
|
||||||
.And(x => GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(It.IsAny<PollyQoSProvider>())))
|
.And(x => GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(It.IsAny<PollyQoSProvider>())))
|
||||||
@ -264,7 +264,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithLoadBalancerKey("").Build();
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build();
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowingRequest(reRoute))
|
this.Given(x => GivenTheFollowingRequest(reRoute))
|
||||||
.And(x => GivenTheServiceProviderReturnsNothing())
|
.And(x => GivenTheServiceProviderReturnsNothing())
|
||||||
@ -284,7 +284,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithLoadBalancerKey("").Build();
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build();
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowingRequest(reRoute))
|
this.Given(x => GivenTheFollowingRequest(reRoute))
|
||||||
.And(x => GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(It.IsAny<PollyQoSProvider>())))
|
.And(x => GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(It.IsAny<PollyQoSProvider>())))
|
||||||
@ -306,7 +306,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithLoadBalancerKey("").Build();
|
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build();
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowingRequest(reRoute))
|
this.Given(x => GivenTheFollowingRequest(reRoute))
|
||||||
.And(x => GivenTheQosProviderHouseReturns(new ErrorResponse<IQoSProvider>(It.IsAny<Error>())))
|
.And(x => GivenTheQosProviderHouseReturns(new ErrorResponse<IQoSProvider>(It.IsAny<Error>())))
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@ -51,7 +49,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.Build();
|
.Build();
|
||||||
@ -71,7 +69,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.WithDangerousAcceptAnyServerCertificateValidator(true)
|
.WithDangerousAcceptAnyServerCertificateValidator(true)
|
||||||
@ -93,7 +91,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.Build();
|
.Build();
|
||||||
@ -124,7 +122,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.Build();
|
.Build();
|
||||||
@ -159,7 +157,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -52,7 +52,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.Build();
|
.Build();
|
||||||
@ -78,7 +78,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().Build())
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
.Build();
|
.Build();
|
||||||
@ -103,7 +103,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true))
|
||||||
.WithLoadBalancerKey("")
|
.WithLoadBalancerKey("")
|
||||||
.WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build())
|
.WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build())
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
using Ocelot.ServiceDiscovery.Providers;
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
using Pivotal.Discovery.Client;
|
using Pivotal.Discovery.Client;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
using Steeltoe.Common.Discovery;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Values;
|
using Values;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Consul;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Infrastructure.Consul;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.ServiceDiscovery.Configuration;
|
||||||
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using Xunit;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Shouldly;
|
||||||
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||||
|
{
|
||||||
|
public class PollingConsulServiceDiscoveryProviderTests
|
||||||
|
{
|
||||||
|
private readonly int _delay;
|
||||||
|
private PollingConsulServiceDiscoveryProvider _provider;
|
||||||
|
private readonly string _serviceName;
|
||||||
|
private List<Service> _services;
|
||||||
|
private readonly Mock<IOcelotLoggerFactory> _factory;
|
||||||
|
private readonly Mock<IOcelotLogger> _logger;
|
||||||
|
private Mock<IServiceDiscoveryProvider> _consulServiceDiscoveryProvider;
|
||||||
|
private List<Service> _result;
|
||||||
|
|
||||||
|
public PollingConsulServiceDiscoveryProviderTests()
|
||||||
|
{
|
||||||
|
_services = new List<Service>();
|
||||||
|
_delay = 1;
|
||||||
|
_factory = new Mock<IOcelotLoggerFactory>();
|
||||||
|
_logger = new Mock<IOcelotLogger>();
|
||||||
|
_factory.Setup(x => x.CreateLogger<PollingConsulServiceDiscoveryProvider>()).Returns(_logger.Object);
|
||||||
|
_consulServiceDiscoveryProvider = new Mock<IServiceDiscoveryProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_service_from_consul()
|
||||||
|
{
|
||||||
|
var service = new Service("", new ServiceHostAndPort("", 0), "", "", new List<string>());
|
||||||
|
|
||||||
|
this.Given(x => GivenConsulReturns(service))
|
||||||
|
.When(x => WhenIGetTheServices(1))
|
||||||
|
.Then(x => ThenTheCountIs(1))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenConsulReturns(Service service)
|
||||||
|
{
|
||||||
|
_services.Add(service);
|
||||||
|
_consulServiceDiscoveryProvider.Setup(x => x.Get()).ReturnsAsync(_services);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCountIs(int count)
|
||||||
|
{
|
||||||
|
_result.Count.ShouldBe(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIGetTheServices(int expected)
|
||||||
|
{
|
||||||
|
_provider = new PollingConsulServiceDiscoveryProvider(_delay, _serviceName, _factory.Object, _consulServiceDiscoveryProvider.Object);
|
||||||
|
|
||||||
|
var result = WaitFor(3000).Until(() => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_result = _provider.Get().GetAwaiter().GetResult();
|
||||||
|
if(_result.Count == expected)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch(Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,153 +1,177 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Infrastructure.Consul;
|
using Ocelot.Infrastructure.Consul;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.ServiceDiscovery;
|
using Ocelot.ServiceDiscovery;
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||||
{
|
{
|
||||||
using Pivotal.Discovery.Client;
|
using Pivotal.Discovery.Client;
|
||||||
|
using Steeltoe.Common.Discovery;
|
||||||
public class ServiceProviderFactoryTests
|
|
||||||
{
|
public class ServiceProviderFactoryTests
|
||||||
private ServiceProviderConfiguration _serviceConfig;
|
{
|
||||||
private IServiceDiscoveryProvider _result;
|
private ServiceProviderConfiguration _serviceConfig;
|
||||||
private readonly ServiceDiscoveryProviderFactory _factory;
|
private IServiceDiscoveryProvider _result;
|
||||||
private DownstreamReRoute _reRoute;
|
private readonly ServiceDiscoveryProviderFactory _factory;
|
||||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
private DownstreamReRoute _reRoute;
|
||||||
private Mock<IDiscoveryClient> _discoveryClient;
|
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
|
private Mock<IDiscoveryClient> _discoveryClient;
|
||||||
public ServiceProviderFactoryTests()
|
private Mock<IOcelotLogger> _logger;
|
||||||
{
|
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
public ServiceProviderFactoryTests()
|
||||||
_discoveryClient = new Mock<IDiscoveryClient>();
|
{
|
||||||
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, new ConsulClientFactory(), _discoveryClient.Object);
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
}
|
_logger = new Mock<IOcelotLogger>();
|
||||||
|
_loggerFactory.Setup(x => x.CreateLogger<PollingConsulServiceDiscoveryProvider>()).Returns(_logger.Object);
|
||||||
[Fact]
|
_discoveryClient = new Mock<IDiscoveryClient>();
|
||||||
public void should_return_no_service_provider()
|
var consulClient = new Mock<IConsulClientFactory>();
|
||||||
{
|
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, consulClient.Object, _discoveryClient.Object);
|
||||||
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
}
|
||||||
.Build();
|
|
||||||
|
[Fact]
|
||||||
var reRoute = new DownstreamReRouteBuilder().Build();
|
public void should_return_no_service_provider()
|
||||||
|
{
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
.Build();
|
||||||
.Then(x => x.ThenTheServiceProviderIs<ConfigurationServiceProvider>())
|
|
||||||
.BDDfy();
|
var reRoute = new DownstreamReRouteBuilder().Build();
|
||||||
}
|
|
||||||
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
[Fact]
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
public void should_return_list_of_configuration_services()
|
.Then(x => x.ThenTheServiceProviderIs<ConfigurationServiceProvider>())
|
||||||
{
|
.BDDfy();
|
||||||
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
}
|
||||||
.Build();
|
|
||||||
|
[Fact]
|
||||||
var downstreamAddresses = new List<DownstreamHostAndPort>()
|
public void should_return_list_of_configuration_services()
|
||||||
{
|
{
|
||||||
new DownstreamHostAndPort("asdf.com", 80),
|
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
||||||
new DownstreamHostAndPort("abc.com", 80)
|
.Build();
|
||||||
};
|
|
||||||
|
var downstreamAddresses = new List<DownstreamHostAndPort>()
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithDownstreamAddresses(downstreamAddresses).Build();
|
{
|
||||||
|
new DownstreamHostAndPort("asdf.com", 80),
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
new DownstreamHostAndPort("abc.com", 80)
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
};
|
||||||
.Then(x => x.ThenTheServiceProviderIs<ConfigurationServiceProvider>())
|
|
||||||
.Then(x => ThenTheFollowingServicesAreReturned(downstreamAddresses))
|
var reRoute = new DownstreamReRouteBuilder().WithDownstreamAddresses(downstreamAddresses).Build();
|
||||||
.BDDfy();
|
|
||||||
}
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
[Fact]
|
.Then(x => x.ThenTheServiceProviderIs<ConfigurationServiceProvider>())
|
||||||
public void should_return_consul_service_provider()
|
.Then(x => ThenTheFollowingServicesAreReturned(downstreamAddresses))
|
||||||
{
|
.BDDfy();
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
}
|
||||||
.WithServiceName("product")
|
|
||||||
.WithUseServiceDiscovery(true)
|
[Fact]
|
||||||
.Build();
|
public void should_return_consul_service_provider()
|
||||||
|
{
|
||||||
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.Build();
|
.WithServiceName("product")
|
||||||
|
.WithUseServiceDiscovery(true)
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
.Build();
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
|
||||||
.Then(x => x.ThenTheServiceProviderIs<ConsulServiceDiscoveryProvider>())
|
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.BDDfy();
|
.Build();
|
||||||
}
|
|
||||||
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
[Fact]
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
public void should_return_service_fabric_provider()
|
.Then(x => x.ThenTheServiceProviderIs<ConsulServiceDiscoveryProvider>())
|
||||||
{
|
.BDDfy();
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
}
|
||||||
.WithServiceName("product")
|
|
||||||
.WithUseServiceDiscovery(true)
|
[Fact]
|
||||||
.Build();
|
public void should_return_polling_consul_service_provider()
|
||||||
|
{
|
||||||
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithType("ServiceFabric")
|
.WithServiceName("product")
|
||||||
.Build();
|
.WithUseServiceDiscovery(true)
|
||||||
|
.Build();
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.Then(x => x.ThenTheServiceProviderIs<ServiceFabricServiceDiscoveryProvider>())
|
.WithType("PollConsul")
|
||||||
.BDDfy();
|
.WithPollingInterval(100000)
|
||||||
}
|
.Build();
|
||||||
|
|
||||||
[Fact]
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
public void should_return_eureka_provider()
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
{
|
.Then(x => x.ThenTheServiceProviderIs<PollingConsulServiceDiscoveryProvider>())
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
.BDDfy();
|
||||||
.WithServiceName("product")
|
}
|
||||||
.WithUseServiceDiscovery(true)
|
|
||||||
.Build();
|
[Fact]
|
||||||
|
public void should_return_service_fabric_provider()
|
||||||
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
{
|
||||||
.WithType("Eureka")
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.Build();
|
.WithServiceName("product")
|
||||||
|
.WithUseServiceDiscovery(true)
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
.Build();
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
|
||||||
.Then(x => x.ThenTheServiceProviderIs<EurekaServiceDiscoveryProvider>())
|
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.BDDfy();
|
.WithType("ServiceFabric")
|
||||||
}
|
.Build();
|
||||||
|
|
||||||
private void ThenTheFollowingServicesAreReturned(List<DownstreamHostAndPort> downstreamAddresses)
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
{
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
var result = (ConfigurationServiceProvider)_result;
|
.Then(x => x.ThenTheServiceProviderIs<ServiceFabricServiceDiscoveryProvider>())
|
||||||
var services = result.Get().Result;
|
.BDDfy();
|
||||||
|
}
|
||||||
for (int i = 0; i < services.Count; i++)
|
|
||||||
{
|
[Fact]
|
||||||
var service = services[i];
|
public void should_return_eureka_provider()
|
||||||
var downstreamAddress = downstreamAddresses[i];
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
service.HostAndPort.DownstreamHost.ShouldBe(downstreamAddress.Host);
|
.WithServiceName("product")
|
||||||
service.HostAndPort.DownstreamPort.ShouldBe(downstreamAddress.Port);
|
.WithUseServiceDiscovery(true)
|
||||||
}
|
.Build();
|
||||||
}
|
|
||||||
|
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
||||||
private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
.WithType("Eureka")
|
||||||
{
|
.Build();
|
||||||
_serviceConfig = serviceConfig;
|
|
||||||
_reRoute = reRoute;
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
}
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
|
.Then(x => x.ThenTheServiceProviderIs<EurekaServiceDiscoveryProvider>())
|
||||||
private void WhenIGetTheServiceProvider()
|
.BDDfy();
|
||||||
{
|
}
|
||||||
_result = _factory.Get(_serviceConfig, _reRoute);
|
|
||||||
}
|
private void ThenTheFollowingServicesAreReturned(List<DownstreamHostAndPort> downstreamAddresses)
|
||||||
|
{
|
||||||
private void ThenTheServiceProviderIs<T>()
|
var result = (ConfigurationServiceProvider)_result;
|
||||||
{
|
var services = result.Get().Result;
|
||||||
_result.ShouldBeOfType<T>();
|
|
||||||
}
|
for (int i = 0; i < services.Count; i++)
|
||||||
}
|
{
|
||||||
}
|
var service = services[i];
|
||||||
|
var downstreamAddress = downstreamAddresses[i];
|
||||||
|
|
||||||
|
service.HostAndPort.DownstreamHost.ShouldBe(downstreamAddress.Host);
|
||||||
|
service.HostAndPort.DownstreamPort.ShouldBe(downstreamAddress.Port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
||||||
|
{
|
||||||
|
_serviceConfig = serviceConfig;
|
||||||
|
_reRoute = reRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIGetTheServiceProvider()
|
||||||
|
{
|
||||||
|
_result = _factory.Get(_serviceConfig, _reRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheServiceProviderIs<T>()
|
||||||
|
{
|
||||||
|
_result.ShouldBeOfType<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
test/Ocelot.UnitTests/idsrv3test.pfx
Normal file
BIN
test/Ocelot.UnitTests/idsrv3test.pfx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user