From ed11f3024c3d520a12cb23aa6e75a42bd36471ff Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 17 Mar 2018 11:35:16 +0000 Subject: [PATCH] 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 --- build.cake | 2 +- docs/features/configuration.rst | 17 +- .../File/FileHttpHandlerOptions.cs | 4 +- .../Requester/GlobalDelegatingHandler.cs | 14 ++ src/Ocelot/Requester/HttpClientBuilder.cs | 61 ++++++-- .../Requester/HttpClientHttpRequester.cs | 47 +----- src/Ocelot/Requester/HttpClientWrapper.cs | 2 +- src/Ocelot/Requester/IHttpClientBuilder.cs | 10 +- src/Ocelot/Requester/IHttpRequester.cs | 2 +- .../Requester/ReRouteDelegatingHandler.cs | 10 ++ test/Ocelot.AcceptanceTests/HeaderTests.cs | 121 +++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 5 + .../HttpHandlerOptionsCreatorTests.cs | 12 +- .../Requester/HttpClientBuilderTests.cs | 145 ++++++++++++++++-- .../Requester/HttpClientHttpRequesterTest.cs | 5 +- 15 files changed, 372 insertions(+), 85 deletions(-) create mode 100644 src/Ocelot/Requester/GlobalDelegatingHandler.cs create mode 100644 src/Ocelot/Requester/ReRouteDelegatingHandler.cs diff --git a/build.cake b/build.cake index af2f9364..c732864b 100644 --- a/build.cake +++ b/build.cake @@ -17,7 +17,7 @@ var artifactsDir = Directory("artifacts"); // unit testing var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; -var minCodeCoverage = 76.4d; +var minCodeCoverage = 82d; var coverallsRepoToken = "coveralls-repo-token-ocelot"; var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 93cae138..8a1e61a1 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -73,10 +73,17 @@ Follow Redirects / Use CookieContainer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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. -- _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 true. + +1. 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 false. +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 `_ 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 ^^^^^^^^^^^^^^^^^^^^^ @@ -127,4 +134,4 @@ finds your Consul agent and interacts to load and store the configuration from C I decided to create this feature after working on the raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. -This feature has a 3 second ttl cache before making a new request to your local consul agent. \ No newline at end of file +This feature has a 3 second ttl cache before making a new request to your local consul agent. diff --git a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs index 7f24b572..2934254c 100644 --- a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs +++ b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs @@ -4,8 +4,8 @@ { public FileHttpHandlerOptions() { - AllowAutoRedirect = true; - UseCookieContainer = true; + AllowAutoRedirect = false; + UseCookieContainer = false; } public bool AllowAutoRedirect { get; set; } diff --git a/src/Ocelot/Requester/GlobalDelegatingHandler.cs b/src/Ocelot/Requester/GlobalDelegatingHandler.cs new file mode 100644 index 00000000..ba5e1c5f --- /dev/null +++ b/src/Ocelot/Requester/GlobalDelegatingHandler.cs @@ -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; } + } +} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index b5604081..6cbb3aec 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,25 +1,61 @@ -using System.Linq; +using System; +using System.Linq; +using System.Net; using System.Net.Http; using Ocelot.Configuration; +using Ocelot.Logging; +using Ocelot.Middleware; namespace Ocelot.Requester { public class HttpClientBuilder : IHttpClientBuilder { 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}; - - var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request)); - - return new HttpClientWrapper(client); + _cacheKey = GetCacheKey(request); + + var httpClient = _cacheHandlers.Get(_cacheKey); + + 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) @@ -39,5 +75,12 @@ namespace Ocelot.Requester }); return httpMessageHandler; } + + private string GetCacheKey(DownstreamContext request) + { + var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}"; + + return baseUrl; + } } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index c6598903..4202f611 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -24,17 +24,15 @@ namespace Ocelot.Requester _factory = house; } - public async Task> GetResponse(DownstreamContext request) + public async Task> GetResponse(DownstreamContext context) { - var builder = new HttpClientBuilder(_factory); + var builder = new HttpClientBuilder(_factory, _cacheHandlers, _logger); - var cacheKey = GetCacheKey(request); - - var httpClient = GetHttpClient(cacheKey, builder, request); + var httpClient = builder.Create(context); try { - var response = await httpClient.SendAsync(request.DownstreamRequest); + var response = await httpClient.SendAsync(context.DownstreamRequest); return new OkResponse(response); } catch (TimeoutRejectedException exception) @@ -51,43 +49,8 @@ namespace Ocelot.Requester } 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 - where T : DelegatingHandler - { - public T DelegatingHandler { get; private set; } - } - - public class GlobalDelegatingHandler - { - public GlobalDelegatingHandler(DelegatingHandler delegatingHandler) - { - DelegatingHandler = delegatingHandler; - } - - public DelegatingHandler DelegatingHandler { get; private set; } } } diff --git a/src/Ocelot/Requester/HttpClientWrapper.cs b/src/Ocelot/Requester/HttpClientWrapper.cs index 21e74e48..b1f0345a 100644 --- a/src/Ocelot/Requester/HttpClientWrapper.cs +++ b/src/Ocelot/Requester/HttpClientWrapper.cs @@ -6,7 +6,7 @@ namespace Ocelot.Requester /// /// This class was made to make unit testing easier when HttpClient is used. /// - internal class HttpClientWrapper : IHttpClient + public class HttpClientWrapper : IHttpClient { public HttpClient Client { get; } diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index f10e55f5..cc8c6160 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -1,14 +1,10 @@ -using System.Net.Http; -using Ocelot.Configuration; +using Ocelot.Middleware; namespace Ocelot.Requester { public interface IHttpClientBuilder { - /// - /// Creates the - /// - /// - IHttpClient Create(DownstreamReRoute request); + IHttpClient Create(DownstreamContext request); + void Save(); } } diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index 5d9aa5dc..86f6209a 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -7,6 +7,6 @@ namespace Ocelot.Requester { public interface IHttpRequester { - Task> GetResponse(DownstreamContext request); + Task> GetResponse(DownstreamContext context); } } diff --git a/src/Ocelot/Requester/ReRouteDelegatingHandler.cs b/src/Ocelot/Requester/ReRouteDelegatingHandler.cs new file mode 100644 index 00000000..0a5c5472 --- /dev/null +++ b/src/Ocelot/Requester/ReRouteDelegatingHandler.cs @@ -0,0 +1,10 @@ +using System.Net.Http; + +namespace Ocelot.Requester +{ + public class ReRouteDelegatingHandler + where T : DelegatingHandler + { + public T DelegatingHandler { get; private set; } + } +} diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 350e1e52..2bd3c2ab 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -16,6 +16,8 @@ namespace Ocelot.AcceptanceTests public class HeaderTests : IDisposable { private IWebHost _builder; + private string _cookieValue; + private int _count; private readonly Steps _steps; public HeaderTests() @@ -184,6 +186,125 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void request_should_reuse_cookies_with_cookie_container() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/sso/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6774, + } + }, + UpstreamPathTemplate = "/sso/{everything}", + UpstreamHttpMethod = new List { "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 + { + new FileReRoute + { + DownstreamPathTemplate = "/sso/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6775, + } + }, + UpstreamPathTemplate = "/sso/{everything}", + UpstreamHttpMethod = new List { "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) { _builder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 0de7113d..2cb0f830 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -274,6 +274,11 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + internal void GivenIAddCookieToMyRequest(string cookie) + { + _ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie); + } + /// /// 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. /// diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index b0b11bfc..ca22956c 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -19,10 +19,10 @@ namespace Ocelot.UnitTests.Configuration } [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 expectedOptions = new HttpHandlerOptions(true, true, false); + var expectedOptions = new HttpHandlerOptions(false, false, false); this.Given(x => GivenTheFollowing(fileReRoute)) .When(x => WhenICreateHttpHandlerOptions()) @@ -61,12 +61,12 @@ namespace Ocelot.UnitTests.Configuration _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute); } - private void ThenTheFollowingOptionsReturned(HttpHandlerOptions options) + private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected) { _httpHandlerOptions.ShouldNotBeNull(); - _httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect); - _httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer); - _httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing); + _httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect); + _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); + _httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing); } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index c4ab1341..7368fac8 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -1,9 +1,19 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; 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 Ocelot.Configuration; using Ocelot.Configuration.Builder; +using Ocelot.Logging; +using Ocelot.Middleware; using Ocelot.Requester; using Ocelot.Responses; using Shouldly; @@ -12,25 +22,37 @@ using Xunit; namespace Ocelot.UnitTests.Requester { - public class HttpClientBuilderTests + public class HttpClientBuilderTests : IDisposable { private readonly HttpClientBuilder _builder; private readonly Mock _factory; private IHttpClient _httpClient; private HttpResponseMessage _response; - private DownstreamReRoute _request; + private DownstreamContext _context; + private readonly Mock _cacheHandlers; + private Mock _logger; + private int _count; + private IWebHost _host; public HttpClientBuilderTests() { + _cacheHandlers = new Mock(); + _logger = new Mock(); _factory = new Mock(); - _builder = new HttpClientBuilder(_factory.Object); + _builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object); } [Fact] public void should_build_http_client() { + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .Build(); + this.Given(x => GivenTheFactoryReturns()) - .And(x => GivenARequest()) + .And(x => GivenARequest(reRoute)) .When(x => WhenIBuild()) .Then(x => ThenTheHttpClientShouldNotBeNull()) .BDDfy(); @@ -39,6 +61,12 @@ namespace Ocelot.UnitTests.Requester [Fact] 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 fakeTwo = new FakeDelegatingHandler(); @@ -49,7 +77,7 @@ namespace Ocelot.UnitTests.Requester }; this.Given(x => GivenTheFactoryReturns(handlers)) - .And(x => GivenARequest()) + .And(x => GivenARequest(reRoute)) .And(x => WhenIBuild()) .When(x => WhenICallTheClient()) .Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo)) @@ -57,12 +85,95 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void GivenARequest() + [Fact] + public void should_re_use_cookies_from_container() { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + var reRoute = new DownstreamReRouteBuilder() + .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())).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() @@ -88,6 +199,14 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse>>(handlers)); } + private void GivenTheFactoryReturnsNothing() + { + var handlers = new List>(); + + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); + } private void GivenTheFactoryReturns(List> handlers) { @@ -98,12 +217,18 @@ namespace Ocelot.UnitTests.Requester private void WhenIBuild() { - _httpClient = _builder.Create(_request); + _httpClient = _builder.Create(_context); } private void ThenTheHttpClientShouldNotBeNull() { _httpClient.ShouldNotBeNull(); } + + public void Dispose() + { + _response?.Dispose(); + _host?.Dispose(); + } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index f2c1f3a0..bbf59692 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -35,7 +35,10 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.CreateLogger()) .Returns(_logger.Object); _cacheHandlers = new Mock(); - _httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _house.Object); + _httpClientRequester = new HttpClientHttpRequester( + _loggerFactory.Object, + _cacheHandlers.Object, + _house.Object); } [Fact]