#372 use period timespan to decide when client can make requests again (#404)

This commit is contained in:
Tom Pallister 2018-06-15 20:29:49 +01:00 committed by GitHub
parent 87c13bd9b4
commit 9979f8a4b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 429 additions and 359 deletions

View File

@ -1,40 +1,40 @@
Rate Limiting Rate Limiting
============= =============
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation. Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much. Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
OK so to get rate limiting working for a ReRoute you need to add the following json to it. OK so to get rate limiting working for a ReRoute you need to add the following json to it.
.. code-block:: json .. code-block:: json
"RateLimitOptions": { "RateLimitOptions": {
"ClientWhitelist": [], "ClientWhitelist": [],
"EnableRateLimiting": true, "EnableRateLimiting": true,
"Period": "1s", "Period": "1s",
"PeriodTimespan": 1, "PeriodTimespan": 1,
"Limit": 1 "Limit": 1
} }
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting. ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
EnableRateLimiting - This value specifies enable endpoint rate limiting. EnableRateLimiting - This value specifies enable endpoint rate limiting.
Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on. Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request.
PeriodTimespan - This value specifies that we can retry after a certain number of seconds. PeriodTimespan - This value specifies that we can retry after a certain number of seconds.
Limit - This value specifies the maximum number of requests that a client can make in a defined period. Limit - This value specifies the maximum number of requests that a client can make in a defined period.
You can also set the following in the GlobalConfiguration part of ocelot.json You can also set the following in the GlobalConfiguration part of ocelot.json
.. code-block:: json .. code-block:: json
"RateLimitOptions": { "RateLimitOptions": {
"DisableRateLimitHeaders": false, "DisableRateLimitHeaders": false,
"QuotaExceededMessage": "Customize Tips!", "QuotaExceededMessage": "Customize Tips!",
"HttpStatusCode": 999, "HttpStatusCode": 999,
"ClientIdHeader" : "Test" "ClientIdHeader" : "Test"
} }
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled. DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled.
QuotaExceededMessage - This value specifies the exceeded message. QuotaExceededMessage - This value specifies the exceeded message.
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs. HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId" ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"

View File

