mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
Feature/#274 (#281)
* #274 added acceptance tests, need to work out failing unit tests but probably going to be a redesign where we hold a reference to the cookie container and empty it if needed * #274 updated code coverage value * #274 offloaded cache logic to builder in preparation for adding state * #274 hacked something together but this is not right approach * #274 updated defaults and docs * #274 updated code coverage
This commit is contained in:
parent
c1b315173f
commit
ed11f3024c
@ -17,7 +17,7 @@ var artifactsDir = Directory("artifacts");
|
|||||||
// unit testing
|
// unit testing
|
||||||
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
|
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
|
||||||
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
|
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
|
||||||
var minCodeCoverage = 76.4d;
|
var minCodeCoverage = 82d;
|
||||||
var coverallsRepoToken = "coveralls-repo-token-ocelot";
|
var coverallsRepoToken = "coveralls-repo-token-ocelot";
|
||||||
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
|
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
|
||||||
|
|
||||||
|
@ -73,10 +73,17 @@ Follow Redirects / Use CookieContainer
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
||||||
- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses.
|
|
||||||
Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true.
|
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
|
||||||
- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests.
|
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
|
||||||
The default value is true.
|
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
|
||||||
|
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
|
||||||
|
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
|
||||||
|
to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user
|
||||||
|
noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients
|
||||||
|
that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight
|
||||||
|
requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting
|
||||||
|
UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request!
|
||||||
|
|
||||||
Multiple environments
|
Multiple environments
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
{
|
{
|
||||||
public FileHttpHandlerOptions()
|
public FileHttpHandlerOptions()
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = true;
|
AllowAutoRedirect = false;
|
||||||
UseCookieContainer = true;
|
UseCookieContainer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AllowAutoRedirect { get; set; }
|
public bool AllowAutoRedirect { get; set; }
|
||||||
|
14
src/Ocelot/Requester/GlobalDelegatingHandler.cs
Normal file
14
src/Ocelot/Requester/GlobalDelegatingHandler.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.Requester
|
||||||
|
{
|
||||||
|
public class GlobalDelegatingHandler
|
||||||
|
{
|
||||||
|
public GlobalDelegatingHandler(DelegatingHandler delegatingHandler)
|
||||||
|
{
|
||||||
|
DelegatingHandler = delegatingHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DelegatingHandler DelegatingHandler { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,61 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
public class HttpClientBuilder : IHttpClientBuilder
|
public class HttpClientBuilder : IHttpClientBuilder
|
||||||
{
|
{
|
||||||
private readonly IDelegatingHandlerHandlerFactory _factory;
|
private readonly IDelegatingHandlerHandlerFactory _factory;
|
||||||
|
private readonly IHttpClientCache _cacheHandlers;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private string _cacheKey;
|
||||||
|
private HttpClient _httpClient;
|
||||||
|
private IHttpClient _client;
|
||||||
|
private HttpClientHandler _httpclientHandler;
|
||||||
|
|
||||||
public HttpClientBuilder(IDelegatingHandlerHandlerFactory house)
|
public HttpClientBuilder(
|
||||||
|
IDelegatingHandlerHandlerFactory factory,
|
||||||
|
IHttpClientCache cacheHandlers,
|
||||||
|
IOcelotLogger logger)
|
||||||
{
|
{
|
||||||
_factory = house;
|
_factory = factory;
|
||||||
|
_cacheHandlers = cacheHandlers;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHttpClient Create(DownstreamReRoute request)
|
public IHttpClient Create(DownstreamContext request)
|
||||||
{
|
{
|
||||||
var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.HttpHandlerOptions.AllowAutoRedirect, UseCookies = request.HttpHandlerOptions.UseCookieContainer};
|
_cacheKey = GetCacheKey(request);
|
||||||
|
|
||||||
var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request));
|
var httpClient = _cacheHandlers.Get(_cacheKey);
|
||||||
|
|
||||||
return new HttpClientWrapper(client);
|
if (httpClient != null)
|
||||||
|
{
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpclientHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = request.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect,
|
||||||
|
UseCookies = request.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer,
|
||||||
|
CookieContainer = new CookieContainer()
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpClient = new HttpClient(CreateHttpMessageHandler(_httpclientHandler, request.DownstreamReRoute));
|
||||||
|
|
||||||
|
_client = new HttpClientWrapper(_httpClient);
|
||||||
|
|
||||||
|
return _client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24));
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request)
|
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request)
|
||||||
@ -39,5 +75,12 @@ namespace Ocelot.Requester
|
|||||||
});
|
});
|
||||||
return httpMessageHandler;
|
return httpMessageHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCacheKey(DownstreamContext request)
|
||||||
|
{
|
||||||
|
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}";
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,15 @@ namespace Ocelot.Requester
|
|||||||
_factory = house;
|
_factory = house;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext request)
|
public async Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var builder = new HttpClientBuilder(_factory);
|
var builder = new HttpClientBuilder(_factory, _cacheHandlers, _logger);
|
||||||
|
|
||||||
var cacheKey = GetCacheKey(request);
|
var httpClient = builder.Create(context);
|
||||||
|
|
||||||
var httpClient = GetHttpClient(cacheKey, builder, request);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await httpClient.SendAsync(request.DownstreamRequest);
|
var response = await httpClient.SendAsync(context.DownstreamRequest);
|
||||||
return new OkResponse<HttpResponseMessage>(response);
|
return new OkResponse<HttpResponseMessage>(response);
|
||||||
}
|
}
|
||||||
catch (TimeoutRejectedException exception)
|
catch (TimeoutRejectedException exception)
|
||||||
@ -51,43 +49,8 @@ namespace Ocelot.Requester
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24));
|
builder.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, DownstreamContext request)
|
|
||||||
{
|
|
||||||
var httpClient = _cacheHandlers.Get(cacheKey);
|
|
||||||
|
|
||||||
if (httpClient == null)
|
|
||||||
{
|
|
||||||
httpClient = builder.Create(request.DownstreamReRoute);
|
|
||||||
}
|
|
||||||
|
|
||||||
return httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetCacheKey(DownstreamContext request)
|
|
||||||
{
|
|
||||||
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}";
|
|
||||||
|
|
||||||
return baseUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ReRouteDelegatingHandler<T>
|
|
||||||
where T : DelegatingHandler
|
|
||||||
{
|
|
||||||
public T DelegatingHandler { get; private set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GlobalDelegatingHandler
|
|
||||||
{
|
|
||||||
public GlobalDelegatingHandler(DelegatingHandler delegatingHandler)
|
|
||||||
{
|
|
||||||
DelegatingHandler = delegatingHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DelegatingHandler DelegatingHandler { get; private set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ namespace Ocelot.Requester
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class was made to make unit testing easier when HttpClient is used.
|
/// This class was made to make unit testing easier when HttpClient is used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class HttpClientWrapper : IHttpClient
|
public class HttpClientWrapper : IHttpClient
|
||||||
{
|
{
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
using System.Net.Http;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
public interface IHttpClientBuilder
|
public interface IHttpClientBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
IHttpClient Create(DownstreamContext request);
|
||||||
/// Creates the <see cref="HttpClient"/>
|
void Save();
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
IHttpClient Create(DownstreamReRoute request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ namespace Ocelot.Requester
|
|||||||
{
|
{
|
||||||
public interface IHttpRequester
|
public interface IHttpRequester
|
||||||
{
|
{
|
||||||
Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext request);
|
Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/Ocelot/Requester/ReRouteDelegatingHandler.cs
Normal file
10
src/Ocelot/Requester/ReRouteDelegatingHandler.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.Requester
|
||||||
|
{
|
||||||
|
public class ReRouteDelegatingHandler<T>
|
||||||
|
where T : DelegatingHandler
|
||||||
|
{
|
||||||
|
public T DelegatingHandler { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,8 @@ namespace Ocelot.AcceptanceTests
|
|||||||
public class HeaderTests : IDisposable
|
public class HeaderTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
|
private string _cookieValue;
|
||||||
|
private int _count;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
|
|
||||||
public HeaderTests()
|
public HeaderTests()
|
||||||
@ -184,6 +186,125 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void request_should_reuse_cookies_with_cookie_container()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/sso/{everything}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 6774,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/sso/{everything}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
UseCookieContainer = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
||||||
|
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void request_should_have_own_cookies_no_cookie_container()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/sso/{everything}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 6775,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/sso/{everything}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
UseCookieContainer = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
||||||
|
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if(_count == 0)
|
||||||
|
{
|
||||||
|
context.Response.Cookies.Append("test", "0");
|
||||||
|
_count++;
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
|
||||||
|
{
|
||||||
|
if(cookieValue == "0" || headerValue == "test=1; path=/")
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
||||||
{
|
{
|
||||||
_builder = new WebHostBuilder()
|
_builder = new WebHostBuilder()
|
||||||
|
@ -274,6 +274,11 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void GivenIAddCookieToMyRequest(string cookie)
|
||||||
|
{
|
||||||
|
_ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -19,10 +19,10 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default()
|
public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default()
|
||||||
{
|
{
|
||||||
var fileReRoute = new FileReRoute();
|
var fileReRoute = new FileReRoute();
|
||||||
var expectedOptions = new HttpHandlerOptions(true, true, false);
|
var expectedOptions = new HttpHandlerOptions(false, false, false);
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
.When(x => WhenICreateHttpHandlerOptions())
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
@ -61,12 +61,12 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute);
|
_httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheFollowingOptionsReturned(HttpHandlerOptions options)
|
private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected)
|
||||||
{
|
{
|
||||||
_httpHandlerOptions.ShouldNotBeNull();
|
_httpHandlerOptions.ShouldNotBeNull();
|
||||||
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect);
|
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect);
|
||||||
_httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer);
|
_httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer);
|
||||||
_httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing);
|
_httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Requester;
|
using Ocelot.Requester;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@ -12,25 +22,37 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Ocelot.UnitTests.Requester
|
namespace Ocelot.UnitTests.Requester
|
||||||
{
|
{
|
||||||
public class HttpClientBuilderTests
|
public class HttpClientBuilderTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpClientBuilder _builder;
|
private readonly HttpClientBuilder _builder;
|
||||||
private readonly Mock<IDelegatingHandlerHandlerFactory> _factory;
|
private readonly Mock<IDelegatingHandlerHandlerFactory> _factory;
|
||||||
private IHttpClient _httpClient;
|
private IHttpClient _httpClient;
|
||||||
private HttpResponseMessage _response;
|
private HttpResponseMessage _response;
|
||||||
private DownstreamReRoute _request;
|
private DownstreamContext _context;
|
||||||
|
private readonly Mock<IHttpClientCache> _cacheHandlers;
|
||||||
|
private Mock<IOcelotLogger> _logger;
|
||||||
|
private int _count;
|
||||||
|
private IWebHost _host;
|
||||||
|
|
||||||
public HttpClientBuilderTests()
|
public HttpClientBuilderTests()
|
||||||
{
|
{
|
||||||
|
_cacheHandlers = new Mock<IHttpClientCache>();
|
||||||
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_factory = new Mock<IDelegatingHandlerHandlerFactory>();
|
_factory = new Mock<IDelegatingHandlerHandlerFactory>();
|
||||||
_builder = new HttpClientBuilder(_factory.Object);
|
_builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_build_http_client()
|
public void should_build_http_client()
|
||||||
{
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.Build();
|
||||||
|
|
||||||
this.Given(x => GivenTheFactoryReturns())
|
this.Given(x => GivenTheFactoryReturns())
|
||||||
.And(x => GivenARequest())
|
.And(x => GivenARequest(reRoute))
|
||||||
.When(x => WhenIBuild())
|
.When(x => WhenIBuild())
|
||||||
.Then(x => ThenTheHttpClientShouldNotBeNull())
|
.Then(x => ThenTheHttpClientShouldNotBeNull())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
@ -39,6 +61,12 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_delegating_handlers_in_order()
|
public void should_call_delegating_handlers_in_order()
|
||||||
{
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.Build();
|
||||||
|
|
||||||
var fakeOne = new FakeDelegatingHandler();
|
var fakeOne = new FakeDelegatingHandler();
|
||||||
var fakeTwo = new FakeDelegatingHandler();
|
var fakeTwo = new FakeDelegatingHandler();
|
||||||
|
|
||||||
@ -49,7 +77,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => GivenTheFactoryReturns(handlers))
|
this.Given(x => GivenTheFactoryReturns(handlers))
|
||||||
.And(x => GivenARequest())
|
.And(x => GivenARequest(reRoute))
|
||||||
.And(x => WhenIBuild())
|
.And(x => WhenIBuild())
|
||||||
.When(x => WhenICallTheClient())
|
.When(x => WhenICallTheClient())
|
||||||
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
|
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
|
||||||
@ -57,12 +85,95 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenARequest()
|
[Fact]
|
||||||
|
public void should_re_use_cookies_from_container()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithIsQos(false)
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build();
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.Build();
|
||||||
|
|
||||||
_request = reRoute;
|
this.Given(_ => GivenADownstreamService())
|
||||||
|
.And(_ => GivenARequest(reRoute))
|
||||||
|
.And(_ => GivenTheFactoryReturnsNothing())
|
||||||
|
.And(_ => WhenIBuild())
|
||||||
|
.And(_ => WhenICallTheClient("http://localhost:5003"))
|
||||||
|
.And(_ => ThenTheCookieIsSet())
|
||||||
|
.And(_ => GivenTheClientIsCached())
|
||||||
|
.And(_ => WhenIBuild())
|
||||||
|
.When(_ => WhenICallTheClient("http://localhost:5003"))
|
||||||
|
.Then(_ => ThenTheResponseIsOk())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheClientIsCached()
|
||||||
|
{
|
||||||
|
_cacheHandlers.Setup(x => x.Get(It.IsAny<string>())).Returns(_httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCookieIsSet()
|
||||||
|
{
|
||||||
|
_response.Headers.TryGetValues("Set-Cookie", out var test).ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheClient(string url)
|
||||||
|
{
|
||||||
|
_response = _httpClient
|
||||||
|
.SendAsync(new HttpRequestMessage(HttpMethod.Get, url))
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheResponseIsOk()
|
||||||
|
{
|
||||||
|
_response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenADownstreamService()
|
||||||
|
{
|
||||||
|
_host = new WebHostBuilder()
|
||||||
|
.UseUrls("http://localhost:5003")
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
context.Response.Cookies.Append("test", "0");
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_count == 1)
|
||||||
|
{
|
||||||
|
if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_host.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenARequest(DownstreamReRoute downstream)
|
||||||
|
{
|
||||||
|
var context = new DownstreamContext(new DefaultHttpContext())
|
||||||
|
{
|
||||||
|
DownstreamReRoute = downstream,
|
||||||
|
DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") },
|
||||||
|
};
|
||||||
|
|
||||||
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenSomethingIsReturned()
|
private void ThenSomethingIsReturned()
|
||||||
@ -88,6 +199,14 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>()))
|
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>()))
|
||||||
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
|
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
|
||||||
}
|
}
|
||||||
|
private void GivenTheFactoryReturnsNothing()
|
||||||
|
{
|
||||||
|
var handlers = new List<Func<DelegatingHandler>>();
|
||||||
|
|
||||||
|
_factory
|
||||||
|
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>()))
|
||||||
|
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenTheFactoryReturns(List<Func<DelegatingHandler>> handlers)
|
private void GivenTheFactoryReturns(List<Func<DelegatingHandler>> handlers)
|
||||||
{
|
{
|
||||||
@ -98,12 +217,18 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
private void WhenIBuild()
|
private void WhenIBuild()
|
||||||
{
|
{
|
||||||
_httpClient = _builder.Create(_request);
|
_httpClient = _builder.Create(_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheHttpClientShouldNotBeNull()
|
private void ThenTheHttpClientShouldNotBeNull()
|
||||||
{
|
{
|
||||||
_httpClient.ShouldNotBeNull();
|
_httpClient.ShouldNotBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_response?.Dispose();
|
||||||
|
_host?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,10 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
|
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
|
||||||
.Returns(_logger.Object);
|
.Returns(_logger.Object);
|
||||||
_cacheHandlers = new Mock<IHttpClientCache>();
|
_cacheHandlers = new Mock<IHttpClientCache>();
|
||||||
_httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _house.Object);
|
_httpClientRequester = new HttpClientHttpRequester(
|
||||||
|
_loggerFactory.Object,
|
||||||
|
_cacheHandlers.Object,
|
||||||
|
_house.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user