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 diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index a6f2e4e9..80dfa16b 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -1,33 +1,16 @@ 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. +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. Since then we extended it in `GitHub #264 `_. Usage -^^^^^^ +^^^^^ -In order to add delegating handlers to the HttpClient transport you need to do the following. +In order to add delegating handlers to the HttpClient transport you need to do two main things. -.. code-block:: csharp - - services.AddOcelot() - .AddDelegatingHandler(() => new FakeHandler()) - .AddDelegatingHandler(() => new FakeHandler()); - -Or for singleton like behaviour.. - -.. code-block:: csharp - - var handlerOne = new FakeHandler(); - var handlerTwo = new FakeHandler(); - - services.AddOcelot() - .AddDelegatingHandler(() => handlerOne) - .AddDelegatingHandler(() => handlerTwo); - -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. - -In order to create a class that can be used a delegating handler it must look as follows +First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the +asp.net core container so you can inject any other services you have registered into the constructor of your handler. .. code-block:: csharp @@ -40,4 +23,57 @@ In order to create a class that can be used a delegating handler it must look as } } -Hopefully other people will find this feature useful! \ No newline at end of file +Next you must add the handlers to Ocelot's container either as singleton like follows.. + +.. code-block:: csharp + + services.AddOcelot() + .AddSingletonDelegatingHandler() + .AddSingletonDelegatingHandler() + +Or transient as below... + +.. code-block:: csharp + + services.AddOcelot() + .AddTransientDelegatingHandler() + .AddTransientDelegatingHandler() + +Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of +the DelegatingHandler is to be applied to specific ReRoutes via configuration.json (more on that later). If it is set to true +then it becomes a global handler and will be applied to all ReRoutes. + +e.g. + +.. code-block:: csharp + + services.AddOcelot() + .AddSingletonDelegatingHandler(true) + +Or transient as below... + +.. code-block:: csharp + + services.AddOcelot() + .AddTransientDelegatingHandler(true) + +Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers +then you must add the following json to the specific ReRoute in configuration.json. The names in the array must match the class names of your +DelegatingHandlers for Ocelot to match them together. + +.. code-block:: json + + "DelegatingHandlers": [ + "FakeHandlerTwo", + "FakeHandler" + ] + +You can have as many DelegatingHandlers as you want and they are run in the following order: + +1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from configuration.json. +2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from configuration.json ordered as they are in the DelegatingHandlers array. +3. Tracing DelegatingHandler if enabled (see tracing docs). +4. QoS DelegatingHandler if enabled (see QoS docs). +5. The HttpClient sends the HttpRequestMessage. + +Hopefully other people will find this feature useful! 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/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs deleted file mode 100644 index aa60a8b0..00000000 --- a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ocelot.Authentication.Handler -{ - public enum SupportedAuthenticationProviders - { - IdentityServer, - Jwt - } -} diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs index 868b668b..27b0f197 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Builder; using Ocelot.Middleware.Pipeline; namespace Ocelot.Authentication.Middleware diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index f6c19522..4ace7dbc 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -1,16 +1,12 @@ -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using Ocelot.Configuration; - -namespace Ocelot.Authorisation.Middleware +namespace Ocelot.Authorisation.Middleware { using System.Collections.Generic; using System.Threading.Tasks; using Errors; - using Microsoft.AspNetCore.Http; - using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Middleware; + using Logging; + using Responses; + using Configuration; public class AuthorisationMiddleware : OcelotMiddleware { diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs index ffc30177..df00eb6b 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs @@ -2,8 +2,6 @@ using Ocelot.Middleware.Pipeline; namespace Ocelot.Authorisation.Middleware { - using Microsoft.AspNetCore.Builder; - public static class AuthorisationMiddlewareMiddlewareExtensions { public static IOcelotPipelineBuilder UseAuthorisationMiddleware(this IOcelotPipelineBuilder builder) diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorisation/ScopesAuthoriser.cs index ec433915..1654e2a1 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/ScopesAuthoriser.cs @@ -34,9 +34,9 @@ namespace Ocelot.Authorisation var userScopes = values.Data; - List matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); + var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); - if (matchesScopes == null || matchesScopes.Count == 0) + if (matchesScopes.Count == 0) { return new ErrorResponse(new List { @@ -48,4 +48,4 @@ namespace Ocelot.Authorisation return new OkResponse(true); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index 5f82f688..0e28aaa7 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -36,9 +36,12 @@ namespace Ocelot.Configuration.Builder private readonly List _downstreamAddresses; private string _upstreamHost; private string _key; + private List _delegatingHandlers; + public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); + _delegatingHandlers = new List(); } public DownstreamReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) @@ -215,6 +218,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithDelegatingHandlers(List delegatingHandlers) + { + _delegatingHandlers = delegatingHandlers; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -243,7 +252,8 @@ namespace Ocelot.Configuration.Builder _isAuthorised, _authenticationOptions, new PathTemplate(_downstreamPathTemplate), - _reRouteKey); + _reRouteKey, + _delegatingHandlers); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index a35c7d54..94cde717 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -212,6 +212,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) + .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 9322d5cf..6c9aa1dd 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -31,8 +31,10 @@ namespace Ocelot.Configuration bool isAuthorised, AuthenticationOptions authenticationOptions, PathTemplate downstreamPathTemplate, - string reRouteKey) + string reRouteKey, + List delegatingHandlers) { + DelegatingHandlers = delegatingHandlers; Key = key; UpstreamPathTemplate = upstreamPathTemplate; UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace ?? new List(); @@ -87,5 +89,6 @@ namespace Ocelot.Configuration public AuthenticationOptions AuthenticationOptions { get; private set; } public PathTemplate DownstreamPathTemplate { get; private set; } public string ReRouteKey { get; private set; } + public List DelegatingHandlers {get;private set;} } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 7169e62c..adc747f9 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -19,6 +19,7 @@ namespace Ocelot.Configuration.File HttpHandlerOptions = new FileHttpHandlerOptions(); UpstreamHeaderTransform = new Dictionary(); DownstreamHostAndPorts = new List(); + DelegatingHandlers = new List(); } public string DownstreamPathTemplate { get; set; } @@ -44,5 +45,6 @@ namespace Ocelot.Configuration.File public List DownstreamHostAndPorts {get;set;} public string UpstreamHost { get; set; } public string Key { get;set; } + public List DelegatingHandlers {get;set;} } } 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/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 48db5ef7..37cfa0d8 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -9,10 +9,19 @@ 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 AddSingletonDelegatingHandler(bool global = false) + where T : DelegatingHandler; + + IOcelotBuilder AddTransientDelegatingHandler(bool global = false) + where T : DelegatingHandler; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index e1ef6b42..9e2e763b 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,41 @@ namespace Ocelot.DependencyInjection return new OcelotAdministrationBuilder(_services, _configurationRoot); } - public IOcelotBuilder AddDelegatingHandler(Func delegatingHandler) + public IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) + where THandler : DelegatingHandler { - _provider.Add(delegatingHandler); + if(global) + { + _services.AddSingleton(); + _services.AddSingleton(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + } + else + { + _services.AddSingleton(); + } + + return this; + } + + public IOcelotBuilder AddTransientDelegatingHandler(bool global = false) + where THandler : DelegatingHandler + { + if(global) + { + _services.AddTransient(); + _services.AddTransient(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + } + else + { + _services.AddTransient(); + } + return this; } diff --git a/src/Ocelot/Authentication/BearerToken.cs b/src/Ocelot/Raft/BearerToken.cs similarity index 78% rename from src/Ocelot/Authentication/BearerToken.cs rename to src/Ocelot/Raft/BearerToken.cs index 5a52614a..983dada9 100644 --- a/src/Ocelot/Authentication/BearerToken.cs +++ b/src/Ocelot/Raft/BearerToken.cs @@ -1,16 +1,17 @@ -using Newtonsoft.Json; - -namespace Ocelot.Authentication -{ - class BearerToken - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("token_type")] - public string TokenType { get; set; } - } -} \ No newline at end of file +using Newtonsoft.Json; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + internal class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } +} 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/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs new file mode 100644 index 00000000..e0d9da82 --- /dev/null +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using Ocelot.Responses; + +namespace Ocelot.Requester +{ + public class DelegatingHandlerHandlerFactory : IDelegatingHandlerHandlerFactory + { + private readonly ITracingHandlerFactory _factory; + private readonly IOcelotLoggerFactory _loggerFactory; + private readonly IQosProviderHouse _qosProviderHouse; + private readonly IServiceProvider _serviceProvider; + + public DelegatingHandlerHandlerFactory(IOcelotLoggerFactory loggerFactory, + ITracingHandlerFactory factory, + IQosProviderHouse qosProviderHouse, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _factory = factory; + _loggerFactory = loggerFactory; + _qosProviderHouse = qosProviderHouse; + } + + public Response>> Get(DownstreamReRoute request) + { + var globalDelegatingHandlers = _serviceProvider + .GetServices() + .ToList(); + + var reRouteSpecificHandlers = _serviceProvider + .GetServices() + .ToList(); + + var handlers = new List>(); + + foreach (var handler in globalDelegatingHandlers) + { + if (GlobalIsInHandlersConfig(request, handler)) + { + reRouteSpecificHandlers.Add(handler.DelegatingHandler); + } + else + { + handlers.Add(() => handler.DelegatingHandler); + } + } + + if (request.DelegatingHandlers.Any()) + { + var sorted = SortByConfigOrder(request, reRouteSpecificHandlers); + + foreach (var handler in sorted) + { + handlers.Add(() => handler); + } + } + + if (request.HttpHandlerOptions.UseTracing) + { + handlers.Add(() => (DelegatingHandler)_factory.Get()); + } + + if (request.IsQos) + { + var qosProvider = _qosProviderHouse.Get(request); + + if (qosProvider.IsError) + { + return new ErrorResponse>>(qosProvider.Errors); + } + + handlers.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); + } + + return new OkResponse>>(handlers); + } + + private List SortByConfigOrder(DownstreamReRoute request, List reRouteSpecificHandlers) + { + return reRouteSpecificHandlers + .Where(x => request.DelegatingHandlers.Contains(x.GetType().Name)) + .OrderBy(d => + { + var type = d.GetType().Name; + var pos = request.DelegatingHandlers.IndexOf(type); + return pos; + }).ToList(); + } + + private bool GlobalIsInHandlersConfig(DownstreamReRoute request, GlobalDelegatingHandler handler) + { + return request.DelegatingHandlers.Contains(handler.DelegatingHandler.GetType().Name); + } + } +} 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 deleted file mode 100644 index 468b6013..00000000 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Ocelot.Responses; - -namespace Ocelot.Requester -{ - public class DelegatingHandlerHandlerProviderFactory : IDelegatingHandlerHandlerProviderFactory - { - private readonly ITracingHandlerFactory _factory; - private readonly IOcelotLoggerFactory _loggerFactory; - private readonly IDelegatingHandlerHandlerProvider _allRoutesProvider; - private readonly IQosProviderHouse _qosProviderHouse; - - public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, - IDelegatingHandlerHandlerProvider allRoutesProvider, - ITracingHandlerFactory factory, - IQosProviderHouse qosProviderHouse) - { - _factory = factory; - _loggerFactory = loggerFactory; - _allRoutesProvider = allRoutesProvider; - _qosProviderHouse = qosProviderHouse; - } - - public Response Get(DownstreamReRoute request) - { - var handlersAppliedToAll = _allRoutesProvider.Get(); - - var provider = new DelegatingHandlerHandlerProvider(); - - foreach (var handler in handlersAppliedToAll) - { - provider.Add(handler); - } - - if (request.HttpHandlerOptions.UseTracing) - { - provider.Add(() => (DelegatingHandler)_factory.Get()); - } - - if (request.IsQos) - { - var qosProvider = _qosProviderHouse.Get(request); - - if (qosProvider.IsError) - { - return new ErrorResponse(qosProvider.Errors); - } - - provider.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); - } - - return new OkResponse(provider); - } - } -} 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..c60e3b78 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -13,23 +13,23 @@ namespace Ocelot.Requester { private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - private readonly IDelegatingHandlerHandlerHouse _house; + private readonly IDelegatingHandlerHandlerFactory _factory; - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, + 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); - + var httpClient = GetHttpClient(cacheKey, builder, request); try @@ -72,8 +72,23 @@ namespace Ocelot.Requester private string GetCacheKey(DownstreamContext request) { var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}"; - + return baseUrl; } } + + public class ReRouteDelegatingHandler where T : DelegatingHandler + { + public T DelegatingHandler { get; private set; } + } + + public class GlobalDelegatingHandler + { + public GlobalDelegatingHandler(DelegatingHandler delegatingHandler) + { + DelegatingHandler = delegatingHandler; + } + + public DelegatingHandler DelegatingHandler { get; private set; } + } } diff --git a/src/Ocelot/Requester/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/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/GzipTests.cs b/test/Ocelot.AcceptanceTests/GzipTests.cs new file mode 100644 index 00000000..8403c305 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/GzipTests.cs @@ -0,0 +1,117 @@ +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/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index b5a2711e..350e1e52 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -using Shouldly; using TestStack.BDDfy; using Xunit; @@ -18,7 +17,6 @@ namespace Ocelot.AcceptanceTests { private IWebHost _builder; private readonly Steps _steps; - private string _downstreamPath; public HeaderTests() { @@ -221,13 +219,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(); @@ -235,11 +235,6 @@ namespace Ocelot.AcceptanceTests _builder.Start(); } - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); - } - public void Dispose() { _builder?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs index c54294cd..740ee6e7 100644 --- a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs @@ -27,7 +27,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_call_handlers() + public void should_call_re_route_ordered_specific_handlers() { var configuration = new FileConfiguration { @@ -42,7 +42,47 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 61879, + Port = 7197, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DelegatingHandlers = new List + { + "FakeHandlerTwo", + "FakeHandler" + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7197", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheOrderedHandlersAreCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_call_global_di_handlers() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 7187, } }, UpstreamPathTemplate = "/", @@ -51,32 +91,111 @@ 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.GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi()) .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_global_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.GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(dependency)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheDependencyIsCalled(dependency)) + .BDDfy(); } - class FakeHandler : DelegatingHandler - { - public DateTime TimeCalled { get; private set; } + private void ThenTheDependencyIsCalled(FakeDependency dependency) + { + dependency.Called.ShouldBeTrue(); + } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + private void ThenTheHandlersAreCalledCorrectly() + { + FakeHandler.TimeCalled.ShouldBeLessThan(FakeHandlerTwo.TimeCalled); + } + + private void ThenTheOrderedHandlersAreCalledCorrectly() + { + FakeHandlerTwo.TimeCalled.ShouldBeLessThan(FakeHandler.TimeCalled); + } + + public class FakeDependency + { + public bool Called; + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandlerWithDependency : DelegatingHandler + { + private readonly FakeDependency _dependency; + + public FakeHandlerWithDependency(FakeDependency dependency) + { + _dependency = dependency; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + _dependency.Called = true; + return base.SendAsync(request, cancellationToken); + } + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandler : DelegatingHandler + { + public static DateTime TimeCalled { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { TimeCalled = DateTime.Now; - return await base.SendAsync(request, cancellationToken); + return base.SendAsync(request, cancellationToken); + } + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandlerTwo : DelegatingHandler + { + public static DateTime TimeCalled { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return base.SendAsync(request, cancellationToken); } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 90d5bc64..ed09ed96 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -50,4 +50,9 @@ + + + ..\..\..\..\Users\TGP\.nuget\packages\castle.core\4.2.1\lib\netstandard1.3\Castle.Core.dll + + diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 61a01059..0de7113d 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -22,6 +22,10 @@ using Ocelot.Middleware; using Shouldly; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using Ocelot.AcceptanceTests.Caching; +using System.IO.Compression; +using System.Text; +using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; +using Ocelot.Requester; namespace Ocelot.AcceptanceTests { @@ -172,10 +176,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 GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi() + where TOne : DelegatingHandler + where TWo : DelegatingHandler { _webHostBuilder = new WebHostBuilder(); @@ -193,8 +196,73 @@ namespace Ocelot.AcceptanceTests { s.AddSingleton(_webHostBuilder); s.AddOcelot() - .AddDelegatingHandler(() => handlerOne) - .AddDelegatingHandler(() => handlerTwo); + .AddSingletonDelegatingHandler() + .AddSingletonDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() + 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() + .AddSingletonDelegatingHandler(true) + .AddSingletonDelegatingHandler(true); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(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() + .AddSingletonDelegatingHandler(true); }) .Configure(a => { @@ -582,6 +650,23 @@ namespace Ocelot.AcceptanceTests _postContent = new StringContent(postcontent); } + public void GivenThePostHasGzipContent(object input) + { + var json = JsonConvert.SerializeObject(input); + var jsonBytes = Encoding.UTF8.GetBytes(json); + var ms = new MemoryStream(); + using (var gzip = new GZipStream(ms, CompressionMode.Compress, true)) + { + gzip.Write(jsonBytes, 0, jsonBytes.Length); + } + + ms.Position = 0; + var 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.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.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 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/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4de07053..79aff9ed 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -402,11 +402,14 @@ namespace Ocelot.UnitTests.Configuration var reRouteOptions = new ReRouteOptionsBuilder() .Build(); + var handlers = new List {"Polly", "Tracer"}; + var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List {"Get"}) + .WithDelegatingHandlers(handlers) .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration @@ -419,6 +422,7 @@ namespace Ocelot.UnitTests.Configuration UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, + DelegatingHandlers = handlers } }, })) @@ -821,7 +825,8 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamReRoute[0].ClaimsToClaims.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToClaims.Count); result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count); result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count); - result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); + result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); + result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index c2f35990..df812f70 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -28,28 +28,58 @@ namespace Ocelot.UnitTests.DependencyInjection private readonly IConfiguration _configRoot; private IOcelotBuilder _ocelotBuilder; private readonly int _maxRetries; + private Exception _ex; public OcelotBuilderTests() { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); _services.AddSingleton(); - _services.AddSingleton(_configRoot); + _services.AddSingleton(_configRoot); _maxRetries = 100; } - private Exception _ex; + [Fact] + public void should_add_specific_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSpecificTransientDelegatingHandler()) + .And(x => AddSpecificTransientDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreTransient()) + .BDDfy(); + } [Fact] - public void should_add_delegating_handlers() + public void should_add_specific_delegating_handler_singleton() { - 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 => AddSpecificDelegatingHandler()) + .And(x => AddSpecificDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreSingleton()) + .BDDfy(); + } + + [Fact] + public void should_add_global_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientGlobalDelegatingHandler()) + .And(x => AddTransientGlobalDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_global_delegating_handlers_singleton() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddGlobalDelegatingHandler()) + .And(x => AddGlobalDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreSingleton()) .BDDfy(); } @@ -120,16 +150,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - private void WhenISetUpAdministration() - { - _ocelotBuilder.AddAdministration("/administration", "secret"); - } - - private void WhenISetUpAdministration(Action options) - { - _ocelotBuilder.AddAdministration("/administration", options); - } - [Fact] public void should_use_logger_factory() { @@ -157,6 +177,62 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + private void ThenTheSpecificHandlersAreSingleton() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0]; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0]; + first.ShouldBe(second); + } + + private void ThenTheSpecificHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0]; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0]; + first.ShouldNotBe(second); + } + + private void ThenTheGlobalHandlersAreSingleton() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0].DelegatingHandler; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0].DelegatingHandler; + first.ShouldBe(second); + } + + private void ThenTheGlobalHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0].DelegatingHandler; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0].DelegatingHandler; + first.ShouldNotBe(second); + } + + private void WhenISetUpAdministration() + { + _ocelotBuilder.AddAdministration("/administration", "secret"); + } + + private void WhenISetUpAdministration(Action options) + { + _ocelotBuilder.AddAdministration("/administration", options); + } + + private void AddTransientGlobalDelegatingHandler() where T : DelegatingHandler + { + _ocelotBuilder.AddTransientDelegatingHandler(true); + } + + private void AddSpecificTransientDelegatingHandler() where T : DelegatingHandler + { + _ocelotBuilder.AddTransientDelegatingHandler(); + } + private void ThenTheCorrectAdminPathIsRegitered() { _serviceProvider = _services.BuildServiceProvider(); @@ -164,29 +240,38 @@ 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].DelegatingHandler.ShouldBeOfType(); + handlers[1].DelegatingHandler.ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsSpecificHandlers() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); } private void OnlyOneVersionOfEachCacheIsRegistered() { var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var thing = (CacheManager.Core.ICacheManager)outputCacheManager.ImplementationInstance; - thing.Configuration.MaxRetries.ShouldBe(_maxRetries); - + var instance = (ICacheManager)outputCacheManager.ImplementationInstance; var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + + instance.Configuration.MaxRetries.ShouldBe(_maxRetries); + outputCache.ShouldNotBeNull(); + ocelotConfigCache.ShouldNotBeNull(); + ocelotConfigCacheManager.ShouldNotBeNull(); + fileConfigCache.ShouldNotBeNull(); + fileConfigCacheManager.ShouldNotBeNull(); } private void WhenISetUpConsul() @@ -213,9 +298,14 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void AddDelegate(DelegatingHandler handler) + private void AddGlobalDelegatingHandler() where T : DelegatingHandler { - _ocelotBuilder.AddDelegatingHandler(() => handler); + _ocelotBuilder.AddSingletonDelegatingHandler(true); + } + + private void AddSpecificDelegatingHandler() where T : DelegatingHandler + { + _ocelotBuilder.AddSingletonDelegatingHandler(); } private void ThenAnOcelotBuilderIsReturned() @@ -286,6 +376,7 @@ namespace Ocelot.UnitTests.DependencyInjection { _serviceProvider = _services.BuildServiceProvider(); var logger = _serviceProvider.GetService(); + logger.ShouldNotBeNull(); } catch (Exception e) { @@ -298,6 +389,7 @@ namespace Ocelot.UnitTests.DependencyInjection try { var tracingHandler = _serviceProvider.GetService(); + tracingHandler.ShouldNotBeNull(); } catch (Exception e) { 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/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] 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/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; 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/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..28a4bcf2 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,196 @@ namespace Ocelot.UnitTests.Requester { public class DelegatingHandlerHandlerProviderFactoryTests { - private readonly DelegatingHandlerHandlerProviderFactory _factory; - private Mock _loggerFactory; + private DelegatingHandlerHandlerFactory _factory; + private readonly Mock _loggerFactory; private DownstreamReRoute _request; - private Response _provider; - private readonly Mock _allRoutesProvider; + private Response>> _result; private readonly Mock _qosProviderHouse; private readonly Mock _tracingFactory; + private IServiceProvider _serviceProvider; + private readonly IServiceCollection _services; 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); + _services = new ServiceCollection(); } - private void GivenTheQosProviderHouseReturns(Response qosProvider) + [Fact] + public void should_follow_ordering_add_specifics() { - _qosProviderHouse - .Setup(x => x.Get(It.IsAny())) - .Returns(qosProvider); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandler", + "FakeDelegatingHandlerTwo" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(6)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(5)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_order_specifics_and_globals() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandlerTwo", + "FakeDelegatingHandler", + "FakeDelegatingHandlerFour" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(6)) + .And(x => ThenHandlerAtPositionIs(0)) //first because global not in config + .And(x => ThenHandlerAtPositionIs(1)) //first from config + .And(x => ThenHandlerAtPositionIs(2)) //second from config + .And(x => ThenHandlerAtPositionIs(3)) //third from config (global) + .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(5)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_order_specifics() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandlerTwo", + "FakeDelegatingHandler" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(6)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(5)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_order_and_only_add_specifics_in_config() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandler", + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(5)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(4)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_dont_add_specifics() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(4)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .BDDfy(); + } + + [Fact] + public void should_apply_re_route_specific() + { + var reRoute = new DownstreamReRouteBuilder() + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandler", + "FakeDelegatingHandlerTwo" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(2)) + .And(x => ThenTheDelegatesAreAddedCorrectly()) + .BDDfy(); } [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 => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) .And(x => ThenTheDelegatesAreAddedCorrectly()) .And(x => ThenItIsPolly(2)) - .BDDfy(); + .BDDfy(); } [Fact] @@ -70,7 +217,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 +231,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,50 +246,92 @@ 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 ThenHandlerAtPositionIs(int pos) + where T : DelegatingHandler + { + var delegates = _result.Data; + var del = delegates[pos].Invoke(); + del.ShouldBeOfType(); + } + + private void GivenTheTracingFactoryReturns() + { + _tracingFactory + .Setup(x => x.Get()) + .Returns(new FakeTracingHandler()); + } + + private void GivenTheServiceProviderReturnsGlobalDelegatingHandlers() + where TOne : DelegatingHandler + where TTwo : DelegatingHandler + { + _services.AddTransient(); + _services.AddTransient(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + _services.AddTransient(); + _services.AddTransient(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + } + + private void GivenTheServiceProviderReturnsSpecificDelegatingHandlers() + where TOne : DelegatingHandler + where TTwo : DelegatingHandler + { + _services.AddTransient(); + _services.AddTransient(); + } + + private void GivenTheServiceProviderReturnsNothing() + { + _serviceProvider = _services.BuildServiceProvider(); + } + private void ThenAnErrorIsReturned() { - _provider.IsError.ShouldBeTrue(); + _result.IsError.ShouldBeTrue(); } private void ThenTheDelegatesAreAddedCorrectly() { - var delegates = _provider.Data.Get(); + var delegates = _result.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 = _result.Data; var del = delegates[i].Invoke(); del.ShouldBeOfType(); } private void ThenThereIsDelegatesInProvider(int count) { - _provider.ShouldNotBeNull(); - _provider.Data.Get().Count.ShouldBe(count); + _result.ShouldNotBeNull(); + _result.Data.Count.ShouldBe(count); } private void GivenTheFollowingRequest(DownstreamReRoute request) @@ -152,13 +341,19 @@ namespace Ocelot.UnitTests.Requester private void WhenIGet() { - _provider = _factory.Get(_request); + _serviceProvider = _services.BuildServiceProvider(); + _factory = new DelegatingHandlerHandlerFactory(_loggerFactory.Object, _tracingFactory.Object, _qosProviderHouse.Object, _serviceProvider); + _result = _factory.Get(_request); } private void ThenNoDelegatesAreInTheProvider() { - _provider.ShouldNotBeNull(); - _provider.Data.Get().Count.ShouldBe(0); + _result.ShouldNotBeNull(); + _result.Data.Count.ShouldBe(0); } } + + internal class FakeTracingHandler : DelegatingHandler, ITracingHandler + { + } } 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 a53487a3..c300c92c 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) @@ -17,12 +18,67 @@ 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()); + } + } + + public class FakeDelegatingHandlerThree : DelegatingHandler + { + public FakeDelegatingHandlerThree() + { + Order = 3; + } + + public int Order { get; private set; } + + public DateTime TimeCalled { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return Task.FromResult(new HttpResponseMessage()); + } + } + + public class FakeDelegatingHandlerFour : DelegatingHandler + { + public FakeDelegatingHandlerFour() + { + Order = 4; + } + + public int Order { get; private set; } + + public DateTime TimeCalled { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + 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 Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return Task.FromResult(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 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); }