From 7e1f8a4604aaa6a9432b49533e1929346ccda1c9 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 4 Mar 2018 10:24:04 +0000 Subject: [PATCH 1/7] Added service fabric to big picture in docs --- docs/images/OcelotServiceFabric.jpg | Bin 0 -> 53509 bytes docs/introduction/bigpicture.rst | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 docs/images/OcelotServiceFabric.jpg diff --git a/docs/images/OcelotServiceFabric.jpg b/docs/images/OcelotServiceFabric.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4aaab7ad8bb169696340e82b606beb7d6a1981d GIT binary patch literal 53509 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 literal 0 HcmV?d00001 diff --git a/docs/introduction/bigpicture.rst b/docs/introduction/bigpicture.rst index 95e62fdc..989c0f5c 100644 --- a/docs/introduction/bigpicture.rst +++ b/docs/introduction/bigpicture.rst @@ -36,3 +36,7 @@ Multiple Instances With Consul ^^^^^^^^^^^ .. image:: ../images/OcelotMultipleInstancesConsul.jpg + +With Service Fabric +^^^^^^^^^^^^^^^^^^^ +.. image:: ../images/OcelotServiceFabric.jpg From db05935b89477b1c824da8ab127d28a66d96e8e9 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 5 Mar 2018 20:18:58 +0000 Subject: [PATCH 2/7] updated not supported with reasons why Ocelot doesnt support swagger (#258) --- docs/introduction/notsupported.rst | 26 +++++- .../Ocelot.ManualTest.csproj | 88 +++++++++---------- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index 8ef9b5e7..35c916a0 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -5,4 +5,28 @@ Ocelot does not support... * Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case! -* Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( \ No newline at end of file +* Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( + +* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore + +.. code-block:: csharp + + app.Map("/swagger/v1/swagger.json", b => + { + b.Run(async x => { + var json = File.ReadAllText("swagger.json"); + await x.Response.WriteAsync(json); + }); + }); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ocelot"); + }); + + app.UseOcelot().Wait(); + +The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. + +In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. Unless I rolled my own Swagger implementation. + +If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this. \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index ed0e2cb8..1e708c68 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -1,47 +1,41 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - true - Ocelot.ManualTest - Exe - Ocelot.ManualTest - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - ..\..\codeanalysis.ruleset - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - all - - - - + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + true + Ocelot.ManualTest + Exe + Ocelot.ManualTest + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + ..\..\codeanalysis.ruleset + + + + PreserveNewest + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + all + + + \ No newline at end of file From b8e95373a470e25f4ffce3e58e64cb59bcd53ab5 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 6 Mar 2018 10:59:01 +0000 Subject: [PATCH 3/7] Fix async/await warnings --- .../Repository/FileConfigurationRepository.cs | 12 ++++++------ .../InMemoryOcelotConfigurationRepository.cs | 10 +++++----- .../ServiceFabricServiceDiscoveryProvider.cs | 9 ++++----- test/Ocelot.AcceptanceTests/HeaderTests.cs | 4 +++- .../Authentication/AuthenticationMiddlewareTests.cs | 11 +++++++---- .../Authorization/AuthorisationMiddlewareTests.cs | 7 ++----- .../Cache/OutputCacheMiddlewareRealCacheTests.cs | 5 ++--- .../Cache/OutputCacheMiddlewareTests.cs | 10 ++-------- .../Claims/ClaimsBuilderMiddlewareTests.cs | 5 ++--- .../DownstreamRouteFinderMiddlewareTests.cs | 7 ++----- .../DownstreamUrlCreatorMiddlewareTests.cs | 10 ++-------- .../HttpHeadersTransformationMiddlewareTests.cs | 7 +++---- .../HttpRequestHeadersBuilderMiddlewareTests.cs | 8 ++------ .../LoadBalancer/LoadBalancerMiddlewareTests.cs | 5 ++--- test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs | 2 +- .../QueryStringBuilderMiddlewareTests.cs | 8 ++------ .../RateLimit/ClientRateLimitMiddlewareTests.cs | 5 ++--- .../RequestId/ReRouteRequestIdMiddlewareTests.cs | 5 ++++- .../Requester/FakeDelegatingHandler.cs | 5 +++-- .../Requester/HttpRequesterMiddlewareTests.cs | 5 ++--- .../Responder/ResponderMiddlewareTests.cs | 5 ++--- 21 files changed, 60 insertions(+), 85 deletions(-) diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs index 29a0c36e..ff5e3876 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs @@ -18,7 +18,7 @@ namespace Ocelot.Configuration.Repository _configFilePath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; } - public async Task> Get() + public Task> Get() { string jsonConfiguration; @@ -29,10 +29,10 @@ namespace Ocelot.Configuration.Repository var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); - return new OkResponse(fileConfiguration); + return Task.FromResult>(new OkResponse(fileConfiguration)); } - public async Task Set(FileConfiguration fileConfiguration) + public Task Set(FileConfiguration fileConfiguration) { string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -45,8 +45,8 @@ namespace Ocelot.Configuration.Repository System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); } - - return new OkResponse(); + + return Task.FromResult(new OkResponse()); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs index 99f6a51b..9ce50ba6 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs @@ -12,19 +12,19 @@ namespace Ocelot.Configuration.Repository private IOcelotConfiguration _ocelotConfiguration; - public async Task> Get() + public Task> Get() { - return new OkResponse(_ocelotConfiguration); + return Task.FromResult>(new OkResponse(_ocelotConfiguration)); } - public async Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) + public Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) { lock (LockObject) { _ocelotConfiguration = ocelotConfiguration; } - return new OkResponse(); + return Task.FromResult(new OkResponse()); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs index 28f9bb65..257298b7 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Ocelot.Values; @@ -14,16 +13,16 @@ namespace Ocelot.ServiceDiscovery _configuration = configuration; } - public async Task> Get() + public Task> Get() { - return new List + return Task.FromResult(new List { new Service(_configuration.ServiceName, new ServiceHostAndPort(_configuration.HostName, _configuration.Port), "doesnt matter with service fabric", "doesnt matter with service fabric", new List()) - }; + }); } } } diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index b5a2711e..0e0db202 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -221,13 +221,15 @@ namespace Ocelot.AcceptanceTests .Configure(app => { app.UsePathBase(basePath); - app.Run(async context => + app.Run(context => { context.Response.OnStarting(() => { context.Response.Headers.Add(headerKey, headerValue); context.Response.StatusCode = statusCode; return Task.CompletedTask; }); + + return Task.CompletedTask; }); }) .Build(); diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index e5cb64ae..e287d5f4 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -6,6 +6,7 @@ namespace Ocelot.UnitTests.Authentication using System.Collections.Generic; using System.IO; using System.Text; + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Authentication.Middleware; @@ -44,10 +45,11 @@ namespace Ocelot.UnitTests.Authentication private void WhenICallTheMiddleware() { - _next = async (context) => { + _next = (context) => { byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); - MemoryStream stream = new MemoryStream(byteArray); + var stream = new MemoryStream(byteArray); context.HttpContext.Response.Body = stream; + return Task.CompletedTask; }; _middleware = new AuthenticationMiddleware(_next, _factory.Object); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); @@ -55,10 +57,11 @@ namespace Ocelot.UnitTests.Authentication private void GivenTheTestServerPipelineIsConfigured() { - _next = async (context) => { + _next = (context) => { byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); - MemoryStream stream = new MemoryStream(byteArray); + var stream = new MemoryStream(byteArray); context.HttpContext.Response.Body = stream; + return Task.CompletedTask; }; } diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 6f1d54f3..472c8643 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -4,18 +4,17 @@ namespace Ocelot.UnitTests.Authorization { using System.Collections.Generic; using System.Security.Claims; + using System.Threading.Tasks; using Moq; using Ocelot.Authorisation; using Ocelot.Authorisation.Middleware; using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; using Ocelot.Responses; using TestStack.BDDfy; using Xunit; using Microsoft.AspNetCore.Http; - using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Configuration; public class AuthorisationMiddlewareTests @@ -36,9 +35,7 @@ namespace Ocelot.UnitTests.Authorization _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _middleware = new AuthorisationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object); } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs index c0a76b2a..210b1cb5 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs @@ -10,6 +10,7 @@ namespace Ocelot.UnitTests.Cache using Shouldly; using System.Collections.Generic; using System.Net.Http; + using System.Threading.Tasks; using Moq; using Ocelot.Cache; using Ocelot.Cache.Middleware; @@ -43,9 +44,7 @@ namespace Ocelot.UnitTests.Cache _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); - _next = async context => { - //do nothing.. - }; + _next = context => Task.CompletedTask; _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator); } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 31cc11a8..78911787 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -7,19 +7,16 @@ namespace Ocelot.UnitTests.Cache using System; using System.Collections.Generic; using System.Net.Http; - using Microsoft.AspNetCore.Builder; + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Cache; using Ocelot.Cache.Middleware; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; - using Ocelot.Responses; using TestStack.BDDfy; using Xunit; @@ -42,10 +39,7 @@ namespace Ocelot.UnitTests.Cache _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; - + _next = context => Task.CompletedTask; _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); } diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index f6bf052c..02e9d8fc 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -3,6 +3,7 @@ namespace Ocelot.UnitTests.Claims { using System.Collections.Generic; + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Claims; @@ -32,9 +33,7 @@ namespace Ocelot.UnitTests.Claims _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _middleware = new ClaimsBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index a5c18c22..d3363b00 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -4,9 +4,8 @@ using Ocelot.Middleware.Multiplexer; namespace Ocelot.UnitTests.DownstreamRouteFinder { using System.Collections.Generic; - using Microsoft.AspNetCore.Builder; + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -42,9 +41,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _multiplexer = new Mock(); _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _provider.Object, _multiplexer.Object); } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 0d678a7b..c226f245 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -6,23 +6,19 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator using System; using System.Collections.Generic; using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; + using System.Threading.Tasks; using Moq; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; - using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Responses; using Ocelot.Values; using TestStack.BDDfy; using Xunit; using Shouldly; - using Ocelot.DownstreamRouteFinder.Middleware; using Microsoft.AspNetCore.Http; public class DownstreamUrlCreatorMiddlewareTests @@ -43,9 +39,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _downstreamUrlTemplateVariableReplacer = new Mock(); _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; } [Fact] diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 0a69bf89..3085a64d 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -11,11 +11,12 @@ using Ocelot.Configuration.Builder; using Ocelot.Headers; using System.Net.Http; using Ocelot.Authorisation.Middleware; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Middleware; namespace Ocelot.UnitTests.Headers { + using System.Threading.Tasks; + public class HttpHeadersTransformationMiddlewareTests { private Mock _preReplacer; @@ -34,9 +35,7 @@ namespace Ocelot.UnitTests.Headers _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object); } diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 2f3a54d3..7f96b247 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -4,14 +4,12 @@ namespace Ocelot.UnitTests.Headers { using System.Collections.Generic; using System.Net.Http; - using Microsoft.AspNetCore.Builder; + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Headers; using Ocelot.Headers.Middleware; @@ -37,9 +35,7 @@ namespace Ocelot.UnitTests.Headers _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); _downstreamContext.DownstreamRequest = new HttpRequestMessage(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 242463a2..e03fe6e1 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -4,6 +4,7 @@ namespace Ocelot.UnitTests.LoadBalancer { using System.Collections.Generic; using System.Net.Http; + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Configuration; @@ -43,9 +44,7 @@ namespace Ocelot.UnitTests.LoadBalancer _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _downstreamContext.DownstreamRequest = _downstreamRequest; } diff --git a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs index adef00d5..1584c987 100644 --- a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs +++ b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs @@ -24,7 +24,7 @@ namespace Ocelot.UnitTests.Middleware { _aggregator = new Mock(); _context = new DownstreamContext(new DefaultHttpContext()); - _pipeline = async context => { _count++; }; + _pipeline = context => Task.FromResult(_count++); _multiplexer = new Multiplexer(_aggregator.Object); } diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index cfc0395d..163a0411 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -4,7 +4,6 @@ namespace Ocelot.UnitTests.QueryStrings { using System.Collections.Generic; using System.Net.Http; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -17,9 +16,8 @@ namespace Ocelot.UnitTests.QueryStrings using TestStack.BDDfy; using Xunit; using System.Security.Claims; - using Microsoft.AspNetCore.Builder; - using Ocelot.DownstreamRouteFinder.Middleware; using Microsoft.AspNetCore.Http; + using System.Threading.Tasks; public class QueryStringBuilderMiddlewareTests { @@ -36,9 +34,7 @@ namespace Ocelot.UnitTests.QueryStrings _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _addQueries = new Mock(); _downstreamContext.DownstreamRequest = new HttpRequestMessage(); _middleware = new QueryStringBuilderMiddleware(_next, _loggerFactory.Object, _addQueries.Object); diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 57d2fdc8..819e38dd 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -15,9 +15,9 @@ namespace Ocelot.UnitTests.RateLimit using Shouldly; using TestStack.BDDfy; using Xunit; - using Ocelot.DownstreamRouteFinder.Middleware; using Microsoft.Extensions.Caching.Memory; using System.IO; + using System.Threading.Tasks; public class ClientRateLimitMiddlewareTests { @@ -42,8 +42,7 @@ namespace Ocelot.UnitTests.RateLimit _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async (context) => { - }; + _next = context => Task.CompletedTask; _middleware = new ClientRateLimitMiddleware(_next, _loggerFactory.Object, _rateLimitCounterHandler); } diff --git a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs index f6bf4eb7..e27fc50f 100644 --- a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs @@ -9,6 +9,7 @@ namespace Ocelot.UnitTests.RequestId using System.Collections.Generic; using System.Linq; using System.Net.Http; + using System.Threading.Tasks; using Moq; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; @@ -40,8 +41,10 @@ namespace Ocelot.UnitTests.RequestId _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { + _next = context => + { context.HttpContext.Response.Headers.Add("LSRequestId", context.HttpContext.TraceIdentifier); + return Task.CompletedTask; }; _middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object); _downstreamContext.DownstreamRequest = _downstreamRequest; diff --git a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs index a53487a3..0db47a8f 100644 --- a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs +++ b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs @@ -17,12 +17,13 @@ namespace Ocelot.UnitTests.Requester } public int Order {get;private set;} + public DateTime TimeCalled {get;private set;} - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { TimeCalled = DateTime.Now; - return new HttpResponseMessage(); + return Task.FromResult(new HttpResponseMessage()); } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 154beee8..f4facf5e 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -13,6 +13,7 @@ namespace Ocelot.UnitTests.Requester using TestStack.BDDfy; using Xunit; using Shouldly; + using System.Threading.Tasks; public class HttpRequesterMiddlewareTests { @@ -30,9 +31,7 @@ namespace Ocelot.UnitTests.Requester _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _middleware = new HttpRequesterMiddleware(_next, _loggerFactory.Object, _requester.Object); } diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index 54b795a2..2e9ffcd6 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -5,6 +5,7 @@ namespace Ocelot.UnitTests.Responder { using Microsoft.AspNetCore.Http; using System.Net.Http; + using System.Threading.Tasks; using Moq; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Errors; @@ -32,9 +33,7 @@ namespace Ocelot.UnitTests.Responder _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - //do nothing - }; + _next = context => Task.CompletedTask; _middleware = new ResponderMiddleware(_next, _responder.Object, _loggerFactory.Object, _codeMapper.Object); } From ea95690253e98cfe7d182594ff6bb0a9cf9f65e1 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 6 Mar 2018 11:13:02 +0000 Subject: [PATCH 4/7] Fix remaining CS0649 violations --- test/Ocelot.AcceptanceTests/HeaderTests.cs | 6 ------ .../Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 0e0db202..5257b448 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -18,7 +18,6 @@ namespace Ocelot.AcceptanceTests { private IWebHost _builder; private readonly Steps _steps; - private string _downstreamPath; public HeaderTests() { @@ -237,11 +236,6 @@ namespace Ocelot.AcceptanceTests _builder.Start(); } - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); - } - public void Dispose() { _builder?.Dispose(); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index e8808f17..1e30a0d1 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -19,12 +19,13 @@ namespace Ocelot.UnitTests.LoadBalancer private readonly LoadBalancerHouse _loadBalancerHouse; private Response _getResult; private readonly Mock _factory; - private ServiceProviderConfiguration _serviceProviderConfig; + private readonly ServiceProviderConfiguration _serviceProviderConfig; public LoadBalancerHouseTests() { _factory = new Mock(); _loadBalancerHouse = new LoadBalancerHouse(_factory.Object); + _serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123); } [Fact] From 8d5c5341eb52f50d49cd5ce76d4e56fc205d62f3 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 6 Mar 2018 11:54:56 +0000 Subject: [PATCH 5/7] Turn some of the rules back on. --- codeanalysis.ruleset | 156 +++++++++++++------------------------------ 1 file changed, 46 insertions(+), 110 deletions(-) diff --git a/codeanalysis.ruleset b/codeanalysis.ruleset index ee66e99d..4b278ba8 100644 --- a/codeanalysis.ruleset +++ b/codeanalysis.ruleset @@ -1,74 +1,10 @@  - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -77,76 +13,76 @@ - + - - + + - + - - - - - - + + + + + + - - + + - - + + - - + + - - + + - - - - - + + + + + - + - + - + - - - - - + + + + + - - + + - - - + + + - - - + + + + - - - + + + - \ No newline at end of file From 5e9ee1f2ede09054e3444ac95fe83b231bbf939a Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 8 Mar 2018 07:32:06 +0000 Subject: [PATCH 6/7] #263 map all content specific headers to downstream request content property, make sure we dont map them to request specific headers, added a gzip encoding acceptance test (#267) --- src/Ocelot/Request/Mapper/RequestMapper.cs | 19 ++- test/Ocelot.AcceptanceTests/GzipTests.cs | 116 ++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 18 +++ .../Request/Mapper/RequestMapperTests.cs | 147 ++++++++++++++++-- 4 files changed, 286 insertions(+), 14 deletions(-) create mode 100644 test/Ocelot.AcceptanceTests/GzipTests.cs diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index 9af0d08e..ee04a551 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -47,11 +47,28 @@ var content = new ByteArrayContent(await ToByteArray(request.Body)); - content.Headers.TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); + content.Headers + .TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); + AddHeaderIfExistsOnRequest("Content-Language", content, request); + AddHeaderIfExistsOnRequest("Content-Location", content, request); + AddHeaderIfExistsOnRequest("Content-Range", content, request); + AddHeaderIfExistsOnRequest("Content-MD5", content, request); + AddHeaderIfExistsOnRequest("Content-Disposition", content, request); + AddHeaderIfExistsOnRequest("Content-Encoding", content, request); + return content; } + private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request) + { + if(request.Headers.ContainsKey(key)) + { + content.Headers + .TryAddWithoutValidation(key, request.Headers[key].ToList()); + } + } + private HttpMethod MapMethod(HttpRequest request) { return new HttpMethod(request.Method); diff --git a/test/Ocelot.AcceptanceTests/GzipTests.cs b/test/Ocelot.AcceptanceTests/GzipTests.cs new file mode 100644 index 00000000..9c04314c --- /dev/null +++ b/test/Ocelot.AcceptanceTests/GzipTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +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 GzipTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public GzipTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + } + } + }; + + var input = "people"; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura", "\"people\"")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasGzipContent(input)) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody, string expected) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + if(context.Request.Headers.TryGetValue("Content-Encoding", out var contentEncoding)) + { + contentEncoding.First().ShouldBe("gzip"); + + string text = null; + using (var decompress = new GZipStream(context.Request.Body, CompressionMode.Decompress)) + { + using (var sr = new StreamReader(decompress)) { + text = sr.ReadToEnd(); + } + } + + if(text != expected) + { + throw new Exception("not gzipped"); + } + + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + }); + }) + .Build(); + + _builder.Start(); + } + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 61a01059..755dd1a1 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -22,6 +22,8 @@ using Ocelot.Middleware; using Shouldly; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using Ocelot.AcceptanceTests.Caching; +using System.IO.Compression; +using System.Text; namespace Ocelot.AcceptanceTests { @@ -582,6 +584,22 @@ namespace Ocelot.AcceptanceTests _postContent = new StringContent(postcontent); } + public void GivenThePostHasGzipContent(object input) + { + string json = JsonConvert.SerializeObject(input); + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + MemoryStream ms = new MemoryStream(); + using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) + { + gzip.Write(jsonBytes, 0, jsonBytes.Length); + } + ms.Position = 0; + StreamContent content = new StreamContent(ms); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + content.Headers.ContentEncoding.Add("gzip"); + _postContent = content; + } + public void ThenTheResponseBodyShouldBe(string expectedBody) { _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index e30a772a..ba719a45 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -15,6 +15,7 @@ using System; using System.IO; using System.Text; + using System.Security.Cryptography; public class RequestMapperTests { @@ -118,19 +119,151 @@ } [Fact] - public void Should_map_content_type_header() + public void Should_handle_no_content() { + this.Given(_ => GivenTheInputRequestHasNoContent()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + [Fact] + public void Should_map_content_headers() + { + byte[] md5bytes = new byte[0]; + using (var md5 = MD5.Create()) + { + md5bytes = md5.ComputeHash(Encoding.UTF8.GetBytes("some md5")); + } + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheContentEncodingIs("gzip, compress")) + .And(_ => GivenTheContentLanguageIs("english")) + .And(_ => GivenTheContentLocationIs("/my-receipts/38")) + .And(_ => GivenTheContentRangeIs("bytes 1-2/*")) + .And(_ => GivenTheContentDispositionIs("inline")) + .And(_ => GivenTheContentMD5Is(md5bytes)) .And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasAValidUri()) .When(_ => WhenMapped()) .Then(_ => ThenNoErrorIsReturned()) .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentEncodingHeader("gzip", "compress")) + .And(_ => ThenTheMappedRequestHasContentLanguageHeader("english")) + .And(_ => ThenTheMappedRequestHasContentLocationHeader("/my-receipts/38")) + .And(_ => ThenTheMappedRequestHasContentMD5Header(md5bytes)) + .And(_ => ThenTheMappedRequestHasContentRangeHeader()) + .And(_ => ThenTheMappedRequestHasContentDispositionHeader("inline")) .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .And(_ => ThenTheContentHeadersAreNotAddedToNonContentHeaders()) .BDDfy(); } + + [Fact] + public void should_not_add_content_headers() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheInputRequestHasMethod("POST")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .And(_ => ThenTheOtherContentTypeHeadersAreNotMapped()) + .BDDfy(); + } + + private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() + { + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentMD5"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentRange"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLanguage"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentEncoding"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLocation"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Length"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Type"); + } + + private void ThenTheOtherContentTypeHeadersAreNotMapped() + { + _mappedRequest.Data.Content.Headers.ContentDisposition.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentRange.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentLanguage.ShouldBeEmpty(); + _mappedRequest.Data.Content.Headers.ContentEncoding.ShouldBeEmpty(); + _mappedRequest.Data.Content.Headers.ContentLocation.ShouldBeNull(); + } + + private void ThenTheMappedRequestHasContentDispositionHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentDisposition.DispositionType.ShouldBe(expected); + } + + private void GivenTheContentDispositionIs(string input) + { + _inputRequest.Headers.Add("Content-Disposition", input); + } + + private void ThenTheMappedRequestHasContentMD5Header(byte[] expected) + { + _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBe(expected); + } + + private void GivenTheContentMD5Is(byte[] input) + { + var base64 = Convert.ToBase64String(input); + _inputRequest.Headers.Add("Content-MD5", base64); + } + + private void ThenTheMappedRequestHasContentRangeHeader() + { + _mappedRequest.Data.Content.Headers.ContentRange.From.ShouldBe(1); + _mappedRequest.Data.Content.Headers.ContentRange.To.ShouldBe(2); + } + + private void GivenTheContentRangeIs(string input) + { + _inputRequest.Headers.Add("Content-Range", input); + } + + private void ThenTheMappedRequestHasContentLocationHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentLocation.OriginalString.ShouldBe(expected); + } + + private void GivenTheContentLocationIs(string input) + { + _inputRequest.Headers.Add("Content-Location", input); + } + + private void ThenTheMappedRequestHasContentLanguageHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentLanguage.First().ShouldBe(expected); + } + + private void GivenTheContentLanguageIs(string input) + { + _inputRequest.Headers.Add("Content-Language", input); + } + + private void ThenTheMappedRequestHasContentEncodingHeader(string expected, string expectedTwo) + { + _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[0].ShouldBe(expected); + _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[1].ShouldBe(expectedTwo); + } + + private void GivenTheContentEncodingIs(string input) + { + _inputRequest.Headers.Add("Content-Encoding", input); + } + private void GivenTheContentTypeIs(string contentType) { _inputRequest.ContentType = contentType; @@ -146,18 +279,6 @@ _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); } - [Fact] - public void Should_handle_no_content() - { - this.Given(_ => GivenTheInputRequestHasNoContent()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoContent()) - .BDDfy(); - } - private void GivenTheInputRequestHasMethod(string method) { _inputRequest.Method = method; From a31a3ae0fcb4dbb1916f8dcb1f767bf36d95ebe2 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 9 Mar 2018 07:37:37 +0000 Subject: [PATCH 7/7] Delegating Handlers from container (#266) * #259 quickly hacked poc for this together, needs tidying up maybe * #266 +semver: breaking removed adding delegating handler funcs directly...i feel from container is enough --- docs/features/delegatinghandlers.rst | 21 +- .../DependencyInjection/IOcelotBuilder.cs | 8 +- .../DependencyInjection/OcelotBuilder.cs | 12 +- .../DelegatingHandlerHandlerHouse.cs | 58 -- .../DelegatingHandlerHandlerProvider.cs | 28 - ...DelegatingHandlerHandlerProviderFactory.cs | 30 +- src/Ocelot/Requester/HttpClientBuilder.cs | 12 +- .../Requester/HttpClientHttpRequester.cs | 8 +- .../IDelegatingHandlerHandlerHouse.cs | 10 - .../IDelegatingHandlerHandlerProvider.cs | 12 - ...DelegatingHandlerHandlerProviderFactory.cs | 7 +- .../HttpDelegatingHandlersTests.cs | 94 ++- test/Ocelot.AcceptanceTests/Steps.cs | 44 +- test/Ocelot.AcceptanceTests/Steps.cs.orig | 722 ++++++++++++++++++ .../DependencyInjection/OcelotBuilderTests.cs | 25 +- .../DelegatingHandlerHandlerHouseTests.cs | 137 ---- ...atingHandlerHandlerProviderFactoryTests.cs | 75 +- .../DelegatingHandlerHandlerProviderTests.cs | 62 -- .../Requester/FakeDelegatingHandler.cs | 18 + .../Requester/HttpClientBuilderTests.cs | 39 +- .../Requester/HttpClientHttpRequesterTest.cs | 9 +- 21 files changed, 975 insertions(+), 456 deletions(-) delete mode 100644 src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs delete mode 100644 src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs delete mode 100644 src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs delete mode 100644 src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs create mode 100644 test/Ocelot.AcceptanceTests/Steps.cs.orig delete mode 100644 test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs delete mode 100644 test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index a6f2e4e9..b3108534 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -4,28 +4,21 @@ Delegating Handers Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ and I decided that it was going to be useful in various ways. Usage -^^^^^^ +^^^^^ In order to add delegating handlers to the HttpClient transport you need to do the following. -.. code-block:: csharp - - services.AddOcelot() - .AddDelegatingHandler(() => new FakeHandler()) - .AddDelegatingHandler(() => new FakeHandler()); - -Or for singleton like behaviour.. +This will register the Handlers as singletons. Because Ocelot caches the HttpClient for the downstream services to avoid +socket exhaustion (well known http client issue) you can only register singleton handlers. .. code-block:: csharp - var handlerOne = new FakeHandler(); - var handlerTwo = new FakeHandler(); - services.AddOcelot() - .AddDelegatingHandler(() => handlerOne) - .AddDelegatingHandler(() => handlerTwo); + .AddDelegatingHandler() + .AddDelegatingHandler() -You can have as many DelegatingHandlers as you want and they are run in a first in first out order. If you are using Ocelot's QoS functionality then that will always be run after your last delegating handler. +You can have as many DelegatingHandlers as you want and they are run in a first in first out order. If you are using Ocelot's QoS functionality then that will always be run after your last delegating handler. If you are also registering handlers in DI these will be +run first. In order to create a class that can be used a delegating handler it must look as follows diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 48db5ef7..ad1e4777 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -3,16 +3,22 @@ using CacheManager.Core; using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; +using Ocelot.Requester; namespace Ocelot.DependencyInjection { public interface IOcelotBuilder { IOcelotBuilder AddStoreOcelotConfigurationInConsul(); + IOcelotBuilder AddCacheManager(Action settings); + IOcelotBuilder AddOpenTracing(Action settings); + IOcelotAdministrationBuilder AddAdministration(string path, string secret); + IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); - IOcelotBuilder AddDelegatingHandler(Func delegatingHandler); + + IOcelotBuilder AddDelegatingHandler() where T : DelegatingHandler; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index e1ef6b42..75dddb1d 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -57,7 +57,6 @@ namespace Ocelot.DependencyInjection { private readonly IServiceCollection _services; private readonly IConfiguration _configurationRoot; - private readonly IDelegatingHandlerHandlerProvider _provider; public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) { @@ -120,8 +119,7 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository @@ -144,9 +142,6 @@ namespace Ocelot.DependencyInjection _services.AddWebEncoders(); _services.AddSingleton(new NullAdministrationPath()); - //these get picked out later and added to http request - _provider = new DelegatingHandlerHandlerProvider(); - _services.TryAddSingleton(_provider); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.AddSingleton(); @@ -187,9 +182,10 @@ namespace Ocelot.DependencyInjection return new OcelotAdministrationBuilder(_services, _configurationRoot); } - public IOcelotBuilder AddDelegatingHandler(Func delegatingHandler) + public IOcelotBuilder AddDelegatingHandler() + where THandler : DelegatingHandler { - _provider.Add(delegatingHandler); + _services.AddSingleton(); return this; } diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs deleted file mode 100644 index eec395fb..00000000 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Requester -{ - public class DelegatingHandlerHandlerHouse : IDelegatingHandlerHandlerHouse - { - private readonly IDelegatingHandlerHandlerProviderFactory _factory; - private readonly ConcurrentDictionary _housed; - - public DelegatingHandlerHandlerHouse(IDelegatingHandlerHandlerProviderFactory factory) - { - _factory = factory; - _housed = new ConcurrentDictionary(); - } - - public Response Get(DownstreamReRoute request) - { - try - { - if (_housed.TryGetValue(request.ReRouteKey, out var provider)) - { - //todo once day we might need a check here to see if we need to create a new provider - provider = _housed[request.ReRouteKey]; - return new OkResponse(provider); - } - - //todo - unit test for this - var providerResponse = _factory.Get(request); - - if (providerResponse.IsError) - { - return new ErrorResponse(providerResponse.Errors); - } - - provider = providerResponse.Data; - AddHoused(request.ReRouteKey, provider); - return new OkResponse(provider); - } - catch (Exception ex) - { - return new ErrorResponse(new List() - { - new UnableToFindDelegatingHandlerProviderError($"unabe to find delegating handler provider for {request.ReRouteKey} exception is {ex}") - }); - } - } - - private void AddHoused(string key, IDelegatingHandlerHandlerProvider provider) - { - _housed.AddOrUpdate(key, provider, (k, v) => provider); - } - } -} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs deleted file mode 100644 index 71684c52..00000000 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; - -namespace Ocelot.Requester -{ - public class DelegatingHandlerHandlerProvider : IDelegatingHandlerHandlerProvider - { - private readonly Dictionary> _handlers; - - public DelegatingHandlerHandlerProvider() - { - _handlers = new Dictionary>(); - } - - public void Add(Func handler) - { - var key = _handlers.Count == 0 ? 0 : _handlers.Count + 1; - _handlers[key] = handler; - } - - public List> Get() - { - return _handlers.Count > 0 ? _handlers.OrderBy(x => x.Key).Select(x => x.Value).ToList() : new List>(); - } - } -} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs index 468b6013..4e242137 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Logging; using Ocelot.Requester.QoS; @@ -7,38 +9,38 @@ using Ocelot.Responses; namespace Ocelot.Requester { - public class DelegatingHandlerHandlerProviderFactory : IDelegatingHandlerHandlerProviderFactory + public class DelegatingHandlerHandlerFactory : IDelegatingHandlerHandlerFactory { private readonly ITracingHandlerFactory _factory; private readonly IOcelotLoggerFactory _loggerFactory; - private readonly IDelegatingHandlerHandlerProvider _allRoutesProvider; private readonly IQosProviderHouse _qosProviderHouse; + private readonly IServiceProvider _serviceProvider; - public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, - IDelegatingHandlerHandlerProvider allRoutesProvider, + public DelegatingHandlerHandlerFactory(IOcelotLoggerFactory loggerFactory, ITracingHandlerFactory factory, - IQosProviderHouse qosProviderHouse) + IQosProviderHouse qosProviderHouse, + IServiceProvider serviceProvider) { + _serviceProvider = serviceProvider; _factory = factory; _loggerFactory = loggerFactory; - _allRoutesProvider = allRoutesProvider; _qosProviderHouse = qosProviderHouse; } - public Response Get(DownstreamReRoute request) + public Response>> Get(DownstreamReRoute request) { - var handlersAppliedToAll = _allRoutesProvider.Get(); + var handlersAppliedToAll = _serviceProvider.GetServices(); - var provider = new DelegatingHandlerHandlerProvider(); + var handlers = new List>(); foreach (var handler in handlersAppliedToAll) { - provider.Add(handler); + handlers.Add(() => handler); } if (request.HttpHandlerOptions.UseTracing) { - provider.Add(() => (DelegatingHandler)_factory.Get()); + handlers.Add(() => (DelegatingHandler)_factory.Get()); } if (request.IsQos) @@ -47,13 +49,13 @@ namespace Ocelot.Requester if (qosProvider.IsError) { - return new ErrorResponse(qosProvider.Errors); + return new ErrorResponse>>(qosProvider.Errors); } - provider.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); + handlers.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); } - return new OkResponse(provider); + return new OkResponse>>(handlers); } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 2d3a0f36..b5604081 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -6,11 +6,11 @@ namespace Ocelot.Requester { public class HttpClientBuilder : IHttpClientBuilder { - private readonly IDelegatingHandlerHandlerHouse _house; + private readonly IDelegatingHandlerHandlerFactory _factory; - public HttpClientBuilder(IDelegatingHandlerHandlerHouse house) + public HttpClientBuilder(IDelegatingHandlerHandlerFactory house) { - _house = house; + _factory = house; } public IHttpClient Create(DownstreamReRoute request) @@ -24,11 +24,9 @@ namespace Ocelot.Requester private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request) { - var provider = _house.Get(request); - - var handlers = provider.Data.Get(); - //todo handle error + var handlers = _factory.Get(request).Data; + handlers .Select(handler => handler) .Reverse() diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index de9ea1c6..f2d32733 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -13,20 +13,20 @@ namespace Ocelot.Requester { private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - private readonly IDelegatingHandlerHandlerHouse _house; + private readonly IDelegatingHandlerHandlerFactory _factory; public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers, - IDelegatingHandlerHandlerHouse house) + IDelegatingHandlerHandlerFactory house) { _logger = loggerFactory.CreateLogger(); _cacheHandlers = cacheHandlers; - _house = house; + _factory = house; } public async Task> GetResponse(DownstreamContext request) { - var builder = new HttpClientBuilder(_house); + var builder = new HttpClientBuilder(_factory); var cacheKey = GetCacheKey(request); diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs deleted file mode 100644 index b236ed16..00000000 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Requester -{ - public interface IDelegatingHandlerHandlerHouse - { - Response Get(DownstreamReRoute request); - } -} diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs deleted file mode 100644 index addaaeb7..00000000 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; - -namespace Ocelot.Requester -{ - public interface IDelegatingHandlerHandlerProvider - { - void Add(Func handler); - List> Get(); - } -} diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs index c77a62bd..53b6a73c 100644 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs @@ -1,10 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; using Ocelot.Configuration; using Ocelot.Responses; namespace Ocelot.Requester { - public interface IDelegatingHandlerHandlerProviderFactory + public interface IDelegatingHandlerHandlerFactory { - Response Get(DownstreamReRoute request); + Response>> Get(DownstreamReRoute request); } } diff --git a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs index c54294cd..72f1f341 100644 --- a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs @@ -26,8 +26,9 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] - public void should_call_handlers() + public void should_call_di_handlers() { var configuration = new FileConfiguration { @@ -42,7 +43,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 61879, + Port = 7187, } }, UpstreamPathTemplate = "/", @@ -51,27 +52,98 @@ namespace Ocelot.AcceptanceTests } }; - var handlerOne = new FakeHandler(); - var handlerTwo = new FakeHandler(); - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:61879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7187", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningWithHandlers(handlerOne, handlerTwo)) + .And(x => _steps.GivenOcelotIsRunningWithHandlersRegisteredInDi()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => ThenTheHandlersAreCalledCorrectly(handlerOne, handlerTwo)) + .And(x => ThenTheHandlersAreCalledCorrectly()) .BDDfy(); } - private void ThenTheHandlersAreCalledCorrectly(FakeHandler one, FakeHandler two) + + [Fact] + public void should_call_di_handlers_with_dependency() { - one.TimeCalled.ShouldBeLessThan(two.TimeCalled); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 7188, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var dependency = new FakeDependency(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7188", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithHandlersRegisteredInDi(dependency)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheDependencyIsCalled(dependency)) + .BDDfy(); } + private void ThenTheDependencyIsCalled(FakeDependency dependency) + { + dependency.Called.ShouldBeTrue(); + } + + private void ThenTheHandlersAreCalledCorrectly() + { + FakeHandler.TimeCalled.ShouldBeLessThan(FakeHandlerTwo.TimeCalled); + } + + public class FakeDependency + { + public bool Called; + } + + class FakeHandlerWithDependency : DelegatingHandler + { + private FakeDependency _dependency; + + public FakeHandlerWithDependency(FakeDependency dependency) + { + _dependency = dependency; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + _dependency.Called = true; + return await base.SendAsync(request, cancellationToken); + } + } + class FakeHandler : DelegatingHandler { - public DateTime TimeCalled { get; private set; } + public static DateTime TimeCalled { get; private set; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return await base.SendAsync(request, cancellationToken); + } + } + class FakeHandlerTwo : DelegatingHandler + { + public static DateTime TimeCalled { get; private set; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 755dd1a1..0ea0e6b6 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -24,6 +24,7 @@ using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBui using Ocelot.AcceptanceTests.Caching; using System.IO.Compression; using System.Text; +using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; namespace Ocelot.AcceptanceTests { @@ -174,10 +175,9 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - /// - /// 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. - /// - public void GivenOcelotIsRunningWithHandlers(DelegatingHandler handlerOne, DelegatingHandler handlerTwo) + public void GivenOcelotIsRunningWithHandlersRegisteredInDi() + where TOne : DelegatingHandler + where TWo : DelegatingHandler { _webHostBuilder = new WebHostBuilder(); @@ -195,8 +195,40 @@ namespace Ocelot.AcceptanceTests { s.AddSingleton(_webHostBuilder); s.AddOcelot() - .AddDelegatingHandler(() => handlerOne) - .AddDelegatingHandler(() => handlerTwo); + .AddDelegatingHandler() + .AddDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithHandlersRegisteredInDi(FakeDependency dependency) + where TOne : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(dependency); + s.AddOcelot() + .AddDelegatingHandler(); }) .Configure(a => { diff --git a/test/Ocelot.AcceptanceTests/Steps.cs.orig b/test/Ocelot.AcceptanceTests/Steps.cs.orig new file mode 100644 index 00000000..84c119ab --- /dev/null +++ b/test/Ocelot.AcceptanceTests/Steps.cs.orig @@ -0,0 +1,722 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Shouldly; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; +using Ocelot.AcceptanceTests.Caching; +<<<<<<< HEAD +using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; +||||||| merged common ancestors +======= +using System.IO.Compression; +using System.Text; +>>>>>>> develop + +namespace Ocelot.AcceptanceTests +{ + public class Steps : IDisposable + { + private TestServer _ocelotServer; + private HttpClient _ocelotClient; + private HttpResponseMessage _response; + private HttpContent _postContent; + private BearerToken _token; + public HttpClient OcelotClient => _ocelotClient; + public string RequestIdKey = "OcRequestId"; + private readonly Random _random; + private IWebHostBuilder _webHostBuilder; + + public Steps() + { + _random = new Random(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = TestConfiguration.ConfigurationPath; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) + { + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + /// + /// 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. + /// + public void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddOpenTracing(option => + { + //this is the url that the butterfly collector server is running on... + option.CollectorUrl = butterflyUrl; + option.Service = "Ocelot"; + }); + }) + .Configure(app => + { + app.Use(async (context, next) => + { + await next.Invoke(); + }); + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + +/* + public void GivenIHaveAddedXForwardedForHeader(string value) + { + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value); + }*/ + + public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func callback) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseMiddleware(callback); + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithHandlersRegisteredInDi() + where TOne : DelegatingHandler + where TWo : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelot() + .AddDelegatingHandler() + .AddDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithHandlersRegisteredInDi(FakeDependency dependency) + where TOne : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(dependency); + s.AddOcelot() + .AddDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + /// + /// 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. + /// + public void GivenOcelotIsRunning(Action options, string authenticationProviderKey) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + s.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void ThenTheResponseHeaderIs(string key, string value) + { + var header = _response.Headers.GetValues(key); + header.First().ShouldBe(value); + } + + public void GivenOcelotIsRunningUsingJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningUsingConsulToStoreConfig() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot().AddStoreOcelotConfigurationInConsul(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }) + .AddStoreOcelotConfigurationInConsul(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + internal void ThenTheResponseShouldBe(FileConfiguration expecteds) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); + } + } + + /// + /// 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. + /// + public void GivenOcelotIsRunning(OcelotPipelineConfiguration ocelotPipelineConfig) + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + var configuration = builder.Build(); + _webHostBuilder = new WebHostBuilder(); + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseConfiguration(configuration) + .ConfigureServices(s => + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + s.AddOcelot(configuration); + }) + .ConfigureLogging(l => + { + l.AddConsole(); + l.AddDebug(); + }) + .Configure(a => + { + a.UseOcelot(ocelotPipelineConfig).Wait(); + })); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenIHaveAddedATokenToMyRequest() + { + _ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + public void GivenIHaveAToken(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveATokenForApiReadOnlyScope(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api.readOnly"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveATokenForApi2(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api2"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "admin"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _ocelotClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + public void VerifyIdentiryServerStarted(string url) + { + using (var httpClient = new HttpClient()) + { + var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; + response.EnsureSuccessStatusCode(); + } + } + + public void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.GetAsync(url).Result; + } + + public void GivenIAddAHeader(string key, string value) + { + _ocelotClient.DefaultRequestHeaders.Add(key, value); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + tasks[i] = GetForServiceDiscoveryTest(urlCopy); + Thread.Sleep(_random.Next(40, 60)); + } + + Task.WaitAll(tasks); + } + + private async Task GetForServiceDiscoveryTest(string url) + { + var response = await _ocelotClient.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + int count = int.Parse(content); + count.ShouldBeGreaterThan(0); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times) + { + for (int i = 0; i < times; i++) + { + var clientId = "ocelotclient1"; + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + request.Headers.Add("ClientId", clientId); + _response = _ocelotClient.SendAsync(request).Result; + } + } + + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) + { + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); + + _response = _ocelotClient.GetAsync(url).Result; + } + + public void WhenIPostUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.PostAsync(url, _postContent).Result; + } + + public void GivenThePostHasContent(string postcontent) + { + _postContent = new StringContent(postcontent); + } + + public void GivenThePostHasGzipContent(object input) + { + string json = JsonConvert.SerializeObject(input); + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + MemoryStream ms = new MemoryStream(); + using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) + { + gzip.Write(jsonBytes, 0, jsonBytes.Length); + } + ms.Position = 0; + StreamContent content = new StreamContent(ms); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + content.Headers.ContentEncoding.Add("gzip"); + _postContent = content; + } + + public void ThenTheResponseBodyShouldBe(string expectedBody) + { + _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); + } + + public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) + { + var responseStatusCode = (int)_response.StatusCode; + responseStatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + _ocelotClient?.Dispose(); + _ocelotServer?.Dispose(); + } + + public void ThenTheRequestIdIsReturned() + { + _response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty(); + } + + public void ThenTheRequestIdIsReturned(string expected) + { + _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); + } + + public void ThenTheContentLengthIs(int expected) + { + _response.Content.Headers.ContentLength.ShouldBe(expected); + } + + public void WhenIMakeLotsOfDifferentRequestsToTheApiGateway() + { + int numberOfRequests = 100; + var aggregateUrl = "/"; + var aggregateExpected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; + var tomUrl = "/tom"; + var tomExpected = "{Hello from Tom}"; + var lauraUrl = "/laura"; + var lauraExpected = "{Hello from Laura}"; + var random = new Random(); + + var aggregateTasks = new Task[numberOfRequests]; + + for (int i = 0; i < numberOfRequests; i++) + { + aggregateTasks[i] = Fire(aggregateUrl, aggregateExpected, random); + } + + var tomTasks = new Task[numberOfRequests]; + + for (int i = 0; i < numberOfRequests; i++) + { + tomTasks[i] = Fire(tomUrl, tomExpected, random); + } + + var lauraTasks = new Task[numberOfRequests]; + + for (int i = 0; i < numberOfRequests; i++) + { + lauraTasks[i] = Fire(lauraUrl, lauraExpected, random); + } + + Task.WaitAll(lauraTasks); + Task.WaitAll(tomTasks); + Task.WaitAll(aggregateTasks); + } + + private async Task Fire(string url, string expectedBody, Random random) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + await Task.Delay(random.Next(0, 2)); + var response = await _ocelotClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + content.ShouldBe(expectedBody); + } + } +} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index c2f35990..4b4e328b 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -41,15 +41,13 @@ namespace Ocelot.UnitTests.DependencyInjection private Exception _ex; [Fact] - public void should_add_delegating_handlers() + public void should_add_delegating_handlers_with_di() { - var fakeOne = new FakeDelegatingHandler(0); - var fakeTwo = new FakeDelegatingHandler(1); this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddDelegate(fakeOne)) - .And(x => AddDelegate(fakeTwo)) - .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .When(x => AddDelegate()) + .And(x => AddDelegate()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) .BDDfy(); } @@ -164,15 +162,12 @@ namespace Ocelot.UnitTests.DependencyInjection path.Path.ShouldBe("/administration"); } - private void ThenTheProviderIsRegisteredAndReturnsHandlers() + private void ThenTheProviderIsRegisteredAndReturnsHandlers() { _serviceProvider = _services.BuildServiceProvider(); - var provider = _serviceProvider.GetService(); - var handlers = provider.Get(); - var handler = (FakeDelegatingHandler)handlers[0].Invoke(); - handler.Order.ShouldBe(0); - handler = (FakeDelegatingHandler)handlers[1].Invoke(); - handler.Order.ShouldBe(1); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); } private void OnlyOneVersionOfEachCacheIsRegistered() @@ -213,9 +208,9 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void AddDelegate(DelegatingHandler handler) + private void AddDelegate() where T : DelegatingHandler { - _ocelotBuilder.AddDelegatingHandler(() => handler); + _ocelotBuilder.AddDelegatingHandler(); } private void ThenAnOcelotBuilderIsReturned() diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs deleted file mode 100644 index 8df95ec1..00000000 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Net.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Errors; -using Ocelot.Requester; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class DelegatingHandlerHandlerHouseTests - { - private readonly DelegatingHandlerHandlerHouse _house; - private Mock _factory; - private readonly Mock _provider; - private DownstreamReRoute _request; - private Response _result; - - public DelegatingHandlerHandlerHouseTests() - { - _provider = new Mock(); - _factory = new Mock(); - _house = new DelegatingHandlerHandlerHouse(_factory.Object); - } - - [Fact] - public void should_create_and_store_provider() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderReturns()) - .When(x => WhenIGet()) - .Then(x => ThenTheFactoryIsCalled(1)) - .And(x => ThenTheProviderIsNotNull()) - .BDDfy(); - } - - [Fact] - public void should_get_provider() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderReturns()) - .And(x => WhenIGet()) - .And(x => GivenTheFactoryIsCleared()) - .When(x => WhenIGet()) - .Then(x => ThenTheFactoryIsCalled(0)) - .And(x => ThenTheProviderIsNotNull()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderThrows()) - .When(x => WhenIGet()) - .And(x => ThenAnErrorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_factory_errors() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderReturnsError()) - .When(x => WhenIGet()) - .Then(x => ThenAnUnknownErrorIsReturned()) - .BDDfy(); - } - - private void ThenAnUnknownErrorIsReturned() - { - _result.IsError.ShouldBeTrue(); - } - - private void ThenAnErrorIsReturned() - { - _result.IsError.ShouldBeTrue(); - _result.Errors[0].ShouldBeOfType(); - } - - private void GivenTheProviderThrows() - { - _factory.Setup(x => x.Get(It.IsAny())).Throws(); - } - - private void GivenTheFactoryIsCleared() - { - _factory = new Mock(); - } - - private void ThenTheProviderIsNotNull() - { - _result.Data.ShouldBe(_provider.Object); - } - - private void WhenIGet() - { - _result = _house.Get(_request); - } - - private void GivenTheRequest(DownstreamReRoute request) - { - _request = request; - } - - private void GivenTheProviderReturns() - { - _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(_provider.Object)); - } - - private void GivenTheProviderReturnsError() - { - _factory.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(It.IsAny())); - } - - private void ThenTheFactoryIsCalled(int times) - { - _factory.Verify(x => x.Get(_request), Times.Exactly(times)); - } - } -} diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index c63e04d2..793f3601 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -17,50 +18,35 @@ namespace Ocelot.UnitTests.Requester { public class DelegatingHandlerHandlerProviderFactoryTests { - private readonly DelegatingHandlerHandlerProviderFactory _factory; + private DelegatingHandlerHandlerFactory _factory; private Mock _loggerFactory; private DownstreamReRoute _request; - private Response _provider; - private readonly Mock _allRoutesProvider; + private Response>> _provider; private readonly Mock _qosProviderHouse; private readonly Mock _tracingFactory; + private IServiceProvider _serviceProvider; public DelegatingHandlerHandlerProviderFactoryTests() { _tracingFactory = new Mock(); _qosProviderHouse = new Mock(); - _allRoutesProvider = new Mock(); _loggerFactory = new Mock(); - _factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, _tracingFactory.Object, _qosProviderHouse.Object); - } - - private void GivenTheQosProviderHouseReturns(Response qosProvider) - { - _qosProviderHouse - .Setup(x => x.Get(It.IsAny())) - .Returns(qosProvider); } [Fact] public void should_all_from_all_routes_provider_and_qos() { - var handlers = new List> - { - () => new FakeDelegatingHandler(0), - () => new FakeDelegatingHandler(1) - }; - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) - .And(x => GivenTheAllRoutesProviderReturns(handlers)) + .And(x => GivenTheServiceProviderReturns()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) .And(x => ThenTheDelegatesAreAddedCorrectly()) .And(x => ThenItIsPolly(2)) - .BDDfy(); + .BDDfy(); } [Fact] @@ -70,7 +56,7 @@ namespace Ocelot.UnitTests.Requester .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) - .And(x => GivenTheAllRoutesProviderReturns()) + .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenNoDelegatesAreInTheProvider()) .BDDfy(); @@ -84,7 +70,7 @@ namespace Ocelot.UnitTests.Requester this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) - .And(x => GivenTheAllRoutesProviderReturns()) + .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(1)) .And(x => ThenItIsPolly(0)) @@ -99,12 +85,28 @@ namespace Ocelot.UnitTests.Requester this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new ErrorResponse(It.IsAny()))) - .And(x => GivenTheAllRoutesProviderReturns()) + .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenAnErrorIsReturned()) .BDDfy(); } + private void GivenTheServiceProviderReturns() + where TOne : DelegatingHandler + where TTwo : DelegatingHandler + { + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + private void GivenTheServiceProviderReturnsNothing() + { + IServiceCollection services = new ServiceCollection(); + _serviceProvider = services.BuildServiceProvider(); + } + private void ThenAnErrorIsReturned() { _provider.IsError.ShouldBeTrue(); @@ -112,29 +114,27 @@ namespace Ocelot.UnitTests.Requester private void ThenTheDelegatesAreAddedCorrectly() { - var delegates = _provider.Data.Get(); + var delegates = _provider.Data; + var del = delegates[0].Invoke(); var handler = (FakeDelegatingHandler) del; - handler.Order.ShouldBe(0); + handler.Order.ShouldBe(1); del = delegates[1].Invoke(); - handler = (FakeDelegatingHandler)del; - handler.Order.ShouldBe(1); + var handlerTwo = (FakeDelegatingHandlerTwo) del; + handlerTwo.Order.ShouldBe(2); } - private void GivenTheAllRoutesProviderReturns() + private void GivenTheQosProviderHouseReturns(Response qosProvider) { - _allRoutesProvider.Setup(x => x.Get()).Returns(new List>()); - } - - private void GivenTheAllRoutesProviderReturns(List> handlers) - { - _allRoutesProvider.Setup(x => x.Get()).Returns(handlers); + _qosProviderHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(qosProvider); } private void ThenItIsPolly(int i) { - var delegates = _provider.Data.Get(); + var delegates = _provider.Data; var del = delegates[i].Invoke(); del.ShouldBeOfType(); } @@ -142,7 +142,7 @@ namespace Ocelot.UnitTests.Requester private void ThenThereIsDelegatesInProvider(int count) { _provider.ShouldNotBeNull(); - _provider.Data.Get().Count.ShouldBe(count); + _provider.Data.Count.ShouldBe(count); } private void GivenTheFollowingRequest(DownstreamReRoute request) @@ -152,13 +152,14 @@ namespace Ocelot.UnitTests.Requester private void WhenIGet() { + _factory = new DelegatingHandlerHandlerFactory(_loggerFactory.Object, _tracingFactory.Object, _qosProviderHouse.Object, _serviceProvider); _provider = _factory.Get(_request); } private void ThenNoDelegatesAreInTheProvider() { _provider.ShouldNotBeNull(); - _provider.Data.Get().Count.ShouldBe(0); + _provider.Data.Count.ShouldBe(0); } } } diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs deleted file mode 100644 index d93e291a..00000000 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Requester; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class DelegatingHandlerHandlerProviderTests - { - private readonly DelegatingHandlerHandlerProvider _provider; - private List> _handlers; - - public DelegatingHandlerHandlerProviderTests() - { - _provider = new DelegatingHandlerHandlerProvider(); - } - - [Fact] - public void should_return_empty_list() - { - this.When(x => WhenIGet()) - .Then(x => ThenAnEmptyListIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_get_delegating_handlers_in_order_first_in_first_out() - { - this.Given(x => GivenTheHandlers()) - .When(x => WhenIGet()) - .Then(x => ThenTheHandlersAreReturnedInOrder()) - .BDDfy(); - } - - private void ThenAnEmptyListIsReturned() - { - _handlers.Count.ShouldBe(0); - } - - private void ThenTheHandlersAreReturnedInOrder() - { - var handler = (FakeDelegatingHandler)_handlers[0].Invoke(); - handler.Order.ShouldBe(0); - handler = (FakeDelegatingHandler)_handlers[1].Invoke(); - handler.Order.ShouldBe(1); - } - - private void WhenIGet() - { - _handlers = _provider.Get(); - } - - private void GivenTheHandlers() - { - _provider.Add(() => new FakeDelegatingHandler(0)); - _provider.Add(() => new FakeDelegatingHandler(1)); - } - } -} diff --git a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs index 0db47a8f..df68978b 100644 --- a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs +++ b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs @@ -9,6 +9,7 @@ namespace Ocelot.UnitTests.Requester { public FakeDelegatingHandler() { + Order = 1; } public FakeDelegatingHandler(int order) @@ -26,4 +27,21 @@ namespace Ocelot.UnitTests.Requester return Task.FromResult(new HttpResponseMessage()); } } + + public class FakeDelegatingHandlerTwo : DelegatingHandler + { + public FakeDelegatingHandlerTwo() + { + Order = 2; + } + + public int Order {get;private set;} + public DateTime TimeCalled {get;private set;} + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return new HttpResponseMessage(); + } + } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index b788c826..c4ab1341 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -15,25 +15,22 @@ namespace Ocelot.UnitTests.Requester public class HttpClientBuilderTests { private readonly HttpClientBuilder _builder; - private readonly Mock _house; - private readonly Mock _provider; + private readonly Mock _factory; private IHttpClient _httpClient; private HttpResponseMessage _response; private DownstreamReRoute _request; public HttpClientBuilderTests() { - _provider = new Mock(); - _house = new Mock(); - _builder = new HttpClientBuilder(_house.Object); + _factory = new Mock(); + _builder = new HttpClientBuilder(_factory.Object); } [Fact] public void should_build_http_client() { - this.Given(x => GivenTheProviderReturns()) + this.Given(x => GivenTheFactoryReturns()) .And(x => GivenARequest()) - .And(x => GivenTheHouseReturns()) .When(x => WhenIBuild()) .Then(x => ThenTheHttpClientShouldNotBeNull()) .BDDfy(); @@ -51,9 +48,8 @@ namespace Ocelot.UnitTests.Requester () => fakeTwo }; - this.Given(x => GivenTheProviderReturns(handlers)) + this.Given(x => GivenTheFactoryReturns(handlers)) .And(x => GivenARequest()) - .And(x => GivenTheHouseReturns()) .And(x => WhenIBuild()) .When(x => WhenICallTheClient()) .Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo)) @@ -69,13 +65,6 @@ namespace Ocelot.UnitTests.Requester _request = reRoute; } - private void GivenTheHouseReturns() - { - _house - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(_provider.Object)); - } - private void ThenSomethingIsReturned() { _response.ShouldNotBeNull(); @@ -91,18 +80,20 @@ namespace Ocelot.UnitTests.Requester fakeOne.TimeCalled.ShouldBeGreaterThan(fakeTwo.TimeCalled); } - private void GivenTheProviderReturns() + private void GivenTheFactoryReturns() { - _provider - .Setup(x => x.Get()) - .Returns(new List>(){ () => new FakeDelegatingHandler()}); + var handlers = new List>(){ () => new FakeDelegatingHandler()}; + + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); } - private void GivenTheProviderReturns(List> handlers) + private void GivenTheFactoryReturns(List> handlers) { - _provider - .Setup(x => x.Get()) - .Returns(handlers); + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); } private void WhenIBuild() diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index 6b01a8c1..f2c1f3a0 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -18,8 +18,7 @@ namespace Ocelot.UnitTests.Requester public class HttpClientHttpRequesterTest { private readonly Mock _cacheHandlers; - private Mock _house; - private Mock _provider; + private Mock _house; private Response _response; private readonly HttpClientHttpRequester _httpClientRequester; private DownstreamContext _request; @@ -28,10 +27,8 @@ namespace Ocelot.UnitTests.Requester public HttpClientHttpRequesterTest() { - _provider = new Mock(); - _provider.Setup(x => x.Get()).Returns(new List>()); - _house = new Mock(); - _house.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(_provider.Object)); + _house = new Mock(); + _house.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(new List>())); _logger = new Mock(); _loggerFactory = new Mock(); _loggerFactory