mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
commit
03abfca543
@ -1,7 +1,7 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
end_of_line = crlf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
|
|
||||||
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 worth with anything that
|
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.
|
||||||
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.
|
||||||
|
@ -11,6 +11,8 @@ The type of load balancer available are:
|
|||||||
|
|
||||||
NoLoadBalancer - takes the first available service from config or service discovery.
|
NoLoadBalancer - takes the first available service from config or service discovery.
|
||||||
|
|
||||||
|
CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below.
|
||||||
|
|
||||||
You must choose in your configuration which load balancer to use.
|
You must choose in your configuration which load balancer to use.
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
@ -34,7 +36,9 @@ The following shows how to set up multiple downstream services for a ReRoute usi
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"LoadBalancer": "LeastConnection",
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection"
|
||||||
|
},
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,9 +56,56 @@ The following shows how to set up a ReRoute using service discovery then select
|
|||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
"ServiceName": "product",
|
"ServiceName": "product",
|
||||||
"LoadBalancer": "LeastConnection",
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection"
|
||||||
|
},
|
||||||
"UseServiceDiscovery": true
|
"UseServiceDiscovery": true
|
||||||
}
|
}
|
||||||
|
|
||||||
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the
|
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the
|
||||||
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
||||||
|
|
||||||
|
CookieStickySessions
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream
|
||||||
|
servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each
|
||||||
|
time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 <https://github.com/ThreeMammals/Ocelot/issues/322>`_
|
||||||
|
though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have!
|
||||||
|
|
||||||
|
In order to set up CookieStickySessions load balancer you need to do something like the following.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.11",
|
||||||
|
"Port": 5000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "CookieStickySessions",
|
||||||
|
"Key": "ASP.NET_SessionId",
|
||||||
|
"Expiry": 1800000
|
||||||
|
},
|
||||||
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you
|
||||||
|
wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this
|
||||||
|
refreshes on every request which is meant to mimick how sessions work usually.
|
||||||
|
|
||||||
|
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
||||||
|
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
||||||
|
|
||||||
|
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
||||||
|
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
||||||
|
moment but could be changed.
|
22
samples/OcelotEureka/ApiGateway/ApiGateway.csproj
Normal file
22
samples/OcelotEureka/ApiGateway/ApiGateway.csproj
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="ocelot.json;appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||||
|
<PackageReference Include="Ocelot" Version="5.5.7" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
38
samples/OcelotEureka/ApiGateway/Program.cs
Normal file
38
samples/OcelotEureka/ApiGateway/Program.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
namespace ApiGateway
|
||||||
|
{
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
BuildWebHost(args).Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHost BuildWebHost(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseUrls("http://localhost:5000")
|
||||||
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
|
config
|
||||||
|
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||||
|
.AddJsonFile("appsettings.json", true, true)
|
||||||
|
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||||
|
.AddJsonFile("ocelot.json")
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
})
|
||||||
|
.ConfigureServices(s =>
|
||||||
|
{
|
||||||
|
s.AddOcelot();
|
||||||
|
})
|
||||||
|
.Configure(a =>
|
||||||
|
{
|
||||||
|
a.UseOcelot().Wait();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:54060/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ApiGateway": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:54061/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
samples/OcelotEureka/ApiGateway/appsettings.json
Normal file
26
samples/OcelotEureka/ApiGateway/appsettings.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"IncludeScopes": true,
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Trace",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spring": {
|
||||||
|
"application": { "name": "Ocelot-Gateway" },
|
||||||
|
"cloud": {
|
||||||
|
"config": {
|
||||||
|
"uri": "http://localhost:5000",
|
||||||
|
"validateCertificates": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eureka": {
|
||||||
|
"client": {
|
||||||
|
"serviceUrl": "http://localhost:8761/eureka/",
|
||||||
|
"shouldRegisterWithEureka": false,
|
||||||
|
"validateCertificates": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
samples/OcelotEureka/ApiGateway/ocelot.json
Normal file
23
samples/OcelotEureka/ApiGateway/ocelot.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"ReRoutes": [
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/Category",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"UpstreamPathTemplate": "/Category",
|
||||||
|
"UseServiceDiscovery": true,
|
||||||
|
"ServiceName": "ncore-rat",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10000,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
},
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
"RequestIdKey": "OcRequestId",
|
||||||
|
"AdministrationPath": "/administration",
|
||||||
|
"ServiceDiscoveryProvider": { "Type": "Eureka" }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace DownstreamService.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class CategoryController : Controller
|
||||||
|
{
|
||||||
|
// GET api/values
|
||||||
|
[HttpGet]
|
||||||
|
public IEnumerable<string> Get()
|
||||||
|
{
|
||||||
|
return new[] { "category1", "category2" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||||
|
<PackageReference Include="Steeltoe.Discovery.Client" Version="1.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
20
samples/OcelotEureka/DownstreamService/Program.cs
Normal file
20
samples/OcelotEureka/DownstreamService/Program.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
|
namespace DownstreamService
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
BuildWebHost(args).Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHost BuildWebHost(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseUrls($"http://{Environment.MachineName}:5001")
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:53908/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "api/values",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DownstreamService": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "api/values",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:53909/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
samples/OcelotEureka/DownstreamService/Startup.cs
Normal file
44
samples/OcelotEureka/DownstreamService/Startup.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace DownstreamService
|
||||||
|
{
|
||||||
|
using Steeltoe.Discovery.Client;
|
||||||
|
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddDiscoveryClient(Configuration);
|
||||||
|
services.AddMvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseDiscoveryClient();
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"IncludeScopes": false,
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
samples/OcelotEureka/DownstreamService/appsettings.json
Normal file
24
samples/OcelotEureka/DownstreamService/appsettings.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"IncludeScopes": false,
|
||||||
|
"LogLevel": { "Default": "Warning" }
|
||||||
|
},
|
||||||
|
"spring": {
|
||||||
|
"application": { "name": "ncore-rat" },
|
||||||
|
"cloud": {
|
||||||
|
"config": {
|
||||||
|
"uri": "http://localhost:5001",
|
||||||
|
"validate_certificates": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eureka": {
|
||||||
|
"client": {
|
||||||
|
"serviceUrl": "http://localhost:8761/eureka/",
|
||||||
|
"shouldFetchRegistry": false,
|
||||||
|
"validateCertificates": false
|
||||||
|
},
|
||||||
|
"instance": { "port": 5001 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
31
samples/OcelotEureka/OcelotEureka.sln
Normal file
31
samples/OcelotEureka/OcelotEureka.sln
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.27428.2027
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownstreamService", "./DownstreamService/DownstreamService.csproj", "{2982C147-9446-47FE-862E-E689B64CC7E7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiGateway", "./ApiGateway/ApiGateway.csproj", "{006CF27E-5400-43E9-B511-C54EC1B9C546}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{2982C147-9446-47FE-862E-E689B64CC7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2982C147-9446-47FE-862E-E689B64CC7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2982C147-9446-47FE-862E-E689B64CC7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2982C147-9446-47FE-862E-E689B64CC7E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{006CF27E-5400-43E9-B511-C54EC1B9C546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{006CF27E-5400-43E9-B511-C54EC1B9C546}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{006CF27E-5400-43E9-B511-C54EC1B9C546}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{006CF27E-5400-43E9-B511-C54EC1B9C546}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {2C604707-2EA1-4CCF-A89C-22B613052C8D}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
39
samples/OcelotEureka/README.md
Normal file
39
samples/OcelotEureka/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#Example how to use Eureka service discovery
|
||||||
|
|
||||||
|
I created this becasue users are having trouble getting Eureka to work with Ocelot, hopefully this helps.
|
||||||
|
Please review the implementation of the individual servics to understand how everything fits together.
|
||||||
|
|
||||||
|
##Instructions
|
||||||
|
|
||||||
|
1. Get Eureka installed and running...
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/spring-cloud-samples/eureka.git
|
||||||
|
$ cd eureka
|
||||||
|
$ mvnw spring-boot:run
|
||||||
|
```
|
||||||
|
Leave the service running
|
||||||
|
|
||||||
|
2. Get Downstream service running and registered with Eureka
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ./DownstreamService/
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave the service running
|
||||||
|
|
||||||
|
3. Get API Gateway running and collecting services from Eureka
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ./ApiGateway/
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave the service running
|
||||||
|
|
||||||
|
4. Make a http request to http://localhost:5000/category you should get the following response
|
||||||
|
|
||||||
|
```json
|
||||||
|
["category1","category2"]
|
||||||
|
```
|
@ -24,7 +24,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private bool _isCached;
|
private bool _isCached;
|
||||||
private CacheOptions _fileCacheOptions;
|
private CacheOptions _fileCacheOptions;
|
||||||
private string _downstreamScheme;
|
private string _downstreamScheme;
|
||||||
private string _loadBalancer;
|
private LoadBalancerOptions _loadBalancerOptions;
|
||||||
private bool _useQos;
|
private bool _useQos;
|
||||||
private QoSOptions _qosOptions;
|
private QoSOptions _qosOptions;
|
||||||
private HttpHandlerOptions _httpHandlerOptions;
|
private HttpHandlerOptions _httpHandlerOptions;
|
||||||
@ -41,6 +41,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private List<AddHeader> _addHeadersToDownstream;
|
private List<AddHeader> _addHeadersToDownstream;
|
||||||
private List<AddHeader> _addHeadersToUpstream;
|
private List<AddHeader> _addHeadersToUpstream;
|
||||||
private bool _dangerousAcceptAnyServerCertificateValidator;
|
private bool _dangerousAcceptAnyServerCertificateValidator;
|
||||||
|
private string _qosKey;
|
||||||
|
|
||||||
public DownstreamReRouteBuilder()
|
public DownstreamReRouteBuilder()
|
||||||
{
|
{
|
||||||
@ -62,9 +63,9 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithLoadBalancer(string loadBalancer)
|
public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions)
|
||||||
{
|
{
|
||||||
_loadBalancer = loadBalancer;
|
_loadBalancerOptions = loadBalancerOptions;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +171,12 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DownstreamReRouteBuilder WithQosKey(string qosKey)
|
||||||
|
{
|
||||||
|
_qosKey = qosKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions)
|
public DownstreamReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions)
|
||||||
{
|
{
|
||||||
_authenticationOptions = authenticationOptions;
|
_authenticationOptions = authenticationOptions;
|
||||||
@ -266,7 +273,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_requestIdHeaderKey,
|
_requestIdHeaderKey,
|
||||||
_isCached,
|
_isCached,
|
||||||
_fileCacheOptions,
|
_fileCacheOptions,
|
||||||
_loadBalancer,
|
_loadBalancerOptions,
|
||||||
_rateLimitOptions,
|
_rateLimitOptions,
|
||||||
_routeClaimRequirement,
|
_routeClaimRequirement,
|
||||||
_claimToQueries,
|
_claimToQueries,
|
||||||
@ -280,7 +287,8 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_delegatingHandlers,
|
_delegatingHandlers,
|
||||||
_addHeadersToDownstream,
|
_addHeadersToDownstream,
|
||||||
_addHeadersToUpstream,
|
_addHeadersToUpstream,
|
||||||
_dangerousAcceptAnyServerCertificateValidator);
|
_dangerousAcceptAnyServerCertificateValidator,
|
||||||
|
_qosKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ using Ocelot.Responses;
|
|||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
|
using LoadBalancer.LoadBalancers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register as singleton
|
/// Register as singleton
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -158,6 +160,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var reRouteKey = CreateReRouteKey(fileReRoute);
|
var reRouteKey = CreateReRouteKey(fileReRoute);
|
||||||
|
|
||||||
|
var qosKey = CreateQosKey(fileReRoute);
|
||||||
|
|
||||||
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
|
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
|
||||||
@ -180,6 +184,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
|
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
|
||||||
|
|
||||||
|
var lbOptions = CreateLoadBalancerOptions(fileReRoute);
|
||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithKey(fileReRoute.Key)
|
.WithKey(fileReRoute.Key)
|
||||||
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
||||||
@ -197,9 +203,10 @@ namespace Ocelot.Configuration.Creator
|
|||||||
.WithIsCached(fileReRouteOptions.IsCached)
|
.WithIsCached(fileReRouteOptions.IsCached)
|
||||||
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
|
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
|
||||||
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
|
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
|
||||||
.WithLoadBalancer(fileReRoute.LoadBalancer)
|
.WithLoadBalancerOptions(lbOptions)
|
||||||
.WithDownstreamAddresses(downstreamAddresses)
|
.WithDownstreamAddresses(downstreamAddresses)
|
||||||
.WithReRouteKey(reRouteKey)
|
.WithReRouteKey(reRouteKey)
|
||||||
|
.WithQosKey(qosKey)
|
||||||
.WithIsQos(fileReRouteOptions.IsQos)
|
.WithIsQos(fileReRouteOptions.IsQos)
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
|
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
|
||||||
@ -219,7 +226,22 @@ namespace Ocelot.Configuration.Creator
|
|||||||
return reRoute;
|
return reRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LoadBalancerOptions CreateLoadBalancerOptions(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return new LoadBalancerOptions(fileReRoute.LoadBalancerOptions.Type, fileReRoute.LoadBalancerOptions.Key, fileReRoute.LoadBalancerOptions.Expiry);
|
||||||
|
}
|
||||||
|
|
||||||
private string CreateReRouteKey(FileReRoute fileReRoute)
|
private string CreateReRouteKey(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Type) && !string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Key) && fileReRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions))
|
||||||
|
{
|
||||||
|
return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateQosKey(fileReRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateQosKey(FileReRoute fileReRoute)
|
||||||
{
|
{
|
||||||
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
|
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
|
||||||
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}";
|
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}";
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Ocelot.Configuration.Creator;
|
|
||||||
using Ocelot.Values;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Creator;
|
||||||
|
using Values;
|
||||||
|
|
||||||
public class DownstreamReRoute
|
public class DownstreamReRoute
|
||||||
{
|
{
|
||||||
public DownstreamReRoute(
|
public DownstreamReRoute(
|
||||||
@ -22,7 +22,7 @@ namespace Ocelot.Configuration
|
|||||||
string requestIdKey,
|
string requestIdKey,
|
||||||
bool isCached,
|
bool isCached,
|
||||||
CacheOptions cacheOptions,
|
CacheOptions cacheOptions,
|
||||||
string loadBalancer,
|
LoadBalancerOptions loadBalancerOptions,
|
||||||
RateLimitOptions rateLimitOptions,
|
RateLimitOptions rateLimitOptions,
|
||||||
Dictionary<string, string> routeClaimsRequirement,
|
Dictionary<string, string> routeClaimsRequirement,
|
||||||
List<ClaimToThing> claimsToQueries,
|
List<ClaimToThing> claimsToQueries,
|
||||||
@ -32,11 +32,12 @@ namespace Ocelot.Configuration
|
|||||||
bool isAuthorised,
|
bool isAuthorised,
|
||||||
AuthenticationOptions authenticationOptions,
|
AuthenticationOptions authenticationOptions,
|
||||||
PathTemplate downstreamPathTemplate,
|
PathTemplate downstreamPathTemplate,
|
||||||
string reRouteKey,
|
string loadBalancerKey,
|
||||||
List<string> delegatingHandlers,
|
List<string> delegatingHandlers,
|
||||||
List<AddHeader> addHeadersToDownstream,
|
List<AddHeader> addHeadersToDownstream,
|
||||||
List<AddHeader> addHeadersToUpstream,
|
List<AddHeader> addHeadersToUpstream,
|
||||||
bool dangerousAcceptAnyServerCertificateValidator)
|
bool dangerousAcceptAnyServerCertificateValidator,
|
||||||
|
string qosKey)
|
||||||
{
|
{
|
||||||
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
|
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
|
||||||
AddHeadersToDownstream = addHeadersToDownstream;
|
AddHeadersToDownstream = addHeadersToDownstream;
|
||||||
@ -56,7 +57,7 @@ namespace Ocelot.Configuration
|
|||||||
RequestIdKey = requestIdKey;
|
RequestIdKey = requestIdKey;
|
||||||
IsCached = isCached;
|
IsCached = isCached;
|
||||||
CacheOptions = cacheOptions;
|
CacheOptions = cacheOptions;
|
||||||
LoadBalancer = loadBalancer;
|
LoadBalancerOptions = loadBalancerOptions;
|
||||||
RateLimitOptions = rateLimitOptions;
|
RateLimitOptions = rateLimitOptions;
|
||||||
RouteClaimsRequirement = routeClaimsRequirement;
|
RouteClaimsRequirement = routeClaimsRequirement;
|
||||||
ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
|
ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
|
||||||
@ -66,39 +67,41 @@ namespace Ocelot.Configuration
|
|||||||
IsAuthorised = isAuthorised;
|
IsAuthorised = isAuthorised;
|
||||||
AuthenticationOptions = authenticationOptions;
|
AuthenticationOptions = authenticationOptions;
|
||||||
DownstreamPathTemplate = downstreamPathTemplate;
|
DownstreamPathTemplate = downstreamPathTemplate;
|
||||||
ReRouteKey = reRouteKey;
|
LoadBalancerKey = loadBalancerKey;
|
||||||
AddHeadersToUpstream = addHeadersToUpstream;
|
AddHeadersToUpstream = addHeadersToUpstream;
|
||||||
|
QosKey = qosKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Key { get; private set; }
|
public string QosKey { get; }
|
||||||
public PathTemplate UpstreamPathTemplate { get;private set; }
|
public string Key { get; }
|
||||||
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;}
|
public PathTemplate UpstreamPathTemplate { get; }
|
||||||
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; private set; }
|
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace { get; }
|
||||||
public List<DownstreamHostAndPort> DownstreamAddresses { get; private set; }
|
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; }
|
||||||
public string ServiceName { get; private set; }
|
public List<DownstreamHostAndPort> DownstreamAddresses { get; }
|
||||||
public HttpHandlerOptions HttpHandlerOptions { get; private set; }
|
public string ServiceName { get; }
|
||||||
public bool UseServiceDiscovery { get; private set; }
|
public HttpHandlerOptions HttpHandlerOptions { get; }
|
||||||
public bool EnableEndpointEndpointRateLimiting { get; private set; }
|
public bool UseServiceDiscovery { get; }
|
||||||
public bool IsQos { get; private set; }
|
public bool EnableEndpointEndpointRateLimiting { get; }
|
||||||
public QoSOptions QosOptionsOptions { get; private set; }
|
public bool IsQos { get; }
|
||||||
public string DownstreamScheme { get; private set; }
|
public QoSOptions QosOptionsOptions { get; }
|
||||||
public string RequestIdKey { get; private set; }
|
public string DownstreamScheme { get; }
|
||||||
public bool IsCached { get; private set; }
|
public string RequestIdKey { get; }
|
||||||
public CacheOptions CacheOptions { get; private set; }
|
public bool IsCached { get; }
|
||||||
public string LoadBalancer { get; private set; }
|
public CacheOptions CacheOptions { get; }
|
||||||
public RateLimitOptions RateLimitOptions { get; private set; }
|
public LoadBalancerOptions LoadBalancerOptions { get; }
|
||||||
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
|
public RateLimitOptions RateLimitOptions { get; }
|
||||||
public List<ClaimToThing> ClaimsToQueries { get; private set; }
|
public Dictionary<string, string> RouteClaimsRequirement { get; }
|
||||||
public List<ClaimToThing> ClaimsToHeaders { get; private set; }
|
public List<ClaimToThing> ClaimsToQueries { get; }
|
||||||
public List<ClaimToThing> ClaimsToClaims { get; private set; }
|
public List<ClaimToThing> ClaimsToHeaders { get; }
|
||||||
public bool IsAuthenticated { get; private set; }
|
public List<ClaimToThing> ClaimsToClaims { get; }
|
||||||
public bool IsAuthorised { get; private set; }
|
public bool IsAuthenticated { get; }
|
||||||
public AuthenticationOptions AuthenticationOptions { get; private set; }
|
public bool IsAuthorised { get; }
|
||||||
public PathTemplate DownstreamPathTemplate { get; private set; }
|
public AuthenticationOptions AuthenticationOptions { get; }
|
||||||
public string ReRouteKey { get; private set; }
|
public PathTemplate DownstreamPathTemplate { get; }
|
||||||
public List<string> DelegatingHandlers {get;private set;}
|
public string LoadBalancerKey { get; }
|
||||||
public List<AddHeader> AddHeadersToDownstream {get;private set;}
|
public List<string> DelegatingHandlers { get; }
|
||||||
public List<AddHeader> AddHeadersToUpstream { get; private set; }
|
public List<AddHeader> AddHeadersToDownstream { get; }
|
||||||
public bool DangerousAcceptAnyServerCertificateValidator { get; private set; }
|
public List<AddHeader> AddHeadersToUpstream { get; }
|
||||||
|
public bool DangerousAcceptAnyServerCertificateValidator { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
src/Ocelot/Configuration/File/FileLoadBalancerOptions.cs
Normal file
9
src/Ocelot/Configuration/File/FileLoadBalancerOptions.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public int Expiry { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ namespace Ocelot.Configuration.File
|
|||||||
UpstreamHeaderTransform = new Dictionary<string, string>();
|
UpstreamHeaderTransform = new Dictionary<string, string>();
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>();
|
DownstreamHostAndPorts = new List<FileHostAndPort>();
|
||||||
DelegatingHandlers = new List<string>();
|
DelegatingHandlers = new List<string>();
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions();
|
||||||
Priority = 1;
|
Priority = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ namespace Ocelot.Configuration.File
|
|||||||
public string ServiceName { get; set; }
|
public string ServiceName { get; set; }
|
||||||
public string DownstreamScheme {get;set;}
|
public string DownstreamScheme {get;set;}
|
||||||
public FileQoSOptions QoSOptions { get; set; }
|
public FileQoSOptions QoSOptions { get; set; }
|
||||||
public string LoadBalancer { get;set; }
|
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
|
||||||
public FileRateLimitRule RateLimitOptions { get; set; }
|
public FileRateLimitRule RateLimitOptions { get; set; }
|
||||||
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
||||||
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
||||||
|
18
src/Ocelot/Configuration/LoadBalancerOptions.cs
Normal file
18
src/Ocelot/Configuration/LoadBalancerOptions.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class LoadBalancerOptions
|
||||||
|
{
|
||||||
|
public LoadBalancerOptions(string type, string key, int expiryInMs)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Key = key;
|
||||||
|
ExpiryInMs = expiryInMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
public int ExpiryInMs { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Responses;
|
||||||
|
using Values;
|
||||||
|
|
||||||
|
public class CookieStickySessions : ILoadBalancer, IDisposable
|
||||||
|
{
|
||||||
|
private readonly int _expiryInMs;
|
||||||
|
private readonly string _key;
|
||||||
|
private readonly ILoadBalancer _loadBalancer;
|
||||||
|
private readonly ConcurrentDictionary<string, StickySession> _stored;
|
||||||
|
private readonly Timer _timer;
|
||||||
|
private bool _expiring;
|
||||||
|
|
||||||
|
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int expiryInMs)
|
||||||
|
{
|
||||||
|
_key = key;
|
||||||
|
_expiryInMs = expiryInMs;
|
||||||
|
_loadBalancer = loadBalancer;
|
||||||
|
_stored = new ConcurrentDictionary<string, StickySession>();
|
||||||
|
_timer = new Timer(x =>
|
||||||
|
{
|
||||||
|
if (_expiring)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_expiring = true;
|
||||||
|
|
||||||
|
Expire();
|
||||||
|
|
||||||
|
_expiring = false;
|
||||||
|
}, null, 0, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||||
|
{
|
||||||
|
var value = context.HttpContext.Request.Cookies[_key];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value) && _stored.ContainsKey(value))
|
||||||
|
{
|
||||||
|
var cached = _stored[value];
|
||||||
|
|
||||||
|
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
|
||||||
|
|
||||||
|
_stored[value] = updated;
|
||||||
|
|
||||||
|
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = await _loadBalancer.Lease(context);
|
||||||
|
|
||||||
|
if (next.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ServiceHostAndPort>(next.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value) && !_stored.ContainsKey(value))
|
||||||
|
{
|
||||||
|
_stored[value] = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<ServiceHostAndPort>(next.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Expire()
|
||||||
|
{
|
||||||
|
var expired = _stored.Where(x => x.Value.Expiry < DateTime.UtcNow);
|
||||||
|
|
||||||
|
foreach (var expire in expired)
|
||||||
|
{
|
||||||
|
_stored.Remove(expire.Key, out _);
|
||||||
|
_loadBalancer.Release(expire.Value.HostAndPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
{
|
{
|
||||||
public interface ILoadBalancer
|
public interface ILoadBalancer
|
||||||
{
|
{
|
||||||
Task<Response<ServiceHostAndPort>> Lease();
|
Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context);
|
||||||
void Release(ServiceHostAndPort hostAndPort);
|
void Release(ServiceHostAndPort hostAndPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
_leases = new List<Lease>();
|
_leases = new List<Lease>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease()
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
{
|
{
|
||||||
var services = await _services.Invoke();
|
var services = await _services.Invoke();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
public class LoadBalancerFactory : ILoadBalancerFactory
|
public class LoadBalancerFactory : ILoadBalancerFactory
|
||||||
{
|
{
|
||||||
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
|
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
|
||||||
|
|
||||||
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
|
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
|
||||||
{
|
{
|
||||||
_serviceProviderFactory = serviceProviderFactory;
|
_serviceProviderFactory = serviceProviderFactory;
|
||||||
@ -16,12 +17,15 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
{
|
{
|
||||||
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
|
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
|
||||||
|
|
||||||
switch (reRoute.LoadBalancer)
|
switch (reRoute.LoadBalancerOptions?.Type)
|
||||||
{
|
{
|
||||||
case "RoundRobin":
|
case nameof(RoundRobin):
|
||||||
return new RoundRobin(async () => await serviceProvider.Get());
|
return new RoundRobin(async () => await serviceProvider.Get());
|
||||||
case "LeastConnection":
|
case nameof(LeastConnection):
|
||||||
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
||||||
|
case nameof(CookieStickySessions):
|
||||||
|
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
|
||||||
|
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs);
|
||||||
default:
|
default:
|
||||||
return new NoLoadBalancer(await serviceProvider.Get());
|
return new NoLoadBalancer(await serviceProvider.Get());
|
||||||
}
|
}
|
||||||
|
@ -22,28 +22,28 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if(_loadBalancers.TryGetValue(reRoute.ReRouteKey, out var loadBalancer))
|
if(_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer))
|
||||||
{
|
{
|
||||||
loadBalancer = _loadBalancers[reRoute.ReRouteKey];
|
loadBalancer = _loadBalancers[reRoute.LoadBalancerKey];
|
||||||
|
|
||||||
if(reRoute.LoadBalancer != loadBalancer.GetType().Name)
|
if(reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name)
|
||||||
{
|
{
|
||||||
loadBalancer = await _factory.Get(reRoute, config);
|
loadBalancer = await _factory.Get(reRoute, config);
|
||||||
AddLoadBalancer(reRoute.ReRouteKey, loadBalancer);
|
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBalancer = await _factory.Get(reRoute, config);
|
loadBalancer = await _factory.Get(reRoute, config);
|
||||||
AddLoadBalancer(reRoute.ReRouteKey, loadBalancer);
|
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||||
{
|
{
|
||||||
new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.ReRouteKey} exception is {ex}")
|
new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.LoadBalancerKey} exception is {ex}")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
@ -15,9 +16,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
_services = services;
|
_services = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease()
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
{
|
{
|
||||||
//todo no point spinning a task up here, also first or default could be null..
|
//todo no point spinning a task up here, also first or default could be null..
|
||||||
|
if (_services == null || _services.Count == 0)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError("There were no services in NoLoadBalancer"));
|
||||||
|
}
|
||||||
|
|
||||||
var service = await Task.FromResult(_services.FirstOrDefault());
|
var service = await Task.FromResult(_services.FirstOrDefault());
|
||||||
return new OkResponse<ServiceHostAndPort>(service.HostAndPort);
|
return new OkResponse<ServiceHostAndPort>(service.HostAndPort);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using System;
|
using System;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
@ -17,7 +18,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
_services = services;
|
_services = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease()
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
{
|
{
|
||||||
var services = await _services.Invoke();
|
var services = await _services.Invoke();
|
||||||
if (_last >= services.Count)
|
if (_last >= services.Count)
|
||||||
|
18
src/Ocelot/LoadBalancer/LoadBalancers/StickySession.cs
Normal file
18
src/Ocelot/LoadBalancer/LoadBalancers/StickySession.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class StickySession
|
||||||
|
{
|
||||||
|
public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry)
|
||||||
|
{
|
||||||
|
HostAndPort = hostAndPort;
|
||||||
|
Expiry = expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceHostAndPort HostAndPort { get; }
|
||||||
|
|
||||||
|
public DateTime Expiry { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ namespace Ocelot.LoadBalancer.Middleware
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostAndPort = await loadBalancer.Data.Lease();
|
var hostAndPort = await loadBalancer.Data.Lease(context);
|
||||||
if(hostAndPort.IsError)
|
if(hostAndPort.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
|
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
|
||||||
|
@ -97,7 +97,11 @@ namespace Ocelot.Requester
|
|||||||
|
|
||||||
private string GetCacheKey(DownstreamContext request)
|
private string GetCacheKey(DownstreamContext request)
|
||||||
{
|
{
|
||||||
return request.DownstreamRequest.OriginalString;
|
var cacheKey = $"{request.DownstreamRequest.Method}:{request.DownstreamRequest.OriginalString}";
|
||||||
|
|
||||||
|
this._logger.LogDebug($"Cache key for request is {cacheKey}");
|
||||||
|
|
||||||
|
return cacheKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,26 +21,26 @@ namespace Ocelot.Requester.QoS
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_qoSProviders.TryGetValue(reRoute.ReRouteKey, out var qosProvider))
|
if (_qoSProviders.TryGetValue(reRoute.QosKey, out var qosProvider))
|
||||||
{
|
{
|
||||||
if (reRoute.IsQos && qosProvider.CircuitBreaker == null)
|
if (reRoute.IsQos && qosProvider.CircuitBreaker == null)
|
||||||
{
|
{
|
||||||
qosProvider = _qoSProviderFactory.Get(reRoute);
|
qosProvider = _qoSProviderFactory.Get(reRoute);
|
||||||
Add(reRoute.ReRouteKey, qosProvider);
|
Add(reRoute.QosKey, qosProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.ReRouteKey]);
|
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.QosKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
qosProvider = _qoSProviderFactory.Get(reRoute);
|
qosProvider = _qoSProviderFactory.Get(reRoute);
|
||||||
Add(reRoute.ReRouteKey, qosProvider);
|
Add(reRoute.QosKey, qosProvider);
|
||||||
return new OkResponse<IQoSProvider>(qosProvider);
|
return new OkResponse<IQoSProvider>(qosProvider);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
|
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
|
||||||
{
|
{
|
||||||
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.ReRouteKey}, exception was {ex}")
|
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.QosKey}, exception was {ex}")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,11 @@ namespace Ocelot.Responder
|
|||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errors.Any(e => e.Code == OcelotErrorCode.UnableToCompleteRequestError))
|
||||||
|
{
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
|
@ -304,7 +304,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/vacancy/",
|
UpstreamPathTemplate = "/vacancy/",
|
||||||
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
||||||
ServiceName = "botCore",
|
ServiceName = "botCore",
|
||||||
LoadBalancer = "LeastConnection"
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
|
||||||
},
|
},
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
@ -321,7 +321,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/vacancy/{vacancyId}",
|
UpstreamPathTemplate = "/vacancy/{vacancyId}",
|
||||||
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
||||||
ServiceName = "botCore",
|
ServiceName = "botCore",
|
||||||
LoadBalancer = "LeastConnection"
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -802,7 +802,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/vacancy/",
|
UpstreamPathTemplate = "/vacancy/",
|
||||||
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
||||||
ServiceName = "botCore",
|
ServiceName = "botCore",
|
||||||
LoadBalancer = "LeastConnection"
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
|
||||||
},
|
},
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
@ -819,7 +819,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/vacancy/{vacancyId}",
|
UpstreamPathTemplate = "/vacancy/{vacancyId}",
|
||||||
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
UpstreamHttpMethod = new List<string> { "Options", "Put", "Get", "Post", "Delete" },
|
||||||
ServiceName = "botCore",
|
ServiceName = "botCore",
|
||||||
LoadBalancer = "LeastConnection"
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -127,7 +127,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -183,7 +183,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/home",
|
UpstreamPathTemplate = "/home",
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -239,7 +239,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/home",
|
UpstreamPathTemplate = "/home",
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -308,7 +308,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -92,7 +92,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.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.NotFound))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ using Ocelot.Middleware.Multiplexer;
|
|||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
|
||||||
|
|
||||||
public class Steps : IDisposable
|
public class Steps : IDisposable
|
||||||
{
|
{
|
||||||
private TestServer _ocelotServer;
|
private TestServer _ocelotServer;
|
||||||
@ -671,6 +674,14 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
|
||||||
|
{
|
||||||
|
var request = _ocelotServer.CreateRequest(url);
|
||||||
|
request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); });
|
||||||
|
var response = request.GetAsync().Result;
|
||||||
|
_response = response;
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenIAddAHeader(string key, string value)
|
public void GivenIAddAHeader(string key, string value)
|
||||||
{
|
{
|
||||||
_ocelotClient.DefaultRequestHeaders.Add(key, value);
|
_ocelotClient.DefaultRequestHeaders.Add(key, value);
|
||||||
@ -690,6 +701,30 @@ namespace Ocelot.AcceptanceTests
|
|||||||
Task.WaitAll(tasks);
|
Task.WaitAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times, string cookie, string value)
|
||||||
|
{
|
||||||
|
var tasks = new Task[times];
|
||||||
|
|
||||||
|
for (int i = 0; i < times; i++)
|
||||||
|
{
|
||||||
|
var urlCopy = url;
|
||||||
|
tasks[i] = GetForServiceDiscoveryTest(urlCopy, cookie, value);
|
||||||
|
Thread.Sleep(_random.Next(40, 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetForServiceDiscoveryTest(string url, string cookie, string value)
|
||||||
|
{
|
||||||
|
var request = _ocelotServer.CreateRequest(url);
|
||||||
|
request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); });
|
||||||
|
var response = await request.GetAsync();
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
int count = int.Parse(content);
|
||||||
|
count.ShouldBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task GetForServiceDiscoveryTest(string url)
|
private async Task GetForServiceDiscoveryTest(string url)
|
||||||
{
|
{
|
||||||
var response = await _ocelotClient.GetAsync(url);
|
var response = await _ocelotClient.GetAsync(url);
|
||||||
|
321
test/Ocelot.AcceptanceTests/StickySessionsTests.cs
Normal file
321
test/Ocelot.AcceptanceTests/StickySessionsTests.cs
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class StickySessionsTests : IDisposable
|
||||||
|
{
|
||||||
|
private IWebHost _builderOne;
|
||||||
|
private IWebHost _builderTwo;
|
||||||
|
private readonly Steps _steps;
|
||||||
|
private int _counterOne;
|
||||||
|
private int _counterTwo;
|
||||||
|
private static readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
public StickySessionsTests()
|
||||||
|
{
|
||||||
|
_steps = new Steps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_same_downstream_host()
|
||||||
|
{
|
||||||
|
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 = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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("/", 10, "sessionid", "123"))
|
||||||
|
.Then(x => x.ThenTheFirstServiceIsCalled(10))
|
||||||
|
.Then(x => x.ThenTheSecondServiceIsCalled(0))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_different_downstream_host_for_different_re_route()
|
||||||
|
{
|
||||||
|
var downstreamPortOne = 52881;
|
||||||
|
var downstreamPortTwo = 52892;
|
||||||
|
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 = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/test",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "bestid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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.WhenIGetUrlOnTheApiGateway("/", "sessionid", "123"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/test", "bestid", "123"))
|
||||||
|
.Then(x => x.ThenTheFirstServiceIsCalled(1))
|
||||||
|
.Then(x => x.ThenTheSecondServiceIsCalled(1))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_same_downstream_host_for_different_re_route()
|
||||||
|
{
|
||||||
|
var downstreamPortOne = 53881;
|
||||||
|
var downstreamPortTwo = 53892;
|
||||||
|
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 = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/test",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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.WhenIGetUrlOnTheApiGateway("/", "sessionid", "123"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/test", "sessionid", "123"))
|
||||||
|
.Then(x => x.ThenTheFirstServiceIsCalled(2))
|
||||||
|
.Then(x => x.ThenTheSecondServiceIsCalled(0))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFirstServiceIsCalled(int expected)
|
||||||
|
{
|
||||||
|
_counterOne.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheSecondServiceIsCalled(int expected)
|
||||||
|
{
|
||||||
|
_counterTwo.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenProductServiceOneIsRunning(string url, int statusCode)
|
||||||
|
{
|
||||||
|
_builderOne = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = string.Empty;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_counterOne++;
|
||||||
|
response = _counterOne.ToString();
|
||||||
|
}
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(response);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(exception.StackTrace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builderOne.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
|
||||||
|
{
|
||||||
|
_builderTwo = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = string.Empty;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_counterTwo++;
|
||||||
|
response = _counterTwo.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(response);
|
||||||
|
}
|
||||||
|
catch (System.Exception exception)
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(exception.StackTrace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builderTwo.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builderOne?.Dispose();
|
||||||
|
_builderTwo?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -101,7 +101,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
Port = secondDownstreamPort
|
Port = secondDownstreamPort
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LoadBalancer = "RoundRobin"
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -159,7 +159,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
DownstreamPathTemplate = "/ws",
|
DownstreamPathTemplate = "/ws",
|
||||||
DownstreamScheme = "ws",
|
DownstreamScheme = "ws",
|
||||||
LoadBalancer = "RoundRobin",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
UseServiceDiscovery = true
|
UseServiceDiscovery = true
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,9 @@ namespace Ocelot.Benchmarks
|
|||||||
// BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0]
|
// BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0]
|
||||||
// Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores
|
// Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores
|
||||||
//.NET Core SDK = 2.1.4
|
//.NET Core SDK = 2.1.4
|
||||||
|
|
||||||
// [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
// [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||||
// DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
// DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||||
|
|
||||||
// Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated |
|
// Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated |
|
||||||
// --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:|
|
// --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:|
|
||||||
// Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |
|
// Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |
|
||||||
@ -121,7 +121,7 @@ namespace Ocelot.Benchmarks
|
|||||||
|
|
||||||
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
||||||
{
|
{
|
||||||
var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json");;
|
var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json");
|
||||||
|
|
||||||
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
|
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
|
||||||
|
|
||||||
|
@ -43,8 +43,6 @@ namespace Ocelot.Benchmarks
|
|||||||
// .NET Core SDK=2.1.4
|
// .NET Core SDK=2.1.4
|
||||||
// [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
// [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||||
// DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
// DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||||
|
|
||||||
|
|
||||||
// Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s |
|
// Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s |
|
||||||
// ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:|
|
// ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:|
|
||||||
// Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 |
|
// Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 |
|
||||||
|
@ -32,7 +32,8 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
private Mock<IConsulClient> _client;
|
private Mock<IConsulClient> _client;
|
||||||
private Mock<IKVEndpoint> _kvEndpoint;
|
private Mock<IKVEndpoint> _kvEndpoint;
|
||||||
private FileConfiguration _fileConfiguration;
|
private FileConfiguration _fileConfiguration;
|
||||||
private Response _result;
|
private Response _setResult;
|
||||||
|
private Response<FileConfiguration> _getResult;
|
||||||
|
|
||||||
public ConsulFileConfigurationRepositoryTests()
|
public ConsulFileConfigurationRepositoryTests()
|
||||||
{
|
{
|
||||||
@ -70,6 +71,56 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_get_config()
|
||||||
|
{
|
||||||
|
var config = FakeFileConfiguration();
|
||||||
|
|
||||||
|
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||||
|
.And(_ => GivenFetchFromConsulSucceeds())
|
||||||
|
.When(_ => WhenIGetTheConfiguration())
|
||||||
|
.Then(_ => ThenTheConfigurationIs(config))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_get_null_config()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenFetchFromConsulReturnsNull())
|
||||||
|
.When(_ => WhenIGetTheConfiguration())
|
||||||
|
.Then(_ => ThenTheConfigurationIsNull())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_get_config_from_cache()
|
||||||
|
{
|
||||||
|
var config = FakeFileConfiguration();
|
||||||
|
|
||||||
|
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||||
|
.And(_ => GivenFetchFromCacheSucceeds())
|
||||||
|
.When(_ => WhenIGetTheConfiguration())
|
||||||
|
.Then(_ => ThenTheConfigurationIs(config))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheConfigurationIsNull()
|
||||||
|
{
|
||||||
|
_getResult.Data.ShouldBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheConfigurationIs(FileConfiguration config)
|
||||||
|
{
|
||||||
|
var expected = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||||
|
var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented);
|
||||||
|
result.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenIGetTheConfiguration()
|
||||||
|
{
|
||||||
|
_getResult = await _repo.Get();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenWritingToConsulSucceeds()
|
private void GivenWritingToConsulSucceeds()
|
||||||
{
|
{
|
||||||
var response = new WriteResult<bool>();
|
var response = new WriteResult<bool>();
|
||||||
@ -79,6 +130,37 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.Setup(x => x.Put(It.IsAny<KVPair>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
|
.Setup(x => x.Put(It.IsAny<KVPair>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GivenFetchFromCacheSucceeds()
|
||||||
|
{
|
||||||
|
_cache.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Returns(_fileConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenFetchFromConsulReturnsNull()
|
||||||
|
{
|
||||||
|
QueryResult<KVPair> result = new QueryResult<KVPair>();
|
||||||
|
|
||||||
|
_kvEndpoint
|
||||||
|
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenFetchFromConsulSucceeds()
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented);
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
|
||||||
|
var kvp = new KVPair("OcelotConfiguration");
|
||||||
|
kvp.Value = bytes;
|
||||||
|
|
||||||
|
var query = new QueryResult<KVPair>();
|
||||||
|
query.Response = kvp;
|
||||||
|
|
||||||
|
_kvEndpoint
|
||||||
|
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(query);
|
||||||
|
}
|
||||||
|
|
||||||
private void ThenTheConfigurationIsStoredAs(FileConfiguration config)
|
private void ThenTheConfigurationIsStoredAs(FileConfiguration config)
|
||||||
{
|
{
|
||||||
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||||
@ -91,7 +173,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private async Task WhenISetTheConfiguration()
|
private async Task WhenISetTheConfiguration()
|
||||||
{
|
{
|
||||||
_result = await _repo.Set(_fileConfiguration);
|
_setResult = await _repo.Set(_fileConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenIHaveAConfiguration(FileConfiguration config)
|
private void GivenIHaveAConfiguration(FileConfiguration config)
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
using Ocelot.UnitTests.TestData;
|
using Ocelot.UnitTests.TestData;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
public class FileConfigurationCreatorTests
|
public class FileInternalConfigurationCreatorTests
|
||||||
{
|
{
|
||||||
private readonly Mock<IConfigurationValidator> _validator;
|
private readonly Mock<IConfigurationValidator> _validator;
|
||||||
private Response<IInternalConfiguration> _config;
|
private Response<IInternalConfiguration> _config;
|
||||||
@ -39,7 +39,7 @@
|
|||||||
private readonly Mock<IHeaderFindAndReplaceCreator> _headerFindAndReplaceCreator;
|
private readonly Mock<IHeaderFindAndReplaceCreator> _headerFindAndReplaceCreator;
|
||||||
private readonly Mock<IDownstreamAddressesCreator> _downstreamAddressesCreator;
|
private readonly Mock<IDownstreamAddressesCreator> _downstreamAddressesCreator;
|
||||||
|
|
||||||
public FileConfigurationCreatorTests()
|
public FileInternalConfigurationCreatorTests()
|
||||||
{
|
{
|
||||||
_logger = new Mock<IOcelotLoggerFactory>();
|
_logger = new Mock<IOcelotLoggerFactory>();
|
||||||
_validator = new Mock<IConfigurationValidator>();
|
_validator = new Mock<IConfigurationValidator>();
|
||||||
@ -75,6 +75,61 @@
|
|||||||
_downstreamAddressesCreator.Object);
|
_downstreamAddressesCreator.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_set_up_sticky_sessions_config()
|
||||||
|
{
|
||||||
|
var reRouteOptions = new ReRouteOptionsBuilder()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamAddresses(new List<DownstreamHostAndPort>() { new DownstreamHostAndPort("127.0.0.1", 80) })
|
||||||
|
.WithDownstreamPathTemplate("/products/{productId}")
|
||||||
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
|
.WithReRouteKey("CookieStickySessions:sessionid")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Expiry = 10,
|
||||||
|
Key = "sessionid",
|
||||||
|
Type = "CookieStickySessions"
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/api/products/{productId}",
|
||||||
|
DownstreamPathTemplate = "/products/{productId}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheDownstreamAddresses())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
|
.When(x => x.WhenICreateTheConfig())
|
||||||
|
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||||
|
{
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
|
.Build()
|
||||||
|
}))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_set_up_aggregate_re_route()
|
public void should_set_up_aggregate_re_route()
|
||||||
{
|
{
|
||||||
@ -144,6 +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)})
|
||||||
|
.WithReRouteKey("/laura|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var lauraReRoute = new ReRouteBuilder()
|
var lauraReRoute = new ReRouteBuilder()
|
||||||
@ -162,6 +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) })
|
||||||
|
.WithReRouteKey("/tom|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var tomReRoute = new ReRouteBuilder()
|
var tomReRoute = new ReRouteBuilder()
|
||||||
@ -353,6 +410,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"})
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -404,6 +462,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithDelegatingHandlers(handlers)
|
.WithDelegatingHandlers(handlers)
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -448,6 +507,7 @@
|
|||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithUseServiceDiscovery(true)
|
.WithUseServiceDiscovery(true)
|
||||||
.WithServiceName("ProductService")
|
.WithServiceName("ProductService")
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -498,6 +558,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithUseServiceDiscovery(false)
|
.WithUseServiceDiscovery(false)
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -540,6 +601,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))
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -584,6 +646,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithRequestIdKey("blahhhh")
|
.WithRequestIdKey("blahhhh")
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -678,6 +741,7 @@
|
|||||||
{
|
{
|
||||||
new ClaimToThing("CustomerId", "CustomerId", "", 0),
|
new ClaimToThing("CustomerId", "CustomerId", "", 0),
|
||||||
})
|
})
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var expected = new List<ReRoute>
|
var expected = new List<ReRoute>
|
||||||
@ -720,6 +784,7 @@
|
|||||||
.WithUpstreamPathTemplate("/api/products/{productId}")
|
.WithUpstreamPathTemplate("/api/products/{productId}")
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.WithAuthenticationOptions(authenticationOptions)
|
.WithAuthenticationOptions(authenticationOptions)
|
||||||
|
.WithReRouteKey("/api/products/{productId}|Get")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var expected = new List<ReRoute>
|
var expected = new List<ReRoute>
|
||||||
@ -817,6 +882,7 @@
|
|||||||
result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count);
|
result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count);
|
||||||
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
|
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
|
||||||
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
|
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
|
||||||
|
result.DownstreamReRoute[0].LoadBalancerKey.ShouldBe(expected.DownstreamReRoute[0].LoadBalancerKey);
|
||||||
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
|
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
|
||||||
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
|
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
|
||||||
result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set");
|
result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set");
|
276
test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs
Normal file
276
test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using Moq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Threading;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.UnitTests.Responder;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
|
||||||
|
public class CookieStickySessionsTests
|
||||||
|
{
|
||||||
|
private readonly CookieStickySessions _stickySessions;
|
||||||
|
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||||
|
private DownstreamContext _downstreamContext;
|
||||||
|
private Response<ServiceHostAndPort> _result;
|
||||||
|
private Response<ServiceHostAndPort> _firstHostAndPort;
|
||||||
|
private Response<ServiceHostAndPort> _secondHostAndPort;
|
||||||
|
|
||||||
|
public CookieStickySessionsTests()
|
||||||
|
{
|
||||||
|
_loadBalancer = new Mock<ILoadBalancer>();
|
||||||
|
const int defaultExpiryInMs = 100;
|
||||||
|
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", defaultExpiryInMs);
|
||||||
|
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_host_and_port()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturns())
|
||||||
|
.When(_ => WhenILease())
|
||||||
|
.Then(_ => ThenTheHostAndPortIsNotNull())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_same_host_and_port()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
|
||||||
|
.When(_ => WhenILeaseTwiceInARow())
|
||||||
|
.Then(_ => ThenTheFirstAndSecondResponseAreTheSame())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_different_host_and_port_if_load_balancer_does()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.When(_ => WhenIMakeTwoRequetsWithDifferentSessionValues())
|
||||||
|
.Then(_ => ThenADifferentHostAndPortIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_error()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsError())
|
||||||
|
.When(_ => WhenILease())
|
||||||
|
.Then(_ => ThenAnErrorIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_expire_sticky_session()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.When(_ => WhenTheStickySessionExpires())
|
||||||
|
.Then(_ => ThenANewHostAndPortIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_refresh_sticky_session()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.When(_ => WhenIMakeRequestsToKeepRefreshingTheSession())
|
||||||
|
.Then(_ => ThenTheSessionIsRefreshed())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_dispose()
|
||||||
|
{
|
||||||
|
_stickySessions.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_release()
|
||||||
|
{
|
||||||
|
_stickySessions.Release(new ServiceHostAndPort("", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ThenTheSessionIsRefreshed()
|
||||||
|
{
|
||||||
|
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
_loadBalancer
|
||||||
|
.Verify(x => x.Lease(It.IsAny<DownstreamContext>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenIMakeRequestsToKeepRefreshingTheSession()
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var cookies = new FakeCookies();
|
||||||
|
cookies.AddCookie("sessionid", "321");
|
||||||
|
context.Request.Cookies = cookies;
|
||||||
|
_downstreamContext = new DownstreamContext(context);
|
||||||
|
|
||||||
|
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
Thread.Sleep(80);
|
||||||
|
|
||||||
|
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
Thread.Sleep(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ThenANewHostAndPortIsReturned()
|
||||||
|
{
|
||||||
|
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||||
|
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenTheStickySessionExpires()
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var cookies = new FakeCookies();
|
||||||
|
cookies.AddCookie("sessionid", "321");
|
||||||
|
context.Request.Cookies = cookies;
|
||||||
|
_downstreamContext = new DownstreamContext(context);
|
||||||
|
|
||||||
|
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
|
||||||
|
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
Thread.Sleep(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenAnErrorIsReturned()
|
||||||
|
{
|
||||||
|
_result.IsError.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheLoadBalancerReturnsError()
|
||||||
|
{
|
||||||
|
_loadBalancer
|
||||||
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
|
.ReturnsAsync(new ErrorResponse<ServiceHostAndPort>(new AnyError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenADifferentHostAndPortIsReturned()
|
||||||
|
{
|
||||||
|
_firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
_firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
_secondHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||||
|
_secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenIMakeTwoRequetsWithDifferentSessionValues()
|
||||||
|
{
|
||||||
|
var contextOne = new DefaultHttpContext();
|
||||||
|
var cookiesOne = new FakeCookies();
|
||||||
|
cookiesOne.AddCookie("sessionid", "321");
|
||||||
|
contextOne.Request.Cookies = cookiesOne;
|
||||||
|
var contextTwo = new DefaultHttpContext();
|
||||||
|
var cookiesTwo = new FakeCookies();
|
||||||
|
cookiesTwo.AddCookie("sessionid", "123");
|
||||||
|
contextTwo.Request.Cookies = cookiesTwo;
|
||||||
|
_firstHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextOne));
|
||||||
|
_secondHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextTwo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheLoadBalancerReturnsSequence()
|
||||||
|
{
|
||||||
|
_loadBalancer
|
||||||
|
.SetupSequence(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("one", 80)))
|
||||||
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("two", 80)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFirstAndSecondResponseAreTheSame()
|
||||||
|
{
|
||||||
|
_firstHostAndPort.Data.DownstreamHost.ShouldBe(_secondHostAndPort.Data.DownstreamHost);
|
||||||
|
_firstHostAndPort.Data.DownstreamPort.ShouldBe(_secondHostAndPort.Data.DownstreamPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenILeaseTwiceInARow()
|
||||||
|
{
|
||||||
|
_firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
_secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheDownstreamRequestHasSessionId(string value)
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var cookies = new FakeCookies();
|
||||||
|
cookies.AddCookie("sessionid", value);
|
||||||
|
context.Request.Cookies = cookies;
|
||||||
|
_downstreamContext = new DownstreamContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheLoadBalancerReturns()
|
||||||
|
{
|
||||||
|
_loadBalancer
|
||||||
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("", 80)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenILease()
|
||||||
|
{
|
||||||
|
_result = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHostAndPortIsNotNull()
|
||||||
|
{
|
||||||
|
_result.Data.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeCookies : IRequestCookieCollection
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public string this[string key] => _cookies[key];
|
||||||
|
|
||||||
|
public int Count => _cookies.Count;
|
||||||
|
|
||||||
|
public ICollection<string> Keys => _cookies.Keys;
|
||||||
|
|
||||||
|
public void AddCookie(string key, string value)
|
||||||
|
{
|
||||||
|
_cookies[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return _cookies.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _cookies.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out string value)
|
||||||
|
{
|
||||||
|
return _cookies.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return _cookies.GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@ -17,9 +19,11 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
private LeastConnection _leastConnection;
|
private LeastConnection _leastConnection;
|
||||||
private List<Service> _services;
|
private List<Service> _services;
|
||||||
private Random _random;
|
private Random _random;
|
||||||
|
private DownstreamContext _context;
|
||||||
|
|
||||||
public LeastConnectionTests()
|
public LeastConnectionTests()
|
||||||
{
|
{
|
||||||
|
_context = new DownstreamContext(new DefaultHttpContext());
|
||||||
_random = new Random();
|
_random = new Random();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,9 +64,9 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
|
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
||||||
|
|
||||||
var hostAndPortOne = _leastConnection.Lease().Result;
|
var hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
var hostAndPortTwo = _leastConnection.Lease().Result;
|
var hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||||
_leastConnection.Release(hostAndPortOne.Data);
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
_leastConnection.Release(hostAndPortTwo.Data);
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
@ -72,9 +76,9 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
hostAndPortOne = _leastConnection.Lease().Result;
|
hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
hostAndPortTwo = _leastConnection.Lease().Result;
|
hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
_leastConnection.Release(hostAndPortOne.Data);
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
_leastConnection.Release(hostAndPortTwo.Data);
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
@ -85,9 +89,9 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
hostAndPortOne = _leastConnection.Lease().Result;
|
hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
hostAndPortTwo = _leastConnection.Lease().Result;
|
hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||||
_leastConnection.Release(hostAndPortOne.Data);
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
_leastConnection.Release(hostAndPortTwo.Data);
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
@ -95,7 +99,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
|
|
||||||
private async Task LeaseDelayAndRelease()
|
private async Task LeaseDelayAndRelease()
|
||||||
{
|
{
|
||||||
var hostAndPort = await _leastConnection.Lease();
|
var hostAndPort = await _leastConnection.Lease(_context);
|
||||||
await Task.Delay(_random.Next(1, 100));
|
await Task.Delay(_random.Next(1, 100));
|
||||||
_leastConnection.Release(hostAndPort.Data);
|
_leastConnection.Release(hostAndPort.Data);
|
||||||
}
|
}
|
||||||
@ -134,15 +138,15 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
_services = availableServices;
|
_services = availableServices;
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
|
|
||||||
var response = _leastConnection.Lease().Result;
|
var response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost);
|
||||||
}
|
}
|
||||||
@ -161,19 +165,19 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
_services = availableServices;
|
_services = availableServices;
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
|
|
||||||
var response = _leastConnection.Lease().Result;
|
var response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
}
|
}
|
||||||
@ -192,26 +196,26 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
_services = availableServices;
|
_services = availableServices;
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
|
|
||||||
var response = _leastConnection.Lease().Result;
|
var response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
//release this so 2 should have 1 connection and we should get 2 back as our next host and port
|
//release this so 2 should have 1 connection and we should get 2 back as our next host and port
|
||||||
_leastConnection.Release(availableServices[1].HostAndPort);
|
_leastConnection.Release(availableServices[1].HostAndPort);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
}
|
}
|
||||||
@ -272,7 +276,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
|
|
||||||
private void WhenIGetTheNextHostAndPort()
|
private void WhenIGetTheNextHostAndPort()
|
||||||
{
|
{
|
||||||
_result = _leastConnection.Lease().Result;
|
_result = _leastConnection.Lease(_context).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheNextHostAndPortIsReturned()
|
private void ThenTheNextHostAndPortIsReturned()
|
||||||
|
@ -14,10 +14,10 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
public class LoadBalancerFactoryTests
|
public class LoadBalancerFactoryTests
|
||||||
{
|
{
|
||||||
private DownstreamReRoute _reRoute;
|
private DownstreamReRoute _reRoute;
|
||||||
private LoadBalancerFactory _factory;
|
private readonly LoadBalancerFactory _factory;
|
||||||
private ILoadBalancer _result;
|
private ILoadBalancer _result;
|
||||||
private Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
private readonly Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
||||||
private Mock<IServiceDiscoveryProvider> _serviceProvider;
|
private readonly Mock<IServiceDiscoveryProvider> _serviceProvider;
|
||||||
private ServiceProviderConfiguration _serviceProviderConfig;
|
private ServiceProviderConfiguration _serviceProviderConfig;
|
||||||
|
|
||||||
public LoadBalancerFactoryTests()
|
public LoadBalancerFactoryTests()
|
||||||
@ -46,7 +46,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
public void should_return_round_robin_load_balancer()
|
public void should_return_round_robin_load_balancer()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithLoadBalancer("RoundRobin")
|
.WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0))
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
public void should_return_round_least_connection_balancer()
|
public void should_return_round_least_connection_balancer()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithLoadBalancer("LeastConnection")
|
.WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0))
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
public void should_call_service_provider()
|
public void should_call_service_provider()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithLoadBalancer("RoundRobin")
|
.WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0))
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@ -90,6 +90,22 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_sticky_session()
|
||||||
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithLoadBalancerOptions(new LoadBalancerOptions("CookieStickySessions", "", 0))
|
||||||
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenAReRoute(reRoute))
|
||||||
|
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
||||||
|
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||||
|
.When(x => x.WhenIGetTheLoadBalancer())
|
||||||
|
.Then(x => x.ThenTheLoadBalancerIsReturned<CookieStickySessions>())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig)
|
private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig)
|
||||||
{
|
{
|
||||||
_serviceProviderConfig = serviceProviderConfig;
|
_serviceProviderConfig = serviceProviderConfig;
|
||||||
|
@ -4,6 +4,7 @@ using Moq;
|
|||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@ -31,7 +32,9 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_store_load_balancer_on_first_request()
|
public void should_store_load_balancer_on_first_request()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithReRouteKey("test")
|
||||||
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
.Then(x => x.ThenItIsAdded())
|
.Then(x => x.ThenItIsAdded())
|
||||||
@ -41,7 +44,10 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_not_store_load_balancer_on_second_request()
|
public void should_not_store_load_balancer_on_second_request()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||||
|
.WithReRouteKey("test")
|
||||||
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||||
@ -52,8 +58,15 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_store_load_balancers_by_key()
|
public void should_store_load_balancers_by_key()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("FakeRoundRobinLoadBalancer").WithReRouteKey("testtwo").Build();
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||||
|
.WithReRouteKey("test")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var reRouteTwo = new DownstreamReRouteBuilder()
|
||||||
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeRoundRobinLoadBalancer", "", 0))
|
||||||
|
.WithReRouteKey("testtwo")
|
||||||
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
.And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer()))
|
.And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer()))
|
||||||
@ -77,9 +90,15 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed()
|
public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||||
|
.WithReRouteKey("test")
|
||||||
|
.Build();
|
||||||
|
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build();
|
var reRouteTwo = new DownstreamReRouteBuilder()
|
||||||
|
.WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0))
|
||||||
|
.WithReRouteKey("test")
|
||||||
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||||
@ -136,7 +155,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
|
|
||||||
class FakeLoadBalancer : ILoadBalancer
|
class FakeLoadBalancer : ILoadBalancer
|
||||||
{
|
{
|
||||||
public Task<Response<ServiceHostAndPort>> Lease()
|
public Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@ -149,7 +168,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
|
|
||||||
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
||||||
{
|
{
|
||||||
public Task<Response<ServiceHostAndPort>> Lease()
|
public Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
{
|
{
|
||||||
_getHostAndPortError = new ErrorResponse<ServiceHostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
_getHostAndPortError = new ErrorResponse<ServiceHostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
||||||
_loadBalancer
|
_loadBalancer
|
||||||
.Setup(x => x.Lease())
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
.ReturnsAsync(_getHostAndPortError);
|
.ReturnsAsync(_getHostAndPortError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
{
|
{
|
||||||
_hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
_hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
||||||
_loadBalancer
|
_loadBalancer
|
||||||
.Setup(x => x.Lease())
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(_hostAndPort));
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(_hostAndPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@ -29,6 +31,33 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_error_if_no_services()
|
||||||
|
{
|
||||||
|
var services = new List<Service>();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenServices(services))
|
||||||
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
|
.Then(x => x.ThenThereIsAnError())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_error_if_null_services()
|
||||||
|
{
|
||||||
|
List<Service> services = null;
|
||||||
|
|
||||||
|
this.Given(x => x.GivenServices(services))
|
||||||
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
|
.Then(x => x.ThenThereIsAnError())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenThereIsAnError()
|
||||||
|
{
|
||||||
|
_result.IsError.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenServices(List<Service> services)
|
private void GivenServices(List<Service> services)
|
||||||
{
|
{
|
||||||
_services = services;
|
_services = services;
|
||||||
@ -37,7 +66,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
private void WhenIGetTheNextHostAndPort()
|
private void WhenIGetTheNextHostAndPort()
|
||||||
{
|
{
|
||||||
_loadBalancer = new NoLoadBalancer(_services);
|
_loadBalancer = new NoLoadBalancer(_services);
|
||||||
_result = _loadBalancer.Lease().Result;
|
_result = _loadBalancer.Lease(new DownstreamContext(new DefaultHttpContext())).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheHostAndPortIs(ServiceHostAndPort expected)
|
private void ThenTheHostAndPortIs(ServiceHostAndPort expected)
|
||||||
|
@ -7,6 +7,8 @@ using Shouldly;
|
|||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
{
|
{
|
||||||
@ -15,9 +17,12 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
private readonly RoundRobin _roundRobin;
|
private readonly RoundRobin _roundRobin;
|
||||||
private readonly List<Service> _services;
|
private readonly List<Service> _services;
|
||||||
private Response<ServiceHostAndPort> _hostAndPort;
|
private Response<ServiceHostAndPort> _hostAndPort;
|
||||||
|
private DownstreamContext _context;
|
||||||
|
|
||||||
public RoundRobinTests()
|
public RoundRobinTests()
|
||||||
{
|
{
|
||||||
|
_context = new DownstreamContext(new DefaultHttpContext());
|
||||||
|
|
||||||
_services = new List<Service>
|
_services = new List<Service>
|
||||||
{
|
{
|
||||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
||||||
@ -47,18 +52,18 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
|
|
||||||
while (stopWatch.ElapsedMilliseconds < 1000)
|
while (stopWatch.ElapsedMilliseconds < 1000)
|
||||||
{
|
{
|
||||||
var address = _roundRobin.Lease().Result;
|
var address = _roundRobin.Lease(_context).Result;
|
||||||
address.Data.ShouldBe(_services[0].HostAndPort);
|
address.Data.ShouldBe(_services[0].HostAndPort);
|
||||||
address = _roundRobin.Lease().Result;
|
address = _roundRobin.Lease(_context).Result;
|
||||||
address.Data.ShouldBe(_services[1].HostAndPort);
|
address.Data.ShouldBe(_services[1].HostAndPort);
|
||||||
address = _roundRobin.Lease().Result;
|
address = _roundRobin.Lease(_context).Result;
|
||||||
address.Data.ShouldBe(_services[2].HostAndPort);
|
address.Data.ShouldBe(_services[2].HostAndPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenIGetTheNextAddress()
|
private void GivenIGetTheNextAddress()
|
||||||
{
|
{
|
||||||
_hostAndPort = _roundRobin.Lease().Result;
|
_hostAndPort = _roundRobin.Lease(_context).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheNextAddressIndexIs(int index)
|
private void ThenTheNextAddressIndexIs(int index)
|
||||||
|
@ -130,6 +130,38 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GET")]
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
public void should_add_verb_to_cache_key(string verb)
|
||||||
|
{
|
||||||
|
var client = "http://localhost:5012";
|
||||||
|
|
||||||
|
HttpMethod method = new HttpMethod(verb);
|
||||||
|
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.WithQosOptions(new QoSOptionsBuilder().Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(_ => GivenADownstreamService())
|
||||||
|
.And(_ => GivenARequestWithAUrlAndMethod(reRoute, client, method))
|
||||||
|
.And(_ => GivenTheFactoryReturnsNothing())
|
||||||
|
.And(_ => WhenIBuild())
|
||||||
|
.And(_ => GivenCacheIsCalledWithExpectedKey($"{method.ToString()}:{client}"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenCacheIsCalledWithExpectedKey(string expectedKey)
|
||||||
|
{
|
||||||
|
this._cacheHandlers.Verify(x => x.Get(It.Is<string>(p => p.Equals(expectedKey, StringComparison.OrdinalIgnoreCase))), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()
|
private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()
|
||||||
{
|
{
|
||||||
_logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamPathTemplate}"), Times.Once);
|
_logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamPathTemplate}"), Times.Once);
|
||||||
@ -197,11 +229,16 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void GivenARequest(DownstreamReRoute downstream)
|
private void GivenARequest(DownstreamReRoute downstream)
|
||||||
|
{
|
||||||
|
GivenARequestWithAUrlAndMethod(downstream, "http://localhost:5003", HttpMethod.Get);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenARequestWithAUrlAndMethod(DownstreamReRoute downstream, string url, HttpMethod method)
|
||||||
{
|
{
|
||||||
var context = new DownstreamContext(new DefaultHttpContext())
|
var context = new DownstreamContext(new DefaultHttpContext())
|
||||||
{
|
{
|
||||||
DownstreamReRoute = downstream,
|
DownstreamReRoute = downstream,
|
||||||
DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") }),
|
DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri(url), Method = method }),
|
||||||
};
|
};
|
||||||
|
|
||||||
_context = context;
|
_context = context;
|
||||||
|
@ -26,7 +26,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_store_qos_provider_on_first_request()
|
public void should_store_qos_provider_on_first_request()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.Then(x => x.ThenItIsAdded())
|
.Then(x => x.ThenItIsAdded())
|
||||||
@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_not_store_qos_provider_on_first_request()
|
public void should_not_store_qos_provider_on_first_request()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
||||||
@ -47,8 +47,8 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_store_qos_providers_by_key()
|
public void should_store_qos_providers_by_key()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithReRouteKey("testTwo").Build();
|
var reRouteTwo = new DownstreamReRouteBuilder().WithQosKey("testTwo").Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider()))
|
.And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider()))
|
||||||
@ -72,9 +72,9 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed()
|
public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
|
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithReRouteKey("test").WithIsQos(true).Build();
|
var reRouteTwo = new DownstreamReRouteBuilder().WithQosKey("test").WithIsQos(true).Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
||||||
|
@ -45,6 +45,13 @@ namespace Ocelot.UnitTests.Responder
|
|||||||
ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.ServiceUnavailable);
|
ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.ServiceUnavailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(OcelotErrorCode.UnableToCompleteRequestError)]
|
||||||
|
public void should_return_internal_server_error(OcelotErrorCode errorCode)
|
||||||
|
{
|
||||||
|
ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.InternalServerError);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(OcelotErrorCode.CannotAddDataError)]
|
[InlineData(OcelotErrorCode.CannotAddDataError)]
|
||||||
[InlineData(OcelotErrorCode.CannotFindDataError)]
|
[InlineData(OcelotErrorCode.CannotFindDataError)]
|
||||||
@ -60,7 +67,6 @@ namespace Ocelot.UnitTests.Responder
|
|||||||
[InlineData(OcelotErrorCode.RateLimitOptionsError)]
|
[InlineData(OcelotErrorCode.RateLimitOptionsError)]
|
||||||
[InlineData(OcelotErrorCode.ServicesAreEmptyError)]
|
[InlineData(OcelotErrorCode.ServicesAreEmptyError)]
|
||||||
[InlineData(OcelotErrorCode.ServicesAreNullError)]
|
[InlineData(OcelotErrorCode.ServicesAreNullError)]
|
||||||
[InlineData(OcelotErrorCode.UnableToCompleteRequestError)]
|
|
||||||
[InlineData(OcelotErrorCode.UnableToCreateAuthenticationHandlerError)]
|
[InlineData(OcelotErrorCode.UnableToCreateAuthenticationHandlerError)]
|
||||||
[InlineData(OcelotErrorCode.UnableToFindDownstreamRouteError)]
|
[InlineData(OcelotErrorCode.UnableToFindDownstreamRouteError)]
|
||||||
[InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)]
|
[InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user