@ -1,148 +1,148 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ocelot.RateLimit namespace Ocelot.RateLimit
{ {
public class RateLimitCore public class RateLimitCore
{ {
private readonly IRateLimitCounterHandler _counterHandler; private readonly IRateLimitCounterHandler _counterHandler;
private static readonly object _processLocker = new object(); private static readonly object _processLocker = new object();
public RateLimitCore(IRateLimitCounterHandler counterStore) public RateLimitCore(IRateLimitCounterHandler counterStore)
{ {
_counterHandler = counterStore; _counterHandler = counterStore;
} }
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option)
{ {
RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1); RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1);
var rule = option.RateLimitRule; var rule = option.RateLimitRule;
var counterId = ComputeCounterKey(requestIdentity, option); var counterId = ComputeCounterKey(requestIdentity, option);
// serial reads and writes // serial reads and writes
lock (_processLocker) lock (_processLocker)
{ {
var entry = _counterHandler.Get(counterId); var entry = _counterHandler.Get(counterId);
if (entry.HasValue) if (entry.HasValue)
{ {
// entry has not expired // entry has not expired
if (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period) >= DateTime.UtcNow) if (entry.Value.Timestamp + TimeSpan.FromSeconds(rule.PeriodTimespan) >= DateTime.UtcNow)
{ {
// increment request count // increment request count
var totalRequests = entry.Value.TotalRequests + 1; var totalRequests = entry.Value.TotalRequests + 1;
// deep copy // deep copy
counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests); counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests);
} }
} }
} }
if (counter.TotalRequests > rule.Limit) if (counter.TotalRequests > rule.Limit)
{ {
var retryAfter = RetryAfterFrom(counter.Timestamp, rule); var retryAfter = RetryAfterFrom(counter.Timestamp, rule);
if (retryAfter > 0) if (retryAfter > 0)
{ {
var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan); var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan);
_counterHandler.Set(counterId, counter, expirationTime); _counterHandler.Set(counterId, counter, expirationTime);
} }
else else
{ {
_counterHandler.Remove(counterId); _counterHandler.Remove(counterId);
} }
} }
else else
{ {
var expirationTime = ConvertToTimeSpan(rule.Period); var expirationTime = ConvertToTimeSpan(rule.Period);
_counterHandler.Set(counterId, counter, expirationTime); _counterHandler.Set(counterId, counter, expirationTime);
} }
return counter; return counter;
} }
public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime) public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime)
{ {
var counterId = ComputeCounterKey(requestIdentity, option); var counterId = ComputeCounterKey(requestIdentity, option);
var rule = option.RateLimitRule; var rule = option.RateLimitRule;
// stores: id (string) - timestamp (datetime) - total_requests (long) // stores: id (string) - timestamp (datetime) - total_requests (long)
_counterHandler.Set(counterId, counter, expirationTime); _counterHandler.Set(counterId, counter, expirationTime);
} }
public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option)
{ {
var rule = option.RateLimitRule; var rule = option.RateLimitRule;
RateLimitHeaders headers = null; RateLimitHeaders headers = null;
var counterId = ComputeCounterKey(requestIdentity, option); var counterId = ComputeCounterKey(requestIdentity, option);
var entry = _counterHandler.Get(counterId); var entry = _counterHandler.Get(counterId);
if (entry.HasValue) if (entry.HasValue)
{ {
headers = new RateLimitHeaders(context, rule.Period, headers = new RateLimitHeaders(context, rule.Period,
(rule.Limit - entry.Value.TotalRequests).ToString(), (rule.Limit - entry.Value.TotalRequests).ToString(),
(entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo) (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)
); );
} }
else else
{ {
headers = new RateLimitHeaders(context, headers = new RateLimitHeaders(context,
rule.Period, rule.Period,
rule.Limit.ToString(), rule.Limit.ToString(),
(DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)); (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo));
} }
return headers; return headers;
} }
public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option)
{ {
var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}";
var idBytes = Encoding.UTF8.GetBytes(key); var idBytes = Encoding.UTF8.GetBytes(key);
byte[] hashBytes; byte[] hashBytes;
using (var algorithm = SHA1.Create()) using (var algorithm = SHA1.Create())
{ {
hashBytes = algorithm.ComputeHash(idBytes); hashBytes = algorithm.ComputeHash(idBytes);
} }
return BitConverter.ToString(hashBytes).Replace("-", string.Empty); return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
} }
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule) public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
{ {
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds); var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
return retryAfter; return retryAfter;
} }
public TimeSpan ConvertToTimeSpan(string timeSpan) public TimeSpan ConvertToTimeSpan(string timeSpan)
{ {
var l = timeSpan.Length - 1; var l = timeSpan.Length - 1;
var value = timeSpan.Substring(0, l); var value = timeSpan.Substring(0, l);
var type = timeSpan.Substring(l, 1); var type = timeSpan.Substring(l, 1);
switch (type) switch (type)
{ {
case "d": case "d":
return TimeSpan.FromDays(double.Parse(value)); return TimeSpan.FromDays(double.Parse(value));
case "h": case "h":
return TimeSpan.FromHours(double.Parse(value)); return TimeSpan.FromHours(double.Parse(value));
case "m": case "m":
return TimeSpan.FromMinutes(double.Parse(value)); return TimeSpan.FromMinutes(double.Parse(value));
case "s": case "s":
return TimeSpan.FromSeconds(double.Parse(value)); return TimeSpan.FromSeconds(double.Parse(value));
default: default:
throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}");
} }
} }
} }
} }

View File

@ -1,171 +1,236 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Shouldly; using Shouldly;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
public class ClientRateLimitTests : IDisposable public class ClientRateLimitTests : IDisposable
{ {
private IWebHost _builder; private IWebHost _builder;
private readonly Steps _steps; private readonly Steps _steps;
private int _counterOne; private int _counterOne;
public ClientRateLimitTests() public ClientRateLimitTests()
{ {
_steps = new Steps(); _steps = new Steps();
} }
public void Dispose() public void Dispose()
{ {
_builder?.Dispose(); _builder?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
[Fact] [Fact]
public void should_call_withratelimiting() public void should_call_withratelimiting()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/api/ClientRateLimit", DownstreamPathTemplate = "/api/ClientRateLimit",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = 51876,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,
RateLimitOptions = new FileRateLimitRule() RateLimitOptions = new FileRateLimitRule()
{ {
EnableRateLimiting = true, EnableRateLimiting = true,
ClientWhitelist = new List<string>(), ClientWhitelist = new List<string>(),
Limit = 3, Limit = 3,
Period = "1s", Period = "1s",
PeriodTimespan = 1000 PeriodTimespan = 1000
} }
} }
}, },
GlobalConfiguration = new FileGlobalConfiguration() GlobalConfiguration = new FileGlobalConfiguration()
{ {
RateLimitOptions = new FileRateLimitOptions() RateLimitOptions = new FileRateLimitOptions()
{ {
ClientIdHeader = "ClientId", ClientIdHeader = "ClientId",
DisableRateLimitHeaders = false, DisableRateLimitHeaders = false,
QuotaExceededMessage = "", QuotaExceededMessage = "",
RateLimitCounterPrefix = "", RateLimitCounterPrefix = "",
HttpStatusCode = 428 HttpStatusCode = 428
}, },
RequestIdKey ="oceclientrequest" RequestIdKey ="oceclientrequest"
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200)) .Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2)) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200)) .Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
.Then(x => _steps.ThenTheStatusCodeShouldBe(428)) .Then(x => _steps.ThenTheStatusCodeShouldBe(428))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_call_middleware_withWhitelistClient() public void should_wait_for_period_timespan_to_elapse_before_making_next_request()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/api/ClientRateLimit", DownstreamPathTemplate = "/api/ClientRateLimit",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = 51926,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,
RateLimitOptions = new FileRateLimitRule() RateLimitOptions = new FileRateLimitRule()
{ {
EnableRateLimiting = true, EnableRateLimiting = true,
ClientWhitelist = new List<string>() { "ocelotclient1"}, ClientWhitelist = new List<string>(),
Limit = 3, Limit = 3,
Period = "1s", Period = "1s",
PeriodTimespan = 100 PeriodTimespan = 2
} }
} }
}, },
GlobalConfiguration = new FileGlobalConfiguration() GlobalConfiguration = new FileGlobalConfiguration()
{ {
RateLimitOptions = new FileRateLimitOptions() RateLimitOptions = new FileRateLimitOptions()
{ {
ClientIdHeader = "ClientId", ClientIdHeader = "ClientId",
DisableRateLimitHeaders = false, DisableRateLimitHeaders = false,
QuotaExceededMessage = "", QuotaExceededMessage = "",
RateLimitCounterPrefix = "" RateLimitCounterPrefix = "",
}, HttpStatusCode = 428
RequestIdKey = "oceclientrequest" },
} RequestIdKey ="oceclientrequest"
}; }
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51926", "/api/ClientRateLimit"))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) .And(x => _steps.GivenOcelotIsRunning())
.Then(x => _steps.ThenTheStatusCodeShouldBe(200)) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
.BDDfy(); .Then(x => _steps.ThenTheStatusCodeShouldBe(200))
} .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
{ .Then(x => _steps.ThenTheStatusCodeShouldBe(428))
_builder = new WebHostBuilder() .And(x => _steps.GivenIWait(1000))
.UseUrls(baseUrl) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
.UseKestrel() .Then(x => _steps.ThenTheStatusCodeShouldBe(428))
.UseContentRoot(Directory.GetCurrentDirectory()) .And(x => _steps.GivenIWait(1000))
.UseIISIntegration() .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
.UseUrls(baseUrl) .Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.Configure(app => .BDDfy();
{ }
app.UsePathBase(basePath);
app.Run(context => [Fact]
{ public void should_call_middleware_withWhitelistClient()
_counterOne++; {
context.Response.StatusCode = 200; var configuration = new FileConfiguration
context.Response.WriteAsync(_counterOne.ToString()); {
return Task.CompletedTask; ReRoutes = new List<FileReRoute>
}); {
}) new FileReRoute
.Build(); {
DownstreamPathTemplate = "/api/ClientRateLimit",
_builder.Start(); DownstreamHostAndPorts = new List<FileHostAndPort>
} {
} new FileHostAndPort
} {
Host = "localhost",
Port = 51876,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = new List<string> { "Get" },
RequestIdKey = _steps.RequestIdKey,
RateLimitOptions = new FileRateLimitRule()
{
EnableRateLimiting = true,
ClientWhitelist = new List<string>() { "ocelotclient1"},
Limit = 3,
Period = "1s",
PeriodTimespan = 100
}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
RateLimitOptions = new FileRateLimitOptions()
{
ClientIdHeader = "ClientId",
DisableRateLimitHeaders = false,
QuotaExceededMessage = "",
RateLimitCounterPrefix = ""
},
RequestIdKey = "oceclientrequest"
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath)
{
_builder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(baseUrl)
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(context =>
{
_counterOne++;
context.Response.StatusCode = 200;
context.Response.WriteAsync(_counterOne.ToString());
return Task.CompletedTask;
});
})
.Build();
_builder.Start();
}
}
}

View File

@ -183,6 +183,11 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
internal void GivenIWait(int wait)
{
Thread.Sleep(wait);
}
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback) public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
{ {
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();