diff --git a/Ocelot.sln b/Ocelot.sln index fb7008d3..6aa429fe 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2024 +VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" EndProject diff --git a/docs/favicon.ico b/docs/favicon.ico index ee470e42..5e606f74 100644 Binary files a/docs/favicon.ico and b/docs/favicon.ico differ diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 425c958e..dd08feab 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -21,7 +21,7 @@ All you need to do to hook into your own IdentityServer is add the following to }; services - .AddOcelot(Configuration) + .AddOcelot() .AddAdministration("/administration", options); } @@ -51,7 +51,7 @@ The secret is the client secret that Ocelot's internal IdentityServer will use t public virtual void ConfigureServices(IServiceCollection services) { services - .AddOcelot(Configuration) + .AddOcelot() .AddAdministration("/administration", "secret"); } diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index dd78fcfa..78aa890f 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -63,7 +63,7 @@ If you want to authenticate using JWT tokens maybe from a provider like Auth0 yo x.Audience = "test"; }); - services.AddOcelot(Configuration); + services.AddOcelot(); } Then map the authentication provider key to a ReRoute in your configuration e.g. @@ -111,7 +111,7 @@ In order to use IdentityServer bearer tokens register your IdentityServer servic services.AddAuthentication() .AddIdentityServerAuthentication(authenticationProviderKey, options); - services.AddOcelot(Configuration); + services.AddOcelot(); } Then map the authentication provider key to a ReRoute in your configuration e.g. diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 3bd4da2f..93cae138 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -78,6 +78,29 @@ Set it true if the request should automatically follow redirection responses fro - _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests. The default value is true. +Multiple environments +^^^^^^^^^^^^^^^^^^^^^ + +Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following +to you + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("configuration.json") + .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") + .AddEnvironmentVariables(); + }) + +Ocelot should now use the environment specific configuration and fall back to configuration.json if there isnt one. + +You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. + Store configuration in consul ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -86,7 +109,7 @@ If you add the following when you register your services Ocelot will attempt to .. code-block:: csharp services - .AddOcelot(Configuration) + .AddOcelot() .AddStoreOcelotConfigurationInConsul(); You also need to add the following to your configuration.json. This is how Ocelot diff --git a/docs/features/raft.rst b/docs/features/raft.rst index 45ea59f2..dd0cf031 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -12,7 +12,7 @@ In order to enable Rafty in Ocelot you must make the following changes to your S public virtual void ConfigureServices(IServiceCollection services) { services - .AddOcelot(Configuration) + .AddOcelot() .AddAdministration("/administration", "secret") .AddRafty(); } diff --git a/docs/features/ratelimiting.rst b/docs/features/ratelimiting.rst new file mode 100644 index 00000000..d929c905 --- /dev/null +++ b/docs/features/ratelimiting.rst @@ -0,0 +1,40 @@ +Rate Limiting +============= + +Thanks to `@catcherwong article `_ for inspiring me to finally write this documentation. + +Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much. + +OK so to get rate limiting working for a ReRoute you need to add the following json to it. + +.. code-block:: json + + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": true, + "Period": "1s", + "PeriodTimespan": 1, + "Limit": 1 + } + +ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting. +EnableRateLimiting - This value specifies enable endpoint rate limiting. +Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on. +PeriodTimespan - This value specifies that we can retry after a certain number of seconds. +Limit - This value specifies the maximum number of requests that a client can make in a defined period. + +You can also set the following in the GlobalConfiguration part of configuration.json + +.. code-block:: json + + "RateLimitOptions": { + "DisableRateLimitHeaders": false, + "QuotaExceededMessage": "Customize Tips!", + "HttpStatusCode": 999, + "ClientIdHeader" : "Test" + } + +DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled. +QuotaExceededMessage - This value specifies the exceeded message. +HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs. +ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId" diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst new file mode 100644 index 00000000..b359eb72 --- /dev/null +++ b/docs/features/requestaggregation.rst @@ -0,0 +1,95 @@ +Request Aggregation +=================== + +Ocelot allow's you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usual where you have +a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type +architecture with Ocelot. + +This feature was requested as part of `Issue 79 `_ . + +In order to set this up you must do something like the following in your configuration.json. Here we have specified two normal ReRoutes and each one has a Key property. +We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. +Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/laura", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51881 + } + ], + "Key": "Laura" + }, + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/tom", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51882 + } + ], + "Key": "Tom" + } + ], + "Aggregates": [ + { + "ReRouteKeys": [ + "Tom", + "Laura" + ], + "UpstreamPathTemplate": "/" + } + ] + } + +You can also set UpstreamHost and ReRouteIsCaseSensitive in the Aggregate configuration. These behave the same as any other ReRoutes. + +If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 25} the the response after aggregation would be as follows. + +.. code-block:: json + + {"Tom":{"Age": 19},"Laura":{"Age": 25}} + +Gotcha's / Further info +^^^^^^^^^^^^^^^^^^^^^^^ + +At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary +as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just +JSON without any pretty spaces etc. + +All headers will be lost from the downstream services response. + +Ocelot will always return content type application/json with an aggregate request. + +You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track. + +Aggregation only supports the GET HTTP Verb. + +If you downstream services return a 404 the aggregate will just return nothing for that downstream service. +It will not change the aggregate response into a 404 even if all the downstreams return a 404. + +Future +^^^^^^ + +There are loads of cool ways to enchance this such as.. + +What happens when downstream goes slow..should we timeout? +Can we do something like GraphQL where the user chooses what fields are returned? +Can we handle 404 better etc? +Can we make this not just support a JSON dictionary response? + diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index a30ea741..0a896d45 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -12,7 +12,7 @@ In your ConfigureServices method .. code-block:: csharp services - .AddOcelot(Configuration) + .AddOcelot() .AddOpenTracing(option => { //this is the url that the butterfly collector server is running on... diff --git a/docs/index.rst b/docs/index.rst index c98c2174..258085ef 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,13 +18,14 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n :hidden: :caption: Features - features/routing features/configuration + features/routing + features/requestaggregation features/servicediscovery features/authentication features/authorisation features/administration - features/raft + features/ratelimiting features/caching features/qualityofservice features/headerstransformation @@ -35,7 +36,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/middlewareinjection features/loadbalancer features/delegatinghandlers - + features/raft .. toctree:: :maxdepth: 2 diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 840505f1..8f635fdc 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -24,13 +24,21 @@ The following is a very basic configuration.json. It won't do anything but shoul { "ReRoutes": [], - "GlobalConfiguration": {} + "GlobalConfiguration": { + "BaseUrl": "https://api.mybusiness.com" + } } +The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under +in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. + +If for some reason you are using containers and do want Ocelot to respond to client on http://123.12.1.1:6543 +then you can do this but if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. + **Program** -Then in your Program.cs you will want to have the following. The main things to note are AddOcelotBaseUrl("http://localhost:5000") (adds the url this instance of Ocelot will run under), -AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot middleware). It is important to call AddOcelotBaseUrl as Ocelot needs to know the URL that it is exposed to the outside world on. +Then in your Program.cs you will want to have the following. The main things to note are +AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot middleware). .. code-block:: csharp @@ -48,8 +56,7 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("configuration.json") - .AddEnvironmentVariables() - .AddOcelotBaseUrl("http://localhost:5000"); + .AddEnvironmentVariables(); }) .ConfigureServices(s => { s.AddOcelot(); @@ -68,15 +75,6 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m } } -AddOcelotBaseUrl -^^^^^^^^^^^^^^^^ - -The most important thing to note here is AddOcelotBaseUrl. Ocelot needs to know the URL it is running under -in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. - -If for some reason you are using containers and do want Ocelot to respond to client on http://123.12.1.1:6543 -then you can do this but if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. - .NET Core 1.0 ^^^^^^^^^^^^^ @@ -138,8 +136,7 @@ An example startup using a json file for configuration can be seen below. .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddJsonFile("configuration.json") - .AddEnvironmentVariables() - .AddOcelotBaseUrl("http://localhost:5000"); + .AddEnvironmentVariables(); Configuration = builder.Build(); } diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index 1d820600..ffdd1ac6 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -1,12 +1,9 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Errors; using Ocelot.Infrastructure.Extensions; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; @@ -14,35 +11,29 @@ namespace Ocelot.Authentication.Middleware { public class AuthenticationMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; - private readonly IApplicationBuilder _app; - private readonly IAuthenticationSchemeProvider _authSchemeProvider; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; - public AuthenticationMiddleware(RequestDelegate next, - IApplicationBuilder app, - IRequestScopedDataRepository requestScopedDataRepository, + public AuthenticationMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) - : base(requestScopedDataRepository) { _next = next; - _app = app; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) + if (IsAuthenticatedRoute(context.DownstreamReRoute)) { - _logger.LogDebug($"{context.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); + _logger.LogDebug($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - var result = await context.AuthenticateAsync(DownstreamRoute.ReRoute.AuthenticationOptions.AuthenticationProviderKey); + var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); - context.User = result.Principal; + context.HttpContext.User = result.Principal; - if (context.User.Identity.IsAuthenticated) + if (context.HttpContext.User.Identity.IsAuthenticated) { - _logger.LogDebug($"Client has been authenticated for {context.Request.Path}"); + _logger.LogDebug($"Client has been authenticated for {context.HttpContext.Request.Path}"); await _next.Invoke(context); } else @@ -50,22 +41,23 @@ namespace Ocelot.Authentication.Middleware var error = new List { new UnauthenticatedError( - $"Request for authenticated route {context.Request.Path} by {context.User.Identity.Name} was unauthenticated") + $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated") }; - _logger.LogError($"Client has NOT been authenticated for {context.Request.Path} and pipeline error set. {error.ToErrorString()}"); - SetPipelineError(error); + _logger.LogError($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error.ToErrorString()}"); + + SetPipelineError(context, error); } } else { - _logger.LogTrace($"No authentication needed for {context.Request.Path}"); + _logger.LogTrace($"No authentication needed for {context.HttpContext.Request.Path}"); await _next.Invoke(context); } } - private static bool IsAuthenticatedRoute(ReRoute reRoute) + private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) { return reRoute.IsAuthenticated; } diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs index 4539ba25..868b668b 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Authentication.Middleware { public static class AuthenticationMiddlewareMiddlewareExtensions { - public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseAuthenticationMiddleware(this IOcelotPipelineBuilder builder) { - return builder.UseMiddleware(builder); + return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 29acb685..4db1045c 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -20,22 +20,22 @@ namespace Ocelot.Authorisation { foreach (var required in routeClaimsRequirement) { - var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0); + var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key); - if (value.IsError) + if (values.IsError) { - return new ErrorResponse(value.Errors); + return new ErrorResponse(values.Errors); } - if (value.Data != null) + if (values.Data != null) { - var authorised = value.Data == required.Value; + var authorised = values.Data.Contains(required.Value); if (!authorised) { return new ErrorResponse(new List { new ClaimValueNotAuthorisedError( - $"claim value: {value.Data} is not the same as required value: {required.Value} for type: {required.Key}") + $"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}") }); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index f3f55e35..f6c19522 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -9,21 +9,20 @@ namespace Ocelot.Authorisation.Middleware using System.Threading.Tasks; using Errors; using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Middleware; public class AuthorisationMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IClaimsAuthoriser _claimsAuthoriser; private readonly IScopesAuthoriser _scopesAuthoriser; private readonly IOcelotLogger _logger; - public AuthorisationMiddleware(RequestDelegate next, - IRequestScopedDataRepository requestScopedDataRepository, + public AuthorisationMiddleware(OcelotRequestDelegate next, IClaimsAuthoriser claimsAuthoriser, IScopesAuthoriser scopesAuthoriser, IOcelotLoggerFactory loggerFactory) - : base(requestScopedDataRepository) { _next = next; _claimsAuthoriser = claimsAuthoriser; @@ -31,19 +30,19 @@ namespace Ocelot.Authorisation.Middleware _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) + if (IsAuthenticatedRoute(context.DownstreamReRoute)) { _logger.LogDebug("route is authenticated scopes must be checked"); - var authorised = _scopesAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.AuthenticationOptions.AllowedScopes); + var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes); if (authorised.IsError) { _logger.LogDebug("error authorising user scopes"); - SetPipelineError(authorised.Errors); + SetPipelineError(context, authorised.Errors); return; } @@ -55,46 +54,46 @@ namespace Ocelot.Authorisation.Middleware { _logger.LogDebug("user scopes is not authorised setting pipeline error"); - SetPipelineError(new List + SetPipelineError(context, new List { new UnauthorisedError( - $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") + $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}") }); } } - if (IsAuthorisedRoute(DownstreamRoute.ReRoute)) + if (IsAuthorisedRoute(context.DownstreamReRoute)) { _logger.LogDebug("route is authorised"); - var authorised = _claimsAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement); + var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement); if (authorised.IsError) { - _logger.LogDebug($"Error whilst authorising {context.User.Identity.Name} for {context.User.Identity.Name}. Setting pipeline error"); + _logger.LogDebug($"Error whilst authorising {context.HttpContext.User.Identity.Name} for {context.HttpContext.User.Identity.Name}. Setting pipeline error"); - SetPipelineError(authorised.Errors); + SetPipelineError(context, authorised.Errors); return; } if (IsAuthorised(authorised)) { - _logger.LogDebug($"{context.User.Identity.Name} has succesfully been authorised for {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); + _logger.LogDebug($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); await _next.Invoke(context); } else { - _logger.LogDebug($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); + _logger.LogDebug($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); - SetPipelineError(new List + SetPipelineError(context, new List { - new UnauthorisedError($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") + new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}") }); } } else { - _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); + _logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); await _next.Invoke(context); } } @@ -104,12 +103,12 @@ namespace Ocelot.Authorisation.Middleware return authorised.Data; } - private static bool IsAuthenticatedRoute(ReRoute reRoute) + private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) { return reRoute.IsAuthenticated; } - private static bool IsAuthorisedRoute(ReRoute reRoute) + private static bool IsAuthorisedRoute(DownstreamReRoute reRoute) { return reRoute.IsAuthorised; } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs index ca1fc945..ffc30177 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs @@ -1,12 +1,14 @@ +using Ocelot.Middleware.Pipeline; + namespace Ocelot.Authorisation.Middleware { using Microsoft.AspNetCore.Builder; public static class AuthorisationMiddlewareMiddlewareExtensions { - public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseAuthorisationMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 07f7445e..873c4cf7 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -7,22 +7,21 @@ using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.IO; +using Ocelot.DownstreamRouteFinder.Middleware; namespace Ocelot.Cache.Middleware { public class OutputCacheMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; private readonly IOcelotCache _outputCache; private readonly IRegionCreator _regionCreator; - public OutputCacheMiddleware(RequestDelegate next, + public OutputCacheMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository scopedDataRepository, IOcelotCache outputCache, IRegionCreator regionCreator) - : base(scopedDataRepository) { _next = next; _outputCache = outputCache; @@ -30,26 +29,26 @@ namespace Ocelot.Cache.Middleware _regionCreator = regionCreator; } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - if (!DownstreamRoute.ReRoute.IsCached) + if (!context.DownstreamReRoute.IsCached) { await _next.Invoke(context); return; } - var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; + var downstreamUrlKey = $"{context.DownstreamRequest.Method.Method}-{context.DownstreamRequest.RequestUri.OriginalString}"; _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); - var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); + var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region); if (cached != null) { _logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); var response = CreateHttpResponseMessage(cached); - SetHttpResponseMessageThisRequest(response); + SetHttpResponseMessageThisRequest(context, response); _logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); @@ -60,20 +59,25 @@ namespace Ocelot.Cache.Middleware await _next.Invoke(context); - if (PipelineError) + if (context.IsError) { _logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey); return; } - cached = await CreateCachedResponse(HttpResponseMessage); + cached = await CreateCachedResponse(context.DownstreamResponse); - _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); + _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); } + private void SetHttpResponseMessageThisRequest(DownstreamContext context, HttpResponseMessage response) + { + context.DownstreamResponse = response; + } + internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) { if (cached == null) diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs index 89e343a0..e28df6f0 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Cache.Middleware { public static class OutputCacheMiddlewareExtensions { - public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseOutputCacheMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs index afd2fae2..5161b247 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; @@ -9,34 +10,32 @@ namespace Ocelot.Claims.Middleware { public class ClaimsBuilderMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IAddClaimsToRequest _addClaimsToRequest; private readonly IOcelotLogger _logger; - public ClaimsBuilderMiddleware(RequestDelegate next, - IRequestScopedDataRepository requestScopedDataRepository, + public ClaimsBuilderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IAddClaimsToRequest addClaimsToRequest) - : base(requestScopedDataRepository) { _next = next; _addClaimsToRequest = addClaimsToRequest; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - if (DownstreamRoute.ReRoute.ClaimsToClaims.Any()) + if (context.DownstreamReRoute.ClaimsToClaims.Any()) { _logger.LogDebug("this route has instructions to convert claims to other claims"); - var result = _addClaimsToRequest.SetClaimsOnContext(DownstreamRoute.ReRoute.ClaimsToClaims, context); + var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext); if (result.IsError) { _logger.LogDebug("error converting claims to other claims, setting pipeline error"); - SetPipelineError(result.Errors); + SetPipelineError(context, result.Errors); return; } } diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs index 099a0add..0790ec7c 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Claims.Middleware { public static class ClaimsBuilderMiddlewareExtensions { - public static IApplicationBuilder UseClaimsBuilderMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseClaimsBuilderMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs new file mode 100644 index 00000000..bc860424 --- /dev/null +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -0,0 +1,251 @@ +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Values; +using System.Linq; + +namespace Ocelot.Configuration.Builder +{ + public class DownstreamReRouteBuilder + { + private AuthenticationOptions _authenticationOptions; + private string _reRouteKey; + private string _downstreamPathTemplate; + private string _upstreamTemplate; + private UpstreamPathTemplate _upstreamTemplatePattern; + private List _upstreamHttpMethod; + private bool _isAuthenticated; + private List _claimsToHeaders; + private List _claimToClaims; + private Dictionary _routeClaimRequirement; + private bool _isAuthorised; + private List _claimToQueries; + private string _requestIdHeaderKey; + private bool _isCached; + private CacheOptions _fileCacheOptions; + private string _downstreamScheme; + private string _loadBalancer; + private bool _useQos; + private QoSOptions _qosOptions; + private HttpHandlerOptions _httpHandlerOptions; + private bool _enableRateLimiting; + private RateLimitOptions _rateLimitOptions; + private bool _useServiceDiscovery; + private string _serviceName; + private List _upstreamHeaderFindAndReplace; + private List _downstreamHeaderFindAndReplace; + private readonly List _downstreamAddresses; + private string _upstreamHost; + private string _key; + + public DownstreamReRouteBuilder() + { + _downstreamAddresses = new List(); + } + + public DownstreamReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) + { + _downstreamAddresses.AddRange(downstreamAddresses); + return this; + } + + public DownstreamReRouteBuilder WithUpstreamHost(string upstreamAddresses) + { + _upstreamHost = upstreamAddresses; + return this; + } + + public DownstreamReRouteBuilder WithLoadBalancer(string loadBalancer) + { + _loadBalancer = loadBalancer; + return this; + } + + public DownstreamReRouteBuilder WithDownstreamScheme(string downstreamScheme) + { + _downstreamScheme = downstreamScheme; + return this; + } + + public DownstreamReRouteBuilder WithDownstreamPathTemplate(string input) + { + _downstreamPathTemplate = input; + return this; + } + + public DownstreamReRouteBuilder WithUpstreamPathTemplate(string input) + { + _upstreamTemplate = input; + return this; + } + + public DownstreamReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) + { + _upstreamTemplatePattern = input; + return this; + } + + public DownstreamReRouteBuilder WithUpstreamHttpMethod(List input) + { + _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); + return this; + } + + public DownstreamReRouteBuilder WithIsAuthenticated(bool input) + { + _isAuthenticated = input; + return this; + } + + public DownstreamReRouteBuilder WithIsAuthorised(bool input) + { + _isAuthorised = input; + return this; + } + + public DownstreamReRouteBuilder WithRequestIdKey(string input) + { + _requestIdHeaderKey = input; + return this; + } + + public DownstreamReRouteBuilder WithClaimsToHeaders(List input) + { + _claimsToHeaders = input; + return this; + } + + public DownstreamReRouteBuilder WithClaimsToClaims(List input) + { + _claimToClaims = input; + return this; + } + + public DownstreamReRouteBuilder WithRouteClaimsRequirement(Dictionary input) + { + _routeClaimRequirement = input; + return this; + } + + public DownstreamReRouteBuilder WithClaimsToQueries(List input) + { + _claimToQueries = input; + return this; + } + + public DownstreamReRouteBuilder WithIsCached(bool input) + { + _isCached = input; + return this; + } + + public DownstreamReRouteBuilder WithCacheOptions(CacheOptions input) + { + _fileCacheOptions = input; + return this; + } + + public DownstreamReRouteBuilder WithIsQos(bool input) + { + _useQos = input; + return this; + } + + public DownstreamReRouteBuilder WithQosOptions(QoSOptions input) + { + _qosOptions = input; + return this; + } + + public DownstreamReRouteBuilder WithReRouteKey(string reRouteKey) + { + _reRouteKey = reRouteKey; + return this; + } + + public DownstreamReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + return this; + } + + public DownstreamReRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public DownstreamReRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public DownstreamReRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) + { + _httpHandlerOptions = input; + return this; + } + + public DownstreamReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + + public DownstreamReRouteBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public DownstreamReRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) + { + _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; + return this; + } + + public DownstreamReRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) + { + _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; + return this; + } + + public DownstreamReRouteBuilder WithKey(string key) + { + _key = key; + return this; + } + + + public DownstreamReRoute Build() + { + return new DownstreamReRoute( + _key, + new PathTemplate(_upstreamTemplate), + _upstreamHeaderFindAndReplace, + _downstreamHeaderFindAndReplace, + _downstreamAddresses, + _serviceName, + _httpHandlerOptions, + _useServiceDiscovery, + _enableRateLimiting, + _useQos, + _qosOptions, + _downstreamScheme, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _loadBalancer, + _rateLimitOptions, + _routeClaimRequirement, + _claimToQueries, + _claimsToHeaders, + _claimToClaims, + _isAuthenticated, + _isAuthorised, + _authenticationOptions, + new PathTemplate(_downstreamPathTemplate), + _reRouteKey); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index fffd8daf..0d059932 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -9,43 +9,26 @@ namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { - private AuthenticationOptions _authenticationOptions; - private string _reRouteKey; - private string _downstreamPathTemplate; private string _upstreamTemplate; private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; - private bool _isAuthenticated; - private List _configHeaderExtractorProperties; - private List _claimToClaims; - private Dictionary _routeClaimRequirement; - private bool _isAuthorised; - private List _claimToQueries; - private string _requestIdHeaderKey; - private bool _isCached; - private CacheOptions _fileCacheOptions; - private string _downstreamScheme; - private string _loadBalancer; - private bool _useQos; - private QoSOptions _qosOptions; - private HttpHandlerOptions _httpHandlerOptions; - private bool _enableRateLimiting; - private RateLimitOptions _rateLimitOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private List _upstreamHeaderFindAndReplace; - private List _downstreamHeaderFindAndReplace; - private readonly List _downstreamAddresses; private string _upstreamHost; + private List _downstreamReRoutes; public ReRouteBuilder() { - _downstreamAddresses = new List(); + _downstreamReRoutes = new List(); } - public ReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) + public ReRouteBuilder WithDownstreamReRoute(DownstreamReRoute value) { - _downstreamAddresses.AddRange(downstreamAddresses); + _downstreamReRoutes.Add(value); + return this; + } + + public ReRouteBuilder WithDownstreamReRoutes(List value) + { + _downstreamReRoutes = value; return this; } @@ -55,24 +38,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithLoadBalancer(string loadBalancer) - { - _loadBalancer = loadBalancer; - return this; - } - - public ReRouteBuilder WithDownstreamScheme(string downstreamScheme) - { - _downstreamScheme = downstreamScheme; - return this; - } - - public ReRouteBuilder WithDownstreamPathTemplate(string input) - { - _downstreamPathTemplate = input; - return this; - } - public ReRouteBuilder WithUpstreamPathTemplate(string input) { _upstreamTemplate = input; @@ -91,158 +56,15 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithIsAuthenticated(bool input) - { - _isAuthenticated = input; - return this; - } - - public ReRouteBuilder WithIsAuthorised(bool input) - { - _isAuthorised = input; - return this; - } - - public ReRouteBuilder WithRequestIdKey(string input) - { - _requestIdHeaderKey = input; - return this; - } - - public ReRouteBuilder WithClaimsToHeaders(List input) - { - _configHeaderExtractorProperties = input; - return this; - } - - public ReRouteBuilder WithClaimsToClaims(List input) - { - _claimToClaims = input; - return this; - } - - public ReRouteBuilder WithRouteClaimsRequirement(Dictionary input) - { - _routeClaimRequirement = input; - return this; - } - - public ReRouteBuilder WithClaimsToQueries(List input) - { - _claimToQueries = input; - return this; - } - - public ReRouteBuilder WithIsCached(bool input) - { - _isCached = input; - return this; - } - - public ReRouteBuilder WithCacheOptions(CacheOptions input) - { - _fileCacheOptions = input; - return this; - } - - public ReRouteBuilder WithIsQos(bool input) - { - _useQos = input; - return this; - } - - public ReRouteBuilder WithQosOptions(QoSOptions input) - { - _qosOptions = input; - return this; - } - - public ReRouteBuilder WithReRouteKey(string reRouteKey) - { - _reRouteKey = reRouteKey; - return this; - } - - public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) - { - _authenticationOptions = authenticationOptions; - return this; - } - - public ReRouteBuilder WithEnableRateLimiting(bool input) - { - _enableRateLimiting = input; - return this; - } - - public ReRouteBuilder WithRateLimitOptions(RateLimitOptions input) - { - _rateLimitOptions = input; - return this; - } - - public ReRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) - { - _httpHandlerOptions = input; - return this; - } - - public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - - public ReRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public ReRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) - { - _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; - return this; - } - - public ReRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) - { - _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; - return this; - } - - public ReRoute Build() { return new ReRoute( - new PathTemplate(_downstreamPathTemplate), + _downstreamReRoutes, new PathTemplate(_upstreamTemplate), _upstreamHttpMethod, _upstreamTemplatePattern, - _isAuthenticated, - _authenticationOptions, - _configHeaderExtractorProperties, - _claimToClaims, - _routeClaimRequirement, - _isAuthorised, - _claimToQueries, - _requestIdHeaderKey, - _isCached, - _fileCacheOptions, - _downstreamScheme, - _loadBalancer, - _reRouteKey, - _useQos, - _qosOptions, - _enableRateLimiting, - _rateLimitOptions, - _httpHandlerOptions, - _useServiceDiscovery, - _serviceName, - _upstreamHeaderFindAndReplace, - _downstreamHeaderFindAndReplace, - _downstreamAddresses, - _upstreamHost); + _upstreamHost + ); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index a0d5a1fe..fbc58020 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -1,19 +1,14 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Ocelot.Cache; using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; -using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.DependencyInjection; -using Ocelot.LoadBalancer; -using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; -using Ocelot.Requester.QoS; using Ocelot.Responses; namespace Ocelot.Configuration.Creator @@ -97,7 +92,16 @@ namespace Ocelot.Configuration.Creator foreach (var reRoute in fileConfiguration.ReRoutes) { - var ocelotReRoute = SetUpReRoute(reRoute, fileConfiguration.GlobalConfiguration); + var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); + + var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); + + reRoutes.Add(ocelotReRoute); + } + + foreach (var aggregate in fileConfiguration.Aggregates) + { + var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); reRoutes.Add(ocelotReRoute); } @@ -108,7 +112,48 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) + { + var applicableReRoutes = reRoutes + .SelectMany(x => x.DownstreamReRoute) + .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) + .ToList(); + + if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + { + //todo - log or throw or return error whatever? + } + + //make another re route out of these + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithDownstreamReRoutes(applicableReRoutes) + .WithUpstreamHost(aggregateReRoute.UpstreamHost) + .Build(); + + return reRoute; + } + + private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) + { + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithDownstreamReRoute(downstreamReRoutes) + .WithUpstreamHost(fileReRoute.UpstreamHost) + .Build(); + + return reRoute; + } + + private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); @@ -138,7 +183,8 @@ namespace Ocelot.Configuration.Creator var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() + .WithKey(fileReRoute.Key) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) diff --git a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs index b40e5e3e..b8303189 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs @@ -5,6 +5,6 @@ namespace Ocelot.Configuration.Creator { public interface IUpstreamTemplatePatternCreator { - UpstreamPathTemplate Create(FileReRoute reRoute); + UpstreamPathTemplate Create(IReRoute reRoute); } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index cc6ebdc7..1700472a 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -12,7 +12,7 @@ namespace Ocelot.Configuration.Creator private const string RegExForwardSlashOnly = "^/$"; private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; - public UpstreamPathTemplate Create(FileReRoute reRoute) + public UpstreamPathTemplate Create(IReRoute reRoute) { var upstreamTemplate = reRoute.UpstreamPathTemplate; @@ -73,4 +73,4 @@ namespace Ocelot.Configuration.Creator return upstreamTemplate[i] == '{'; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs new file mode 100644 index 00000000..9322d5cf --- /dev/null +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.Configuration +{ + public class DownstreamReRoute + { + public DownstreamReRoute( + string key, + PathTemplate upstreamPathTemplate, + List upstreamHeadersFindAndReplace, + List downstreamHeadersFindAndReplace, + List downstreamAddresses, + string serviceName, + HttpHandlerOptions httpHandlerOptions, + bool useServiceDiscovery, + bool enableEndpointEndpointRateLimiting, + bool isQos, + QoSOptions qosOptionsOptions, + string downstreamScheme, + string requestIdKey, + bool isCached, + CacheOptions cacheOptions, + string loadBalancer, + RateLimitOptions rateLimitOptions, + Dictionary routeClaimsRequirement, + List claimsToQueries, + List claimsToHeaders, + List claimsToClaims, + bool isAuthenticated, + bool isAuthorised, + AuthenticationOptions authenticationOptions, + PathTemplate downstreamPathTemplate, + string reRouteKey) + { + Key = key; + UpstreamPathTemplate = upstreamPathTemplate; + UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace ?? new List(); + DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace ?? new List(); + DownstreamAddresses = downstreamAddresses ?? new List(); + ServiceName = serviceName; + HttpHandlerOptions = httpHandlerOptions; + UseServiceDiscovery = useServiceDiscovery; + EnableEndpointEndpointRateLimiting = enableEndpointEndpointRateLimiting; + IsQos = isQos; + QosOptionsOptions = qosOptionsOptions; + DownstreamScheme = downstreamScheme; + RequestIdKey = requestIdKey; + IsCached = isCached; + CacheOptions = cacheOptions; + LoadBalancer = loadBalancer; + RateLimitOptions = rateLimitOptions; + RouteClaimsRequirement = routeClaimsRequirement; + ClaimsToQueries = claimsToQueries ?? new List(); + ClaimsToHeaders = claimsToHeaders ?? new List(); + ClaimsToClaims = claimsToClaims ?? new List(); + IsAuthenticated = isAuthenticated; + IsAuthorised = isAuthorised; + AuthenticationOptions = authenticationOptions; + DownstreamPathTemplate = downstreamPathTemplate; + ReRouteKey = reRouteKey; + } + + public string Key { get; private set; } + public PathTemplate UpstreamPathTemplate { get;private set; } + public List UpstreamHeadersFindAndReplace {get;private set;} + public List DownstreamHeadersFindAndReplace { get; private set; } + public List DownstreamAddresses { get; private set; } + public string ServiceName { get; private set; } + public HttpHandlerOptions HttpHandlerOptions { get; private set; } + public bool UseServiceDiscovery { get; private set; } + public bool EnableEndpointEndpointRateLimiting { get; private set; } + public bool IsQos { get; private set; } + public QoSOptions QosOptionsOptions { get; private set; } + public string DownstreamScheme { get; private set; } + public string RequestIdKey { get; private set; } + public bool IsCached { get; private set; } + public CacheOptions CacheOptions { get; private set; } + public string LoadBalancer { get; private set; } + public RateLimitOptions RateLimitOptions { get; private set; } + public Dictionary RouteClaimsRequirement { get; private set; } + public List ClaimsToQueries { get; private set; } + public List ClaimsToHeaders { get; private set; } + public List ClaimsToClaims { get; private set; } + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public AuthenticationOptions AuthenticationOptions { get; private set; } + public PathTemplate DownstreamPathTemplate { get; private set; } + public string ReRouteKey { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs new file mode 100644 index 00000000..8c9eabba --- /dev/null +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.File +{ + public class FileAggregateReRoute : IReRoute + { + public List ReRouteKeys { get;set; } + public string UpstreamPathTemplate { get;set; } + public string UpstreamHost { get; set; } + public bool ReRouteIsCaseSensitive { get; set; } + + // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) + public List UpstreamHttpMethod + { + get { return new List {"Get"}; } + } + } +} diff --git a/src/Ocelot/Configuration/File/FileConfiguration.cs b/src/Ocelot/Configuration/File/FileConfiguration.cs index 2c4120aa..6d73ce3f 100644 --- a/src/Ocelot/Configuration/File/FileConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileConfiguration.cs @@ -8,9 +8,12 @@ namespace Ocelot.Configuration.File { ReRoutes = new List(); GlobalConfiguration = new FileGlobalConfiguration(); + Aggregates = new List(); } public List ReRoutes { get; set; } + // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates + public List Aggregates { get;set; } public FileGlobalConfiguration GlobalConfiguration { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 5788d921..adbbdce1 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -11,8 +11,10 @@ namespace Ocelot.Configuration.File public string RequestIdKey { get; set; } - public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} + public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; } public FileRateLimitOptions RateLimitOptions { get; set; } + + public string BaseUrl { get ;set; } } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 9ca6c7c9..7169e62c 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -2,7 +2,7 @@ namespace Ocelot.Configuration.File { - public class FileReRoute + public class FileReRoute : IReRoute { public FileReRoute() { @@ -36,12 +36,13 @@ namespace Ocelot.Configuration.File public string ServiceName { get; set; } public string DownstreamScheme {get;set;} public FileQoSOptions QoSOptions { get; set; } - public string LoadBalancer {get;set;} + public string LoadBalancer { get;set; } public FileRateLimitRule RateLimitOptions { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - public bool UseServiceDiscovery {get;set;} + public bool UseServiceDiscovery { get;set; } public List DownstreamHostAndPorts {get;set;} public string UpstreamHost { get; set; } + public string Key { get;set; } } } diff --git a/src/Ocelot/Configuration/File/IReRoute.cs b/src/Ocelot/Configuration/File/IReRoute.cs new file mode 100644 index 00000000..69128d3a --- /dev/null +++ b/src/Ocelot/Configuration/File/IReRoute.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + public interface IReRoute + { + string UpstreamPathTemplate { get; set; } + bool ReRouteIsCaseSensitive { get; set; } + } +} diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 607e971c..47f26291 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,98 +1,30 @@ using System.Collections.Generic; using System.Net.Http; using Ocelot.Configuration.Creator; +using Ocelot.Requester.QoS; using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(PathTemplate downstreamPathTemplate, + public ReRoute(List downstreamReRoute, PathTemplate upstreamPathTemplate, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, - bool isAuthenticated, - AuthenticationOptions authenticationOptions, - List claimsToHeaders, - List claimsToClaims, - Dictionary routeClaimsRequirement, - bool isAuthorised, - List claimsToQueries, - string requestIdKey, - bool isCached, - CacheOptions cacheOptions, - string downstreamScheme, - string loadBalancer, - string reRouteKey, - bool isQos, - QoSOptions qosOptions, - bool enableEndpointRateLimiting, - RateLimitOptions ratelimitOptions, - HttpHandlerOptions httpHandlerOptions, - bool useServiceDiscovery, - string serviceName, - List upstreamHeadersFindAndReplace, - List downstreamHeadersFindAndReplace, - List downstreamAddresses, string upstreamHost) { UpstreamHost = upstreamHost; - DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace ?? new List(); - UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace ?? new List(); - ServiceName = serviceName; - UseServiceDiscovery = useServiceDiscovery; - ReRouteKey = reRouteKey; - LoadBalancer = loadBalancer; - DownstreamAddresses = downstreamAddresses ?? new List(); - DownstreamPathTemplate = downstreamPathTemplate; + DownstreamReRoute = downstreamReRoute; UpstreamPathTemplate = upstreamPathTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; - IsAuthenticated = isAuthenticated; - AuthenticationOptions = authenticationOptions; - RouteClaimsRequirement = routeClaimsRequirement; - IsAuthorised = isAuthorised; - RequestIdKey = requestIdKey; - IsCached = isCached; - CacheOptions = cacheOptions; - ClaimsToQueries = claimsToQueries ?? new List(); - ClaimsToClaims = claimsToClaims ?? new List(); - ClaimsToHeaders = claimsToHeaders ?? new List(); - DownstreamScheme = downstreamScheme; - IsQos = isQos; - QosOptionsOptions = qosOptions; - EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting; - RateLimitOptions = ratelimitOptions; - HttpHandlerOptions = httpHandlerOptions; } - public string ReRouteKey {get;private set;} - public PathTemplate DownstreamPathTemplate { get; private set; } public PathTemplate UpstreamPathTemplate { get; private set; } public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } public List UpstreamHttpMethod { get; private set; } - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public AuthenticationOptions AuthenticationOptions { get; private set; } - public List ClaimsToQueries { get; private set; } - public List ClaimsToHeaders { get; private set; } - public List ClaimsToClaims { get; private set; } - public Dictionary RouteClaimsRequirement { get; private set; } - public string RequestIdKey { get; private set; } - public bool IsCached { get; private set; } - public CacheOptions CacheOptions { get; private set; } - public string DownstreamScheme {get;private set;} - public bool IsQos { get; private set; } - public QoSOptions QosOptionsOptions { get; private set; } - public string LoadBalancer {get;private set;} - public bool EnableEndpointEndpointRateLimiting { get; private set; } - public RateLimitOptions RateLimitOptions { get; private set; } - public HttpHandlerOptions HttpHandlerOptions { get; private set; } - public bool UseServiceDiscovery {get;private set;} - public string ServiceName {get;private set;} - public List UpstreamHeadersFindAndReplace {get;private set;} - public List DownstreamHeadersFindAndReplace {get;private set;} - public List DownstreamAddresses {get;private set;} public string UpstreamHost { get; private set; } + public List DownstreamReRoute { get; private set; } } } diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs index 8274af2f..7e78b3aa 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -11,11 +11,11 @@ namespace Ocelot.Configuration.Repository { public class ConsulFileConfigurationPoller : IDisposable { - private IOcelotLogger _logger; - private IFileConfigurationRepository _repo; - private IFileConfigurationSetter _setter; + private readonly IOcelotLogger _logger; + private readonly IFileConfigurationRepository _repo; + private readonly IFileConfigurationSetter _setter; private string _previousAsJson; - private Timer _timer; + private readonly Timer _timer; private bool _polling; public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) @@ -77,4 +77,4 @@ namespace Ocelot.Configuration.Repository _timer.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 0a150a02..58da7c4f 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -19,6 +19,29 @@ namespace Ocelot.Configuration.Validator RuleForEach(configuration => configuration.ReRoutes) .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); + + RuleForEach(configuration => configuration.ReRoutes) + .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates)) + .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate"); + + RuleForEach(configuration => configuration.Aggregates) + .Must((config, aggregateReRoute) => IsNotDuplicateIn(aggregateReRoute, config.Aggregates)) + .WithMessage((config, aggregate) => $"{nameof(aggregate)} {aggregate.UpstreamPathTemplate} has duplicate aggregate"); + + RuleForEach(configuration => configuration.Aggregates) + .Must((config, aggregateReRoute) => AllReRoutesForAggregateExist(aggregateReRoute, config.ReRoutes)) + .WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct Key property"); + + RuleForEach(configuration => configuration.Aggregates) + .Must((config, aggregateReRoute) => DoesNotContainReRoutesWithSpecificRequestIdKeys(aggregateReRoute, config.ReRoutes)) + .WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates"); + } + + private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List reRoutes) + { + var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key)); + + return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count; } public async Task> IsValid(FileConfiguration configuration) @@ -37,10 +60,21 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } - private static bool IsNotDuplicateIn(FileReRoute reRoute, List reRoutes) + private static bool DoesNotContainReRoutesWithSpecificRequestIdKeys(FileAggregateReRoute fileAggregateReRoute, + List reRoutes) + { + var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key)); + + return reRoutesForAggregate.All(r => string.IsNullOrEmpty(r.RequestIdKey)); + } + + private static bool IsNotDuplicateIn(FileReRoute reRoute, + List reRoutes) { var matchingReRoutes = reRoutes - .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)).ToList(); + .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate + && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)) + .ToList(); if(matchingReRoutes.Count == 1) { @@ -62,5 +96,27 @@ namespace Ocelot.Configuration.Validator return true; } + + private static bool IsNotDuplicateIn(FileReRoute reRoute, + List aggregateReRoutes) + { + var duplicate = aggregateReRoutes + .Any(a => a.UpstreamPathTemplate == reRoute.UpstreamPathTemplate + && a.UpstreamHost == reRoute.UpstreamHost + && reRoute.UpstreamHttpMethod.Select(x => x.ToLower()).Contains("get")); + + return !duplicate; + } + + private static bool IsNotDuplicateIn(FileAggregateReRoute reRoute, + List aggregateReRoutes) + { + var matchingReRoutes = aggregateReRoutes + .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate + && r.UpstreamHost == reRoute.UpstreamHost) + .ToList(); + + return matchingReRoutes.Count <= 1; + } } } diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index c525f67f..56360ad5 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Memory; @@ -6,6 +7,7 @@ namespace Ocelot.DependencyInjection { public static class ConfigurationBuilderExtensions { + [Obsolete("Please set BaseUrl in configuration.json GlobalConfiguration.BaseUrl")] public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl) { var memorySource = new MemoryConfigurationSource(); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index d51b8a8d..f4192713 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,4 +1,6 @@ +using Butterfly.Client.Tracing; using Microsoft.Extensions.Options; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.DependencyInjection { @@ -30,7 +32,6 @@ namespace Ocelot.DependencyInjection using Ocelot.Middleware; using Ocelot.QueryStrings; using Ocelot.RateLimit; - using Ocelot.Request.Builder; using Ocelot.Request.Mapper; using Ocelot.Requester; using Ocelot.Requester.QoS; @@ -114,7 +115,6 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -126,7 +126,7 @@ namespace Ocelot.DependencyInjection // 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 - _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.AddMemoryCache(); _services.TryAddSingleton(); @@ -147,8 +147,14 @@ namespace Ocelot.DependencyInjection //these get picked out later and added to http request _provider = new DelegatingHandlerHandlerProvider(); - _services.TryAddSingleton(_provider); - _services.AddTransient(); + _services.TryAddSingleton(_provider); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.AddSingleton(); + + // We add this here so that we can always inject something into the factory for IoC.. + _services.AddSingleton(); + } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) @@ -191,7 +197,8 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddOpenTracing(Action settings) { - _services.AddTransient(); + // Earlier we add FakeServiceTracer and need to remove it here before we add butterfly + _services.RemoveAll(); _services.AddButterfly(settings); return this; } diff --git a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs index f2e219cb..ed04daef 100644 --- a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs +++ b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs @@ -13,6 +13,5 @@ namespace Ocelot.DownstreamRouteFinder } public List TemplatePlaceholderNameAndValues { get; private set; } public ReRoute ReRoute { get; private set; } - public object UpstreamHeadersFindAndReplace {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 24765fb4..4b467b95 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,71 +1,71 @@ -using System.Linq; -using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Infrastructure.Extensions; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.DownstreamRouteFinder.Middleware { public class DownstreamRouteFinderMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IDownstreamRouteFinder _downstreamRouteFinder; private readonly IOcelotLogger _logger; private readonly IOcelotConfigurationProvider _configProvider; + private readonly IMultiplexer _multiplexer; - public DownstreamRouteFinderMiddleware(RequestDelegate next, + public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IDownstreamRouteFinder downstreamRouteFinder, - IRequestScopedDataRepository requestScopedDataRepository, - IOcelotConfigurationProvider configProvider) - :base(requestScopedDataRepository) + IDownstreamRouteFinder downstreamRouteFinder, + IOcelotConfigurationProvider configProvider, + IMultiplexer multiplexer) { _configProvider = configProvider; + _multiplexer = multiplexer; _next = next; _downstreamRouteFinder = downstreamRouteFinder; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - var upstreamUrlPath = context.Request.Path.ToString(); + var upstreamUrlPath = context.HttpContext.Request.Path.ToString(); - var upstreamHost = context.Request.Headers["Host"]; + var upstreamHost = context.HttpContext.Request.Headers["Host"]; - var configuration = await _configProvider.Get(); - - if(configuration.IsError) + var configuration = await _configProvider.Get(); + + if (configuration.IsError) { _logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); - SetPipelineError(configuration.Errors); + SetPipelineError(context, configuration.Errors); return; } - SetServiceProviderConfigurationForThisRequest(configuration.Data.ServiceProviderConfiguration); + context.ServiceProviderConfiguration = configuration.Data.ServiceProviderConfiguration; _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method, configuration.Data, upstreamHost); + var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.HttpContext.Request.Method, configuration.Data, upstreamHost); if (downstreamRoute.IsError) { _logger.LogError($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); - SetPipelineError(downstreamRoute.Errors); + SetPipelineError(context, downstreamRoute.Errors); return; } - _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate); + //todo - put this back in + // _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamReRoute.DownstreamPathTemplate); - SetDownstreamRouteForThisRequest(downstreamRoute.Data); + context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; - await _next.Invoke(context); + await _multiplexer.Multiplex(context, downstreamRoute.Data.ReRoute, _next); } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs index 81d2dd2d..13dacac8 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.DownstreamRouteFinder.Middleware { public static class DownstreamRouteFinderMiddlewareExtensions { - public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseDownstreamRouteFinderMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 01a388c5..5b5e8346 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -5,22 +5,21 @@ using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System; +using Ocelot.DownstreamRouteFinder.Middleware; namespace Ocelot.DownstreamUrlCreator.Middleware { public class DownstreamUrlCreatorMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IDownstreamPathPlaceholderReplacer _replacer; private readonly IOcelotLogger _logger; private readonly IUrlBuilder _urlBuilder; - public DownstreamUrlCreatorMiddleware(RequestDelegate next, + public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IDownstreamPathPlaceholderReplacer replacer, - IRequestScopedDataRepository requestScopedDataRepository, IUrlBuilder urlBuilder) - :base(requestScopedDataRepository) { _next = next; _replacer = replacer; @@ -28,30 +27,30 @@ namespace Ocelot.DownstreamUrlCreator.Middleware _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { var dsPath = _replacer - .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); + .Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); if (dsPath.IsError) { _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - SetPipelineError(dsPath.Errors); + SetPipelineError(context, dsPath.Errors); return; } - var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri) + var uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri) { Path = dsPath.Data.Value, - Scheme = DownstreamRoute.ReRoute.DownstreamScheme + Scheme = context.DownstreamReRoute.DownstreamScheme }; - DownstreamRequest.RequestUri = uriBuilder.Uri; + context.DownstreamRequest.RequestUri = uriBuilder.Uri; - _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", DownstreamRequest.RequestUri); + _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", context.DownstreamRequest.RequestUri); await _next.Invoke(context); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs index 238bc7ef..671636c5 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.DownstreamUrlCreator.Middleware { public static class DownstreamUrlCreatorMiddlewareExtensions { - public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseDownstreamUrlCreatorMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index a08031ea..9b49c84a 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Ocelot.Configuration.Provider; +using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -16,24 +17,23 @@ namespace Ocelot.Errors.Middleware /// public class ExceptionHandlerMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; - private readonly IRequestScopedDataRepository _requestScopedDataRepository; - private readonly IOcelotConfigurationProvider _configProvider; + private readonly IOcelotConfigurationProvider _provider; + private readonly IRequestScopedDataRepository _repo; - public ExceptionHandlerMiddleware(RequestDelegate next, + public ExceptionHandlerMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IOcelotConfigurationProvider configProvider) - :base(requestScopedDataRepository) + IOcelotConfigurationProvider provider, + IRequestScopedDataRepository repo) { - _configProvider = configProvider; + _provider = provider; + _repo = repo; _next = next; - _requestScopedDataRepository = requestScopedDataRepository; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { try { @@ -42,7 +42,6 @@ namespace Ocelot.Errors.Middleware _logger.LogDebug("ocelot pipeline started"); await _next.Invoke(context); - } catch (Exception e) { @@ -58,12 +57,12 @@ namespace Ocelot.Errors.Middleware _logger.LogDebug("ocelot pipeline finished"); } - private async Task TrySetGlobalRequestId(HttpContext context) + private async Task TrySetGlobalRequestId(DownstreamContext context) { //try and get the global request id and set it for logs... //should this basically be immutable per request...i guess it should! //first thing is get config - var configuration = await _configProvider.Get(); + var configuration = await _provider.Get(); //if error throw to catch below.. if(configuration.IsError) @@ -75,22 +74,23 @@ namespace Ocelot.Errors.Middleware var key = configuration.Data.RequestId; StringValues upstreamRequestIds; - if (!string.IsNullOrEmpty(key) && context.Request.Headers.TryGetValue(key, out upstreamRequestIds)) + if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out upstreamRequestIds)) { - context.TraceIdentifier = upstreamRequestIds.First(); - _requestScopedDataRepository.Add("RequestId", context.TraceIdentifier); - } - } - - private void SetInternalServerErrorOnResponse(HttpContext context) - { - if (!context.Response.HasStarted) - { - context.Response.StatusCode = 500; + //todo fix looking in both places + context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); + _repo.Add("RequestId", context.HttpContext.TraceIdentifier); } } - private string CreateMessage(HttpContext context, Exception e) + private void SetInternalServerErrorOnResponse(DownstreamContext context) + { + if (!context.HttpContext.Response.HasStarted) + { + context.HttpContext.Response.StatusCode = 500; + } + } + + private string CreateMessage(DownstreamContext context, Exception e) { var message = $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; @@ -100,7 +100,7 @@ namespace Ocelot.Errors.Middleware message = $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; } - return $"{message} RequestId: {context.TraceIdentifier}"; + return $"{message} RequestId: {context.HttpContext.TraceIdentifier}"; } } } diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs index 14731eb2..5d8874b0 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Errors.Middleware { public static class ExceptionHandlerMiddlewareExtensions { - public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseExceptionHandlerMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index b86cc4d2..f4281a23 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; @@ -8,17 +9,15 @@ namespace Ocelot.Headers.Middleware { public class HttpHeadersTransformationMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer; - public HttpHeadersTransformationMiddleware(RequestDelegate next, + public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, IHttpContextRequestHeaderReplacer preReplacer, IHttpResponseHeaderReplacer postReplacer) - : base(requestScopedDataRepository) { _next = next; _postReplacer = postReplacer; @@ -26,17 +25,18 @@ namespace Ocelot.Headers.Middleware _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - var preFAndRs = this.DownstreamRoute.ReRoute.UpstreamHeadersFindAndReplace; + var preFAndRs = context.DownstreamReRoute.UpstreamHeadersFindAndReplace; - _preReplacer.Replace(context, preFAndRs); + //todo - this should be on httprequestmessage not httpcontext? + _preReplacer.Replace(context.HttpContext, preFAndRs); await _next.Invoke(context); - var postFAndRs = this.DownstreamRoute.ReRoute.DownstreamHeadersFindAndReplace; + var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; - _postReplacer.Replace(HttpResponseMessage, postFAndRs, DownstreamRequest); + _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs index ce920b1a..46bb84dc 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Headers.Middleware { public static class HttpHeadersTransformationMiddlewareExtensions { - public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseHttpHeadersTransformationMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index 35d2fe5a..6e8dc31e 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; @@ -9,34 +10,32 @@ namespace Ocelot.Headers.Middleware { public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IAddHeadersToRequest _addHeadersToRequest; private readonly IOcelotLogger _logger; - public HttpRequestHeadersBuilderMiddleware(RequestDelegate next, + public HttpRequestHeadersBuilderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, IAddHeadersToRequest addHeadersToRequest) - : base(requestScopedDataRepository) { _next = next; _addHeadersToRequest = addHeadersToRequest; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - if (DownstreamRoute.ReRoute.ClaimsToHeaders.Any()) + if (context.DownstreamReRoute.ClaimsToHeaders.Any()) { - _logger.LogDebug($"{ DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); + _logger.LogDebug($"{ context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); - var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToHeaders, context.User.Claims, DownstreamRequest); + var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest); if (response.IsError) { _logger.LogDebug("Error setting headers on context, setting pipeline error"); - SetPipelineError(response.Errors); + SetPipelineError(context, response.Errors); return; } diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs index 2a8c50fa..69f23860 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Headers.Middleware { public static class HttpRequestHeadersBuilderMiddlewareExtensions { - public static IApplicationBuilder UseHttpRequestHeadersBuilderMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseHttpRequestHeadersBuilderMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 8a9803fb..f02230ec 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -5,6 +5,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancerFactory { - Task Get(ReRoute reRoute, ServiceProviderConfiguration config); + Task Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs index a4711fbe..d9d051c2 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs @@ -6,6 +6,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancerHouse { - Task> Get(ReRoute reRoute, ServiceProviderConfiguration config); + Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 3a583b8c..29d84ba4 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -12,7 +12,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers _serviceProviderFactory = serviceProviderFactory; } - public async Task Get(ReRoute reRoute, ServiceProviderConfiguration config) + public async Task Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) { var serviceProvider = _serviceProviderFactory.Get(config, reRoute); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 7fc1ae05..3d8059b9 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -18,7 +18,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers _loadBalancers = new ConcurrentDictionary(); } - public async Task> Get(ReRoute reRoute, ServiceProviderConfiguration config) + public async Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) { try { diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index aa37196f..8c2e963a 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; @@ -11,28 +12,26 @@ namespace Ocelot.LoadBalancer.Middleware { public class LoadBalancingMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; private readonly ILoadBalancerHouse _loadBalancerHouse; - public LoadBalancingMiddleware(RequestDelegate next, + public LoadBalancingMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, ILoadBalancerHouse loadBalancerHouse) - : base(requestScopedDataRepository) { _next = next; - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); _loadBalancerHouse = loadBalancerHouse; } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - var loadBalancer = await _loadBalancerHouse.Get(DownstreamRoute.ReRoute, ServiceProviderConfiguration); + var loadBalancer = await _loadBalancerHouse.Get(context.DownstreamReRoute, context.ServiceProviderConfiguration); if(loadBalancer.IsError) { _logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); - SetPipelineError(loadBalancer.Errors); + SetPipelineError(context, loadBalancer.Errors); return; } @@ -40,11 +39,11 @@ namespace Ocelot.LoadBalancer.Middleware if(hostAndPort.IsError) { _logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); - SetPipelineError(hostAndPort.Errors); + SetPipelineError(context, hostAndPort.Errors); return; } - var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri); + var uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri); uriBuilder.Host = hostAndPort.Data.DownstreamHost; @@ -53,7 +52,7 @@ namespace Ocelot.LoadBalancer.Middleware uriBuilder.Port = hostAndPort.Data.DownstreamPort; } - DownstreamRequest.RequestUri = uriBuilder.Uri; + context.DownstreamRequest.RequestUri = uriBuilder.Uri; try { diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs index db026396..a2da1060 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.LoadBalancer.Middleware { public static class LoadBalancingMiddlewareExtensions { - public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseLoadBalancingMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Middleware/BaseUrlFinder.cs b/src/Ocelot/Middleware/BaseUrlFinder.cs index de24e682..fe3acea7 100644 --- a/src/Ocelot/Middleware/BaseUrlFinder.cs +++ b/src/Ocelot/Middleware/BaseUrlFinder.cs @@ -13,9 +13,11 @@ namespace Ocelot.Middleware public string Find() { - var baseUrl = _config.GetValue("BaseUrl", ""); + //tries to get base url out of file... + var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", ""); - return string.IsNullOrEmpty(baseUrl) ? "http://localhost:5000" : baseUrl; + //falls back to memory config then finally default.. + return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl; } } } diff --git a/src/Ocelot/Middleware/DownstreamContext.cs b/src/Ocelot/Middleware/DownstreamContext.cs new file mode 100644 index 00000000..84ebc77a --- /dev/null +++ b/src/Ocelot/Middleware/DownstreamContext.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Errors; + +namespace Ocelot.Middleware +{ + public class DownstreamContext + { + public DownstreamContext(HttpContext httpContext) + { + this.HttpContext = httpContext; + Errors = new List(); + } + + public List TemplatePlaceholderNameAndValues { get; set; } + public ServiceProviderConfiguration ServiceProviderConfiguration {get; set;} + public HttpContext HttpContext { get; private set; } + public DownstreamReRoute DownstreamReRoute { get; set; } + public HttpRequestMessage DownstreamRequest { get; set; } + public HttpResponseMessage DownstreamResponse { get; set; } + public List Errors { get;set; } + //public string RequestId {get;set;} + //public string PreviousRequestId {get;set;} + public bool IsError => Errors.Count > 0; + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IMultiplexer.cs b/src/Ocelot/Middleware/Multiplexer/IMultiplexer.cs new file mode 100644 index 00000000..f3a2975d --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IMultiplexer.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IMultiplexer + { + Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs new file mode 100644 index 00000000..b85c2cc8 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IResponseAggregator + { + Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs new file mode 100644 index 00000000..c4ebb08b --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public class Multiplexer : IMultiplexer + { + private readonly IResponseAggregator _aggregator; + + public Multiplexer(IResponseAggregator aggregator) + { + _aggregator = aggregator; + } + + public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) + { + var tasks = new Task[reRoute.DownstreamReRoute.Count]; + + for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, + ServiceProviderConfiguration = context.ServiceProviderConfiguration, + DownstreamReRoute = reRoute.DownstreamReRoute[i], + }; + + tasks[i] = Fire(downstreamContext, next); + } + + await Task.WhenAll(tasks); + + var downstreamContexts = new List(); + + foreach (var task in tasks) + { + var finished = await task; + downstreamContexts.Add(finished); + } + + await _aggregator.Aggregate(reRoute, context, downstreamContexts); + } + + private async Task Fire(DownstreamContext context, OcelotRequestDelegate next) + { + await next.Invoke(context); + return context; + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs new file mode 100644 index 00000000..3d9a997d --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public class SimpleJsonResponseAggregator : IResponseAggregator + { + public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts) + { + if (reRoute.DownstreamReRoute.Count > 1) + { + await MapAggregtes(originalContext, downstreamContexts); + } + else + { + MapNotAggregate(originalContext, downstreamContexts); + } + } + + private async Task MapAggregtes(DownstreamContext originalContext, List downstreamContexts) + { + await MapAggregateContent(originalContext, downstreamContexts); + } + + private static async Task MapAggregateContent(DownstreamContext originalContext, List downstreamContexts) + { + var contentBuilder = new StringBuilder(); + + contentBuilder.Append("{"); + + for (int i = 0; i < downstreamContexts.Count; i++) + { + if (downstreamContexts[i].IsError) + { + MapAggregateError(originalContext, downstreamContexts, i); + return; + } + + var content = await downstreamContexts[i].DownstreamResponse.Content.ReadAsStringAsync(); + + contentBuilder.Append($"\"{downstreamContexts[i].DownstreamReRoute.Key}\":{content}"); + + if (i + 1 < downstreamContexts.Count) + { + contentBuilder.Append(","); + } + } + + contentBuilder.Append("}"); + + originalContext.DownstreamResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(contentBuilder.ToString()) + { + Headers = {ContentType = new MediaTypeHeaderValue("application/json")} + } + }; + } + + private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) + { + originalContext.Errors.AddRange(downstreamContexts[i].Errors); + originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; + } + + private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) + { + //assume at least one..if this errors then it will be caught by global exception handler + var finished = downstreamContexts.First(); + + originalContext.Errors = finished.Errors; + + originalContext.DownstreamRequest = finished.DownstreamRequest; + + originalContext.DownstreamResponse = finished.DownstreamResponse; + } + } +} diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index 8b1eae2e..bf49fd03 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -1,67 +1,20 @@ using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder; using Ocelot.Errors; -using Ocelot.Infrastructure.RequestData; namespace Ocelot.Middleware { public abstract class OcelotMiddleware { - private readonly IRequestScopedDataRepository _requestScopedDataRepository; - - protected OcelotMiddleware(IRequestScopedDataRepository requestScopedDataRepository) + protected OcelotMiddleware() { - _requestScopedDataRepository = requestScopedDataRepository; MiddlewareName = this.GetType().Name; } public string MiddlewareName { get; } - public bool PipelineError => _requestScopedDataRepository.Get("OcelotMiddlewareError").Data; - - public List PipelineErrors => _requestScopedDataRepository.Get>("OcelotMiddlewareErrors").Data; - - public DownstreamRoute DownstreamRoute => _requestScopedDataRepository.Get("DownstreamRoute").Data; - - public Request.Request Request => _requestScopedDataRepository.Get("Request").Data; - - public HttpRequestMessage DownstreamRequest => _requestScopedDataRepository.Get("DownstreamRequest").Data; - - public HttpResponseMessage HttpResponseMessage => _requestScopedDataRepository.Get("HttpResponseMessage").Data; - - public ServiceProviderConfiguration ServiceProviderConfiguration => _requestScopedDataRepository.Get("ServiceProviderConfiguration").Data; - - public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute) + public void SetPipelineError(DownstreamContext context, List errors) { - _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute); - } - - public void SetServiceProviderConfigurationForThisRequest(ServiceProviderConfiguration serviceProviderConfiguration) - { - _requestScopedDataRepository.Add("ServiceProviderConfiguration", serviceProviderConfiguration); - } - - public void SetUpstreamRequestForThisRequest(Request.Request request) - { - _requestScopedDataRepository.Add("Request", request); - } - - public void SetDownstreamRequest(HttpRequestMessage request) - { - _requestScopedDataRepository.Add("DownstreamRequest", request); - } - - public void SetHttpResponseMessageThisRequest(HttpResponseMessage responseMessage) - { - _requestScopedDataRepository.Add("HttpResponseMessage", responseMessage); - } - - public void SetPipelineError(List errors) - { - _requestScopedDataRepository.Add("OcelotMiddlewareError", true); - _requestScopedDataRepository.Add("OcelotMiddlewareErrors", errors); + context.Errors = errors; } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 249dceb5..541a8e93 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Linq; using System.Threading.Tasks; - using Authorisation.Middleware; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -15,24 +14,11 @@ using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; - using Ocelot.LoadBalancer.Middleware; using Ocelot.Responses; - using Ocelot.Authentication.Middleware; - using Ocelot.Cache.Middleware; - using Ocelot.Claims.Middleware; - using Ocelot.DownstreamRouteFinder.Middleware; - using Ocelot.DownstreamUrlCreator.Middleware; - using Ocelot.Errors.Middleware; - using Ocelot.Headers.Middleware; using Ocelot.Logging; - using Ocelot.QueryStrings.Middleware; - using Ocelot.Request.Middleware; - using Ocelot.Requester.Middleware; - using Ocelot.RequestId.Middleware; - using Ocelot.Responder.Middleware; - using Ocelot.RateLimit.Middleware; using Rafty.Concensus; using Rafty.Infrastructure; + using Ocelot.Middleware.Pipeline; public static class OcelotMiddlewareExtensions { @@ -43,7 +29,7 @@ /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await builder.UseOcelot(new OcelotMiddlewareConfiguration()); + await builder.UseOcelot(new OcelotPipelineConfiguration()); return builder; } @@ -52,9 +38,9 @@ /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration /// /// - /// + /// /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var configuration = await CreateConfiguration(builder); @@ -67,91 +53,21 @@ ConfigureDiagnosticListener(builder); - // This is registered to catch any global exceptions that are not handled - // It also sets the Request Id if anything is set globally - builder.UseExceptionHandlerMiddleware(); + var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); - // Allow the user to respond with absolutely anything they want. - builder.UseIfNotNull(middlewareConfiguration.PreErrorResponderMiddleware); + pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); - // This is registered first so it can catch any errors and issue an appropriate response - builder.UseResponderMiddleware(); + var firstDelegate = pipelineBuilder.Build(); - // Then we get the downstream route information - builder.UseDownstreamRouteFinderMiddleware(); + //inject first delegate into first piece of asp.net middleware..maybe not like this + //then because we are updating the http context in ocelot it comes out correct for + //rest of asp.net.. - // Now we have the ds route we can transform headers and stuff? - builder.UseHttpHeadersTransformationMiddleware(); - - // Initialises downstream request - builder.UseDownstreamRequestInitialiser(); - - // We check whether the request is ratelimit, and if there is no continue processing - builder.UseRateLimiting(); - - // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware) - // If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten - // This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware. - builder.UseRequestIdMiddleware(); - - // Allow pre authentication logic. The idea being people might want to run something custom before what is built in. - builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware); - - // Now we know where the client is going to go we can authenticate them. - // We allow the ocelot middleware to be overriden by whatever the - // user wants - if (middlewareConfiguration.AuthenticationMiddleware == null) + builder.Use(async (context, task) => { - builder.UseAuthenticationMiddleware(); - } - else - { - builder.Use(middlewareConfiguration.AuthenticationMiddleware); - } - - // The next thing we do is look at any claims transforms in case this is important for authorisation - builder.UseClaimsBuilderMiddleware(); - - // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. - builder.UseIfNotNull(middlewareConfiguration.PreAuthorisationMiddleware); - - // Now we have authenticated and done any claims transformation we - // can authorise the request - // We allow the ocelot middleware to be overriden by whatever the - // user wants - if (middlewareConfiguration.AuthorisationMiddleware == null) - { - builder.UseAuthorisationMiddleware(); - } - else - { - builder.Use(middlewareConfiguration.AuthorisationMiddleware); - } - - // Now we can run any header transformation logic - builder.UseHttpRequestHeadersBuilderMiddleware(); - - // Allow the user to implement their own query string manipulation logic - builder.UseIfNotNull(middlewareConfiguration.PreQueryStringBuilderMiddleware); - - // Now we can run any query string transformation logic - builder.UseQueryStringBuilderMiddleware(); - - // Get the load balancer for this request - builder.UseLoadBalancingMiddleware(); - - // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used - builder.UseDownstreamUrlCreatorMiddleware(); - - // Not sure if this is the best place for this but we use the downstream url - // as the basis for our cache key. - builder.UseOutputCacheMiddleware(); - - // Everything should now be ready to build or HttpRequest - builder.UseHttpRequestBuilderMiddleware(); - - //We fire off the request and set the response on the scoped data repo - builder.UseHttpRequesterMiddleware(); + var downstreamContext = new DownstreamContext(context); + await firstDelegate.Invoke(downstreamContext); + }); return builder; } @@ -257,23 +173,34 @@ var ocelotConfigurationRepository = (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( typeof(IOcelotConfigurationRepository)); + var ocelotConfigurationCreator = (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( typeof(IOcelotConfigurationCreator)); var fileConfigFromConsul = await consulFileConfigRepo.Get(); + if (fileConfigFromConsul.Data == null) { config = await setter.Set(fileConfig.Value); + var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); } else { var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); + if(ocelotConfig.IsError) { return new ErrorResponse(ocelotConfig.Errors); } + config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); + + if (config.IsError) + { + return new ErrorResponse(config.Errors); + } + //todo - this starts the poller if it has been registered...please this is so bad. var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs similarity index 63% rename from src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs rename to src/Ocelot/Middleware/OcelotPipelineConfiguration.cs index e3d22928..3cfded9b 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs @@ -1,44 +1,43 @@ -namespace Ocelot.Middleware -{ - using System; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - - public class OcelotMiddlewareConfiguration - { - /// - /// This is called after the global error handling middleware so any code before calling next.invoke - /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called - /// in the Ocelot pipeline before we go to the global error handler. - /// - public Func, Task> PreErrorResponderMiddleware { get; set; } - - /// - /// This is to allow the user to run any extra authentication before the Ocelot authentication - /// kicks in - /// - public Func, Task> PreAuthenticationMiddleware { get; set; } - - /// - /// This allows the user to completely override the ocelot authentication middleware - /// - public Func, Task> AuthenticationMiddleware { get; set; } - - /// - /// This is to allow the user to run any extra authorisation before the Ocelot authentication - /// kicks in - /// - public Func, Task> PreAuthorisationMiddleware { get; set; } - - /// - /// This allows the user to completely override the ocelot authorisation middleware - /// - public Func, Task> AuthorisationMiddleware { get; set; } - - /// - /// This allows the user to implement there own query string manipulation logic - /// - public Func, Task> PreQueryStringBuilderMiddleware { get; set; } - - } -} \ No newline at end of file +namespace Ocelot.Middleware +{ + using System; + using System.Threading.Tasks; + + public class OcelotPipelineConfiguration + { + /// + /// This is called after the global error handling middleware so any code before calling next.invoke + /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called + /// in the Ocelot pipeline before we go to the global error handler. + /// + public Func, Task> PreErrorResponderMiddleware { get; set; } + + /// + /// This is to allow the user to run any extra authentication before the Ocelot authentication + /// kicks in + /// + public Func, Task> PreAuthenticationMiddleware { get; set; } + + /// + /// This allows the user to completely override the ocelot authentication middleware + /// + public Func, Task> AuthenticationMiddleware { get; set; } + + /// + /// This is to allow the user to run any extra authorisation before the Ocelot authentication + /// kicks in + /// + public Func, Task> PreAuthorisationMiddleware { get; set; } + + /// + /// This allows the user to completely override the ocelot authorisation middleware + /// + public Func, Task> AuthorisationMiddleware { get; set; } + + /// + /// This allows the user to implement there own query string manipulation logic + /// + public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + + } +} diff --git a/src/Ocelot/Middleware/OcelotRequestDelegate.cs b/src/Ocelot/Middleware/OcelotRequestDelegate.cs new file mode 100644 index 00000000..130dfd86 --- /dev/null +++ b/src/Ocelot/Middleware/OcelotRequestDelegate.cs @@ -0,0 +1,6 @@ +using System.Threading.Tasks; + +namespace Ocelot.Middleware +{ + public delegate Task OcelotRequestDelegate(DownstreamContext downstreamContext); +} diff --git a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs new file mode 100644 index 00000000..3bc0d6b0 --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages + +using System; + +namespace Ocelot.Middleware.Pipeline +{ + public interface IOcelotPipelineBuilder + { + IServiceProvider ApplicationServices { get; } + OcelotPipelineBuilder Use(Func middleware); + OcelotRequestDelegate Build(); + } +} diff --git a/src/Ocelot/Middleware/Pipeline/LICENSE.txt b/src/Ocelot/Middleware/Pipeline/LICENSE.txt new file mode 100644 index 00000000..7b2956ec --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/LICENSE.txt @@ -0,0 +1,14 @@ +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs new file mode 100644 index 00000000..1e37514c --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Middleware.Pipeline +{ + public class OcelotPipelineBuilder : IOcelotPipelineBuilder + { + private readonly IList> _middlewares; + + public OcelotPipelineBuilder(IServiceProvider provider) + { + ApplicationServices = provider; + _middlewares = new List>(); + } + + public IServiceProvider ApplicationServices { get; } + + public OcelotPipelineBuilder Use(Func middleware) + { + _middlewares.Add(middleware); + return this; + } + + public OcelotRequestDelegate Build() + { + OcelotRequestDelegate app = context => + { + context.HttpContext.Response.StatusCode = 404; + return Task.CompletedTask; + }; + + foreach (var component in _middlewares.Reverse()) + { + app = component(app); + } + + return app; + } + } +} diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs new file mode 100644 index 00000000..422097fc --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Ocelot.Middleware.Pipeline +{ + public static class OcelotPipelineBuilderExtensions + { + internal const string InvokeMethodName = "Invoke"; + internal const string InvokeAsyncMethodName = "InvokeAsync"; + private static readonly MethodInfo GetServiceInfo = typeof(OcelotPipelineBuilderExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static); + + public static IOcelotPipelineBuilder UseMiddleware(this IOcelotPipelineBuilder app, params object[] args) + { + return app.UseMiddleware(typeof(TMiddleware), args); + } + + public static IOcelotPipelineBuilder Use(this IOcelotPipelineBuilder app, Func, Task> middleware) + { + return app.Use(next => + { + return context => + { + Func simpleNext = () => next(context); + return middleware(context, simpleNext); + }; + }); + } + + public static IOcelotPipelineBuilder UseMiddleware(this IOcelotPipelineBuilder app, Type middleware, params object[] args) + { + return app.Use(next => + { + var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); + var invokeMethods = methods.Where(m => + string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal) + || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal) + ).ToArray(); + + if (invokeMethods.Length > 1) + { + throw new InvalidOperationException(); + } + + if (invokeMethods.Length == 0) + { + throw new InvalidOperationException(); + } + + var methodinfo = invokeMethods[0]; + if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType)) + { + throw new InvalidOperationException(); + } + + var parameters = methodinfo.GetParameters(); + if (parameters.Length == 0 || parameters[0].ParameterType != typeof(DownstreamContext)) + { + throw new InvalidOperationException(); + } + + var ctorArgs = new object[args.Length + 1]; + ctorArgs[0] = next; + Array.Copy(args, 0, ctorArgs, 1, args.Length); + var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); + if (parameters.Length == 1) + { + return (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance); + } + + var factory = Compile(methodinfo, parameters); + + return context => + { + var serviceProvider = context.HttpContext.RequestServices ?? app.ApplicationServices; + if (serviceProvider == null) + { + throw new InvalidOperationException(); + } + + return factory(instance, context, serviceProvider); + }; + }); + } + + private static Func Compile(MethodInfo methodinfo, ParameterInfo[] parameters) + { + var middleware = typeof(T); + var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext"); + var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider"); + var instanceArg = Expression.Parameter(middleware, "middleware"); + + var methodArguments = new Expression[parameters.Length]; + methodArguments[0] = httpContextArg; + for (int i = 1; i < parameters.Length; i++) + { + var parameterType = parameters[i].ParameterType; + if (parameterType.IsByRef) + { + throw new NotSupportedException(); + } + + var parameterTypeExpression = new Expression[] + { + providerArg, + Expression.Constant(parameterType, typeof(Type)), + Expression.Constant(methodinfo.DeclaringType, typeof(Type)) + }; + + var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression); + methodArguments[i] = Expression.Convert(getServiceCall, parameterType); + } + + Expression middlewareInstanceArg = instanceArg; + if (methodinfo.DeclaringType != typeof(T)) + { + middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType); + } + + var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments); + + var lambda = Expression.Lambda>(body, instanceArg, httpContextArg, providerArg); + + return lambda.Compile(); + } + + private static object GetService(IServiceProvider sp, Type type) + { + var service = sp.GetService(type); + if (service == null) + { + throw new InvalidOperationException(); + } + + return service; + } + } +} diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs new file mode 100644 index 00000000..374b867f --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -0,0 +1,121 @@ +using System; +using System.Threading.Tasks; +using Ocelot.Authentication.Middleware; +using Ocelot.Authorisation.Middleware; +using Ocelot.Cache.Middleware; +using Ocelot.Claims.Middleware; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.DownstreamUrlCreator.Middleware; +using Ocelot.Errors.Middleware; +using Ocelot.Headers.Middleware; +using Ocelot.LoadBalancer.Middleware; +using Ocelot.QueryStrings.Middleware; +using Ocelot.RateLimit.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Requester.Middleware; +using Ocelot.RequestId.Middleware; +using Ocelot.Responder.Middleware; + +namespace Ocelot.Middleware.Pipeline +{ + public static class OcelotPipelineExtensions + { + public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder, + OcelotPipelineConfiguration pipelineConfiguration = null) + { + // This is registered to catch any global exceptions that are not handled + // It also sets the Request Id if anything is set globally + builder.UseExceptionHandlerMiddleware(); + + // Allow the user to respond with absolutely anything they want. + builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware); + + // This is registered first so it can catch any errors and issue an appropriate response + builder.UseResponderMiddleware(); + + // Then we get the downstream route information + builder.UseDownstreamRouteFinderMiddleware(); + + // Now we have the ds route we can transform headers and stuff? + builder.UseHttpHeadersTransformationMiddleware(); + + // Initialises downstream request + builder.UseDownstreamRequestInitialiser(); + + // We check whether the request is ratelimit, and if there is no continue processing + builder.UseRateLimiting(); + + // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware) + // If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten + // This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware. + builder.UseRequestIdMiddleware(); + + // Allow pre authentication logic. The idea being people might want to run something custom before what is built in. + builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware); + + // Now we know where the client is going to go we can authenticate them. + // We allow the ocelot middleware to be overriden by whatever the + // user wants + if (pipelineConfiguration.AuthenticationMiddleware == null) + { + builder.UseAuthenticationMiddleware(); + } + else + { + builder.Use(pipelineConfiguration.AuthenticationMiddleware); + } + + // The next thing we do is look at any claims transforms in case this is important for authorisation + builder.UseClaimsBuilderMiddleware(); + + // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. + builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware); + + // Now we have authenticated and done any claims transformation we + // can authorise the request + // We allow the ocelot middleware to be overriden by whatever the + // user wants + if (pipelineConfiguration.AuthorisationMiddleware == null) + { + builder.UseAuthorisationMiddleware(); + } + else + { + builder.Use(pipelineConfiguration.AuthorisationMiddleware); + } + + // Now we can run any header transformation logic + builder.UseHttpRequestHeadersBuilderMiddleware(); + + // Allow the user to implement their own query string manipulation logic + builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware); + + // Now we can run any query string transformation logic + builder.UseQueryStringBuilderMiddleware(); + + // Get the load balancer for this request + builder.UseLoadBalancingMiddleware(); + + // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used + builder.UseDownstreamUrlCreatorMiddleware(); + + // Not sure if this is the best place for this but we use the downstream url + // as the basis for our cache key. + builder.UseOutputCacheMiddleware(); + + //We fire off the request and set the response on the scoped data repo + builder.UseHttpRequesterMiddleware(); + + return builder.Build(); + } + + private static void UseIfNotNull(this IOcelotPipelineBuilder builder, + Func, Task> middleware) + { + if (middleware != null) + { + builder.Use(middleware); + } + } + } +} diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index eb4ce773..62552bed 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -23,7 +23,7 @@ True - + diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index bdd79730..ae5dca85 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; @@ -9,34 +10,32 @@ namespace Ocelot.QueryStrings.Middleware { public class QueryStringBuilderMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IAddQueriesToRequest _addQueriesToRequest; private readonly IOcelotLogger _logger; - public QueryStringBuilderMiddleware(RequestDelegate next, + public QueryStringBuilderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, IAddQueriesToRequest addQueriesToRequest) - : base(requestScopedDataRepository) { _next = next; _addQueriesToRequest = addQueriesToRequest; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - if (DownstreamRoute.ReRoute.ClaimsToQueries.Any()) + if (context.DownstreamReRoute.ClaimsToQueries.Any()) { - _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); + _logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); - var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToQueries, context.User.Claims, DownstreamRequest); + var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest); if (response.IsError) { _logger.LogDebug("there was an error setting queries on context, setting pipeline error"); - SetPipelineError(response.Errors); + SetPipelineError(context, response.Errors); return; } } diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs index 647afb07..0a32f727 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.QueryStrings.Middleware { public static class QueryStringBuilderMiddlewareExtensions { - public static IApplicationBuilder UseQueryStringBuilderMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseQueryStringBuilderMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index e8dbaeef..064ec54b 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -7,21 +7,20 @@ using Ocelot.Infrastructure.RequestData; using Microsoft.AspNetCore.Http; using Ocelot.Logging; using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.Middleware; namespace Ocelot.RateLimit.Middleware { public class ClientRateLimitMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; private readonly IRateLimitCounterHandler _counterHandler; private readonly ClientRateLimitProcessor _processor; - public ClientRateLimitMiddleware(RequestDelegate next, + public ClientRateLimitMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, IRateLimitCounterHandler counterHandler) - : base(requestScopedDataRepository) { _next = next; _logger = loggerFactory.CreateLogger(); @@ -29,23 +28,23 @@ namespace Ocelot.RateLimit.Middleware _processor = new ClientRateLimitProcessor(counterHandler); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - var options = DownstreamRoute.ReRoute.RateLimitOptions; + var options = context.DownstreamReRoute.RateLimitOptions; // check if rate limiting is enabled - if (!DownstreamRoute.ReRoute.EnableEndpointEndpointRateLimiting) + if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting) { - _logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}"); + _logger.LogDebug($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate}"); await _next.Invoke(context); return; } // compute identity from request - var identity = SetIdentity(context, options); + var identity = SetIdentity(context.HttpContext, options); // check white list if (IsWhitelisted(identity, options)) { - _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting"); + _logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate} is white listed from rate limiting"); await _next.Invoke(context); return; } @@ -63,11 +62,11 @@ namespace Ocelot.RateLimit.Middleware var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule); // log blocked request - LogBlockedRequest(context, identity, counter, rule); + LogBlockedRequest(context.HttpContext, identity, counter, rule, context.DownstreamReRoute); var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); // break execution - await ReturnQuotaExceededResponse(context, options, retrystring); + await ReturnQuotaExceededResponse(context.HttpContext, options, retrystring); return; } @@ -76,8 +75,8 @@ namespace Ocelot.RateLimit.Middleware //set X-Rate-Limit headers for the longest period if (!options.DisableRateLimitHeaders) { - var headers = _processor.GetRateLimitHeaders(context, identity, options); - context.Response.OnStarting(SetRateLimitHeaders, state: headers); + var headers = _processor.GetRateLimitHeaders(context.HttpContext, identity, options); + context.HttpContext.Response.OnStarting(SetRateLimitHeaders, state: headers); } await _next.Invoke(context); @@ -107,9 +106,9 @@ namespace Ocelot.RateLimit.Middleware return false; } - public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) + public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute) { - _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); + _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); } public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) diff --git a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs index a1533e00..2583be4a 100644 --- a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs +++ b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs @@ -1,14 +1,10 @@ -using Microsoft.AspNetCore.Builder; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Ocelot.Middleware.Pipeline; namespace Ocelot.RateLimit.Middleware { public static class RateLimitMiddlewareExtensions { - public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseRateLimiting(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs deleted file mode 100644 index 5b8ef968..00000000 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Responses; -using Ocelot.Requester.QoS; -using System.Net.Http; - -namespace Ocelot.Request.Builder -{ - public sealed class HttpRequestCreator : IRequestCreator - { - public async Task> Build( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool useCookieContainer, - bool allowAutoRedirect, - string reRouteKey, - bool isTracing) - { - return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer, reRouteKey, isTracing)); - } - } -} diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs deleted file mode 100644 index abde4a6b..00000000 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Ocelot.Request.Builder -{ - using System.Net.Http; - using System.Threading.Tasks; - - using Ocelot.Requester.QoS; - using Ocelot.Responses; - - public interface IRequestCreator - { - Task> Build( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool useCookieContainer, - bool allowAutoRedirect, - string reRouteKe, - bool isTracing); - } -} diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index 3ade73d1..f14c1394 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -2,38 +2,36 @@ namespace Ocelot.Request.Middleware { using System.Threading.Tasks; using Microsoft.AspNetCore.Http; - + using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; private readonly Mapper.IRequestMapper _requestMapper; - public DownstreamRequestInitialiserMiddleware(RequestDelegate next, + public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, Mapper.IRequestMapper requestMapper) - :base(requestScopedDataRepository) { _next = next; _logger = loggerFactory.CreateLogger(); _requestMapper = requestMapper; } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { - var downstreamRequest = await _requestMapper.Map(context.Request); + var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request); if (downstreamRequest.IsError) { - SetPipelineError(downstreamRequest.Errors); + SetPipelineError(context, downstreamRequest.Errors); return; } - SetDownstreamRequest(downstreamRequest.Data); + context.DownstreamRequest = downstreamRequest.Data; await _next.Invoke(context); } diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs deleted file mode 100644 index 4160db31..00000000 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Builder; -using Ocelot.Requester.QoS; - -namespace Ocelot.Request.Middleware -{ - public class HttpRequestBuilderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IRequestCreator _requestCreator; - private readonly IOcelotLogger _logger; - private readonly IQosProviderHouse _qosProviderHouse; - - public HttpRequestBuilderMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IRequestCreator requestCreator, - IQosProviderHouse qosProviderHouse) - :base(requestScopedDataRepository) - { - _next = next; - _requestCreator = requestCreator; - _qosProviderHouse = qosProviderHouse; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute); - - if (qosProvider.IsError) - { - _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); - - SetPipelineError(qosProvider.Errors); - - return; - } - - var buildResult = await _requestCreator.Build( - DownstreamRequest, - DownstreamRoute.ReRoute.IsQos, - qosProvider.Data, - DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, - DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect, - DownstreamRoute.ReRoute.ReRouteKey, - DownstreamRoute.ReRoute.HttpHandlerOptions.UseTracing); - - if (buildResult.IsError) - { - _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); - SetPipelineError(buildResult.Errors); - return; - } - - _logger.LogDebug("setting upstream request"); - - SetUpstreamRequestForThisRequest(buildResult.Data); - - await _next.Invoke(context); - } - } -} diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs index 80a41998..35084659 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs @@ -1,17 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Request.Middleware { public static class HttpRequestBuilderMiddlewareExtensions { - public static IApplicationBuilder UseHttpRequestBuilderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - - public static IApplicationBuilder UseDownstreamRequestInitialiser(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseDownstreamRequestInitialiser(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs deleted file mode 100644 index 3bb670f6..00000000 --- a/src/Ocelot/Request/Request.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Net.Http; -using Ocelot.Requester.QoS; - -namespace Ocelot.Request -{ - public class Request - { - public Request( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool allowAutoRedirect, - bool useCookieContainer, - string reRouteKey, - bool isTracing - ) - { - HttpRequestMessage = httpRequestMessage; - IsQos = isQos; - QosProvider = qosProvider; - AllowAutoRedirect = allowAutoRedirect; - UseCookieContainer = useCookieContainer; - ReRouteKey = reRouteKey; - IsTracing = isTracing; - } - - public HttpRequestMessage HttpRequestMessage { get; private set; } - public bool IsQos { get; private set; } - public bool IsTracing { get; private set; } - public IQoSProvider QosProvider { get; private set; } - public bool AllowAutoRedirect { get; private set; } - public bool UseCookieContainer { get; private set; } - public string ReRouteKey { get; private set; } - } -} \ No newline at end of file diff --git a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs index d4dfbe11..0b146b27 100644 --- a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs @@ -8,63 +8,65 @@ using Ocelot.Middleware; using System.Net.Http; using System.Net.Http.Headers; using System.Collections.Generic; +using Ocelot.DownstreamRouteFinder.Middleware; namespace Ocelot.RequestId.Middleware { public class ReRouteRequestIdMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IOcelotLogger _logger; private readonly IRequestScopedDataRepository _requestScopedDataRepository; - public ReRouteRequestIdMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, + + public ReRouteRequestIdMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository requestScopedDataRepository) - : base(requestScopedDataRepository) { _next = next; - _logger = loggerFactory.CreateLogger(); _requestScopedDataRepository = requestScopedDataRepository; + _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { SetOcelotRequestId(context); await _next.Invoke(context); } - private void SetOcelotRequestId(HttpContext context) + private void SetOcelotRequestId(DownstreamContext context) { // if get request ID is set on upstream request then retrieve it - var key = DownstreamRoute.ReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; + var key = context.DownstreamReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; StringValues upstreamRequestIds; - if (context.Request.Headers.TryGetValue(key, out upstreamRequestIds)) + if (context.HttpContext.Request.Headers.TryGetValue(key, out upstreamRequestIds)) { //set the traceidentifier - context.TraceIdentifier = upstreamRequestIds.First(); + context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); - //check if we have previous id + //todo fix looking in both places + //check if we have previous id in scoped repo var previousRequestId = _requestScopedDataRepository.Get("RequestId"); - if(!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data)) + if (!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data)) { //we have a previous request id lets store it and update request id _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); - _requestScopedDataRepository.Update("RequestId", context.TraceIdentifier); + _requestScopedDataRepository.Update("RequestId", context.HttpContext.TraceIdentifier); } else { //else just add request id - _requestScopedDataRepository.Add("RequestId", context.TraceIdentifier); + _requestScopedDataRepository.Add("RequestId", context.HttpContext.TraceIdentifier); } } // set request ID on downstream request, if required - var requestId = new RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier); + var requestId = new RequestId(context.DownstreamReRoute.RequestIdKey, context.HttpContext.TraceIdentifier); - if (ShouldAddRequestId(requestId, DownstreamRequest.Headers)) + if (ShouldAddRequestId(requestId, context.DownstreamRequest.Headers)) { - AddRequestIdHeader(requestId, DownstreamRequest); + AddRequestIdHeader(requestId, context.DownstreamRequest); } } @@ -86,4 +88,4 @@ namespace Ocelot.RequestId.Middleware httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs b/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs index 236c984a..f450f9a5 100644 --- a/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs +++ b/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.RequestId.Middleware { public static class RequestIdMiddlewareExtensions { - public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseRequestIdMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs index 0a7d6947..eec395fb 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using Ocelot.Configuration; using Ocelot.Errors; using Ocelot.Responses; @@ -17,7 +18,7 @@ namespace Ocelot.Requester _housed = new ConcurrentDictionary(); } - public Response Get(Request.Request request) + public Response Get(DownstreamReRoute request) { try { @@ -28,7 +29,15 @@ namespace Ocelot.Requester return new OkResponse(provider); } - provider = _factory.Get(request); + //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); } diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs index 1e6cb4c4..468b6013 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs @@ -1,22 +1,31 @@ +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 ITracingHandler _tracingHandler; + private readonly ITracingHandlerFactory _factory; private readonly IOcelotLoggerFactory _loggerFactory; private readonly IDelegatingHandlerHandlerProvider _allRoutesProvider; + private readonly IQosProviderHouse _qosProviderHouse; - public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, IDelegatingHandlerHandlerProvider allRoutesProvider, ITracingHandler tracingHandler) + public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, + IDelegatingHandlerHandlerProvider allRoutesProvider, + ITracingHandlerFactory factory, + IQosProviderHouse qosProviderHouse) { - _tracingHandler = tracingHandler; + _factory = factory; _loggerFactory = loggerFactory; _allRoutesProvider = allRoutesProvider; + _qosProviderHouse = qosProviderHouse; } - public IDelegatingHandlerHandlerProvider Get(Request.Request request) + public Response Get(DownstreamReRoute request) { var handlersAppliedToAll = _allRoutesProvider.Get(); @@ -27,17 +36,24 @@ namespace Ocelot.Requester provider.Add(handler); } - if (request.IsTracing) + if (request.HttpHandlerOptions.UseTracing) { - provider.Add(() => (DelegatingHandler)_tracingHandler); + provider.Add(() => (DelegatingHandler)_factory.Get()); } if (request.IsQos) { - provider.Add(() => new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _loggerFactory)); + var qosProvider = _qosProviderHouse.Get(request); + + if (qosProvider.IsError) + { + return new ErrorResponse(qosProvider.Errors); + } + + provider.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); } - return provider; + return new OkResponse(provider); } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 561b5205..2d3a0f36 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net.Http; +using Ocelot.Configuration; namespace Ocelot.Requester { @@ -12,21 +13,23 @@ namespace Ocelot.Requester _house = house; } - public IHttpClient Create(Request.Request request) + public IHttpClient Create(DownstreamReRoute request) { - var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.AllowAutoRedirect, UseCookies = request.UseCookieContainer}; + var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.HttpHandlerOptions.AllowAutoRedirect, UseCookies = request.HttpHandlerOptions.UseCookieContainer}; var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request)); return new HttpClientWrapper(client); } - private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, Request.Request request) + private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request) { var provider = _house.Get(request); + var handlers = provider.Data.Get(); + //todo handle error - provider.Data.Get() + handlers .Select(handler => handler) .Reverse() .ToList() @@ -39,4 +42,4 @@ namespace Ocelot.Requester return httpMessageHandler; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 9fa3b271..a531d9b9 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -2,6 +2,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; using Ocelot.Logging; +using Ocelot.Middleware; using Ocelot.Responses; using Polly.CircuitBreaker; using Polly.Timeout; @@ -23,7 +24,7 @@ namespace Ocelot.Requester _house = house; } - public async Task> GetResponse(Request.Request request) + public async Task> GetResponse(DownstreamContext request) { var builder = new HttpClientBuilder(_house); @@ -33,7 +34,7 @@ namespace Ocelot.Requester try { - var response = await httpClient.SendAsync(request.HttpRequestMessage); + var response = await httpClient.SendAsync(request.DownstreamRequest); return new OkResponse(response); } catch (TimeoutRejectedException exception) @@ -57,28 +58,23 @@ namespace Ocelot.Requester } - private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, Request.Request request) + private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, DownstreamContext request) { var httpClient = _cacheHandlers.Get(cacheKey); if (httpClient == null) { - httpClient = builder.Create(request); + httpClient = builder.Create(request.DownstreamReRoute); } return httpClient; } - private string GetCacheKey(Request.Request request) + private string GetCacheKey(DownstreamContext request) { - var baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; - - if (request.IsQos) - { - baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}"; - } + var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}"; return baseUrl; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs index 78dd1fc1..b236ed16 100644 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs +++ b/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs @@ -1,9 +1,10 @@ +using Ocelot.Configuration; using Ocelot.Responses; namespace Ocelot.Requester { public interface IDelegatingHandlerHandlerHouse { - Response Get(Request.Request request); + Response Get(DownstreamReRoute request); } } diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs index dcf007d4..c77a62bd 100644 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs @@ -1,7 +1,10 @@ +using Ocelot.Configuration; +using Ocelot.Responses; + namespace Ocelot.Requester { public interface IDelegatingHandlerHandlerProviderFactory { - IDelegatingHandlerHandlerProvider Get(Request.Request request); + Response Get(DownstreamReRoute request); } } diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index d6b43501..f10e55f5 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -9,6 +9,6 @@ namespace Ocelot.Requester /// Creates the /// /// - IHttpClient Create(Request.Request request); + IHttpClient Create(DownstreamReRoute request); } } diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index 81d9fa22..5d9aa5dc 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -1,11 +1,12 @@ using System.Net.Http; using System.Threading.Tasks; +using Ocelot.Middleware; using Ocelot.Responses; namespace Ocelot.Requester { public interface IHttpRequester { - Task> GetResponse(Request.Request request); + Task> GetResponse(DownstreamContext request); } } diff --git a/src/Ocelot/Requester/ITracingHandlerFactory.cs b/src/Ocelot/Requester/ITracingHandlerFactory.cs new file mode 100644 index 00000000..6f0f6619 --- /dev/null +++ b/src/Ocelot/Requester/ITracingHandlerFactory.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Requester +{ + public interface ITracingHandlerFactory + { + ITracingHandler Get(); + } +} diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index ae3e7c5f..68397b9f 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -3,42 +3,41 @@ using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.Threading.Tasks; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Requester.QoS; namespace Ocelot.Requester.Middleware { public class HttpRequesterMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IHttpRequester _requester; private readonly IOcelotLogger _logger; - public HttpRequesterMiddleware(RequestDelegate next, + public HttpRequesterMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IHttpRequester requester, - IRequestScopedDataRepository requestScopedDataRepository -) - :base(requestScopedDataRepository) + IHttpRequester requester) { _next = next; _requester = requester; _logger = loggerFactory.CreateLogger(); } - public async Task Invoke(HttpContext context) - { - var response = await _requester.GetResponse(Request); + public async Task Invoke(DownstreamContext context) + { + var response = await _requester.GetResponse(context); if (response.IsError) { _logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); - SetPipelineError(response.Errors); + SetPipelineError(context, response.Errors); return; } _logger.LogDebug("setting http response message"); - SetHttpResponseMessageThisRequest(response.Data); + context.DownstreamResponse = response.Data; } } } diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs index b32d3247..82013d4c 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Requester.Middleware { public static class HttpRequesterMiddlewareExtensions { - public static IApplicationBuilder UseHttpRequesterMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseHttpRequesterMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs index ff588fe8..e2658c7f 100644 --- a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -12,11 +12,6 @@ namespace Ocelot.Requester { } - public class NoTracingHandler : DelegatingHandler, ITracingHandler - { - - } - public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler { private readonly IServiceTracer _tracer; diff --git a/src/Ocelot/Requester/QoS/IQoSProvider.cs b/src/Ocelot/Requester/QoS/IQoSProvider.cs index 599a5b7a..7382cb06 100644 --- a/src/Ocelot/Requester/QoS/IQoSProvider.cs +++ b/src/Ocelot/Requester/QoS/IQoSProvider.cs @@ -4,4 +4,4 @@ { CircuitBreaker CircuitBreaker { get; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs index 043ec19d..2152fb41 100644 --- a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs +++ b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs @@ -5,6 +5,6 @@ namespace Ocelot.Requester.QoS { public interface IQoSProviderFactory { - IQoSProvider Get(ReRoute reRoute); + IQoSProvider Get(DownstreamReRoute reRoute); } } diff --git a/src/Ocelot/Requester/QoS/IQosProviderHouse.cs b/src/Ocelot/Requester/QoS/IQosProviderHouse.cs index add24a6b..29f1324a 100644 --- a/src/Ocelot/Requester/QoS/IQosProviderHouse.cs +++ b/src/Ocelot/Requester/QoS/IQosProviderHouse.cs @@ -5,6 +5,6 @@ namespace Ocelot.Requester.QoS { public interface IQosProviderHouse { - Response Get(ReRoute reRoute); + Response Get(DownstreamReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/PollyQoSProvider.cs b/src/Ocelot/Requester/QoS/PollyQoSProvider.cs index 7a23986c..029650e5 100644 --- a/src/Ocelot/Requester/QoS/PollyQoSProvider.cs +++ b/src/Ocelot/Requester/QoS/PollyQoSProvider.cs @@ -15,7 +15,7 @@ namespace Ocelot.Requester.QoS private readonly IOcelotLogger _logger; private readonly CircuitBreaker _circuitBreaker; - public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory) + public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); @@ -48,4 +48,4 @@ namespace Ocelot.Requester.QoS public CircuitBreaker CircuitBreaker => _circuitBreaker; } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/QoS/QoSProviderFactory.cs b/src/Ocelot/Requester/QoS/QoSProviderFactory.cs index 0150cf8f..f985c9b5 100644 --- a/src/Ocelot/Requester/QoS/QoSProviderFactory.cs +++ b/src/Ocelot/Requester/QoS/QoSProviderFactory.cs @@ -12,7 +12,7 @@ namespace Ocelot.Requester.QoS _loggerFactory = loggerFactory; } - public IQoSProvider Get(ReRoute reRoute) + public IQoSProvider Get(DownstreamReRoute reRoute) { if (reRoute.IsQos) { @@ -22,4 +22,4 @@ namespace Ocelot.Requester.QoS return new NoQoSProvider(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/QoS/QosProviderHouse.cs b/src/Ocelot/Requester/QoS/QosProviderHouse.cs index 6d09f274..64e2bb7d 100644 --- a/src/Ocelot/Requester/QoS/QosProviderHouse.cs +++ b/src/Ocelot/Requester/QoS/QosProviderHouse.cs @@ -17,7 +17,7 @@ namespace Ocelot.Requester.QoS _qoSProviders = new ConcurrentDictionary(); } - public Response Get(ReRoute reRoute) + public Response Get(DownstreamReRoute reRoute) { try { @@ -50,4 +50,4 @@ namespace Ocelot.Requester.QoS _qoSProviders.AddOrUpdate(key, qosProvider, (x, y) => qosProvider); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs new file mode 100644 index 00000000..5cb72a79 --- /dev/null +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -0,0 +1,32 @@ +using Butterfly.Client.Tracing; +using Butterfly.OpenTracing; + +namespace Ocelot.Requester +{ + public class TracingHandlerFactory : ITracingHandlerFactory + { + private readonly IServiceTracer _tracer; + + public TracingHandlerFactory(IServiceTracer tracer) + { + _tracer = tracer; + } + + public ITracingHandler Get() + { + return new OcelotHttpTracingHandler(_tracer); + } + } + + public class FakeServiceTracer : IServiceTracer + { + public ITracer Tracer { get; } + public string ServiceName { get; } + public string Environment { get; } + public string Identity { get; } + public ISpan Start(ISpanBuilder spanBuilder) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index d0c12642..bac062f8 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -5,6 +5,7 @@ using Ocelot.Logging; using Ocelot.Middleware; using System.Collections.Generic; using System.Threading.Tasks; +using Ocelot.DownstreamRouteFinder.Middleware; namespace Ocelot.Responder.Middleware { @@ -13,18 +14,16 @@ namespace Ocelot.Responder.Middleware /// public class ResponderMiddleware : OcelotMiddleware { - private readonly RequestDelegate _next; + private readonly OcelotRequestDelegate _next; private readonly IHttpResponder _responder; private readonly IErrorsToHttpStatusCodeMapper _codeMapper; private readonly IOcelotLogger _logger; - public ResponderMiddleware(RequestDelegate next, + public ResponderMiddleware(OcelotRequestDelegate next, IHttpResponder responder, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, IErrorsToHttpStatusCodeMapper codeMapper ) - :base(requestScopedDataRepository) { _next = next; _responder = responder; @@ -33,20 +32,20 @@ namespace Ocelot.Responder.Middleware } - public async Task Invoke(HttpContext context) + public async Task Invoke(DownstreamContext context) { await _next.Invoke(context); - if (PipelineError) + if (context.IsError) { - var errors = PipelineErrors; - _logger.LogError($"{PipelineErrors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); - SetErrorResponse(context, errors); + var errors = context.Errors; + _logger.LogError($"{errors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); + SetErrorResponse(context.HttpContext, errors); } else { _logger.LogDebug("no pipeline errors, setting and returning completed response"); - await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); + await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs b/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs index c998b534..9379708f 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; namespace Ocelot.Responder.Middleware { public static class ResponderMiddlewareExtensions { - public static IApplicationBuilder UseResponderMiddleware(this IApplicationBuilder builder) + public static IOcelotPipelineBuilder UseResponderMiddleware(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index c4ea0ac6..9f0bc93a 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -4,6 +4,6 @@ namespace Ocelot.ServiceDiscovery { public interface IServiceDiscoveryProviderFactory { - IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute); + IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index e8c97bd4..0e75cb1a 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -14,7 +14,7 @@ namespace Ocelot.ServiceDiscovery _factory = factory; } - public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) + public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) { if (reRoute.UseServiceDiscovery) { diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs new file mode 100644 index 00000000..c0ed2de2 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +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 AggregateTests : IDisposable + { + private IWebHost _serviceOneBuilder; + private IWebHost _serviceTwoBuilder; + private readonly Steps _steps; + private string _downstreamPathOne; + private string _downstreamPathTwo; + + public AggregateTests() + { + _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 = 51885, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51886, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51885", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_one_service_404() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51881, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51882, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var expected = "{\"Laura\":,\"Tom\":{Hello from Tom}}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51881", "/", 404, "")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51882", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_both_service_404() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51883, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51884, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var expected = "{\"Laura\":,\"Tom\":}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51883", "/", 404, "")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51884", "/", 404, "")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + + [Fact] + public void should_be_thread_safe() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51878", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51880", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIMakeLotsOfDifferentRequestsToTheApiGateway()) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceOneBuilder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPathOne != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _serviceOneBuilder.Start(); + } + + private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceOneBuilder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPathTwo != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _serviceOneBuilder.Start(); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) + { + _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); + _downstreamPathTwo.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _serviceOneBuilder?.Dispose(); + _serviceTwoBuilder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index b30f1db0..aaa4faff 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -235,6 +235,66 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_fix_issue_240() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + }, + RouteClaimsRequirement = + { + {"Role", "User"} + } + } + } + }; + + var users = new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("Role", "AdminUser"), + new Claim("Role", "User") + }, + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt, users)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) { _servicebuilder = new WebHostBuilder() @@ -335,7 +395,75 @@ namespace Ocelot.AcceptanceTests _identityServerBuilder.Start(); _steps.VerifyIdentiryServerStarted(url); + } + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, List users) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("api.readOnly"), + new Scope("openid"), + new Scope("offline_access"), + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId", "Role" + } + }, + + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + + } + }) + .AddTestUsers(users); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); } public void Dispose() diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs new file mode 100644 index 00000000..84346985 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Butterfly.Client.AspNetCore; +using static Rafty.Infrastructure.Wait; + +namespace Ocelot.AcceptanceTests +{ + public class ButterflyTracingTests : IDisposable + { + private IWebHost _serviceOneBuilder; + private IWebHost _serviceTwoBuilder; + private IWebHost _fakeButterfly; + private readonly Steps _steps; + private string _downstreamPathOne; + private string _downstreamPathTwo; + private int _butterflyCalled; + + public ButterflyTracingTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_forward_tracing_information_from_ocelot_and_downstream_services() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/values", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51887, + } + }, + UpstreamPathTemplate = "/api001/values", + UpstreamHttpMethod = new List { "Get" }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak = 10, + TimeoutValue = 5000 + } + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51888, + } + }, + UpstreamPathTemplate = "/api002/values", + UpstreamHttpMethod = new List { "Get" }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak = 10, + TimeoutValue = 5000 + } + } + } + }; + + var butterflyUrl = "http://localhost:9618"; + + this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) + .And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl)) + .And(x => GivenFakeButterfly(butterflyUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .BDDfy(); + + + var commandOnAllStateMachines = WaitFor(5000).Until(() => _butterflyCalled == 4); + + commandOnAllStateMachines.ShouldBeTrue(); + } + + private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) + { + _serviceOneBuilder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .ConfigureServices(services => { + services.AddButterfly(option => + { + option.CollectorUrl = butterflyUrl; + option.Service = "Service One"; + option.IgnoredRoutesRegexPatterns = new string[0]; + }); + }) + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPathOne != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _serviceOneBuilder.Start(); + } + + private void GivenFakeButterfly(string baseUrl) + { + _fakeButterfly = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.Run(async context => + { + _butterflyCalled++; + await context.Response.WriteAsync("OK..."); + }); + }) + .Build(); + + _fakeButterfly.Start(); + } + + private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) + { + _serviceTwoBuilder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .ConfigureServices(services => { + services.AddButterfly(option => + { + option.CollectorUrl = butterflyUrl; + option.Service = "Service Two"; + option.IgnoredRoutesRegexPatterns = new string[0]; + }); + }) + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPathTwo != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _serviceTwoBuilder.Start(); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) + { + _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); + _downstreamPathTwo.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _serviceOneBuilder?.Dispose(); + _serviceTwoBuilder?.Dispose(); + _fakeButterfly?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 755237a5..f64e2ce2 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -37,7 +37,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51899, } }, DownstreamScheme = "http", @@ -51,13 +51,13 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) @@ -80,7 +80,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51899, } }, DownstreamScheme = "http", @@ -94,13 +94,13 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) @@ -122,7 +122,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51899, } }, DownstreamScheme = "http", @@ -136,13 +136,13 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom")) .And(x => x.GivenTheCacheExpires()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 247338eb..7176ce26 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -36,7 +36,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51877, } }, DownstreamScheme = "http", @@ -46,7 +46,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51877", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -69,7 +69,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51877, } }, DownstreamScheme = "http", @@ -80,7 +80,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51877", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -103,7 +103,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51877, } }, DownstreamScheme = "http", @@ -114,7 +114,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51877", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -137,7 +137,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51877, } }, DownstreamScheme = "http", @@ -148,7 +148,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51877", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -171,7 +171,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51877, } }, DownstreamScheme = "http", @@ -182,7 +182,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51877", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -205,7 +205,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51877, } }, DownstreamScheme = "http", @@ -216,7 +216,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51877", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 47a4c03d..11ec83b1 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -48,7 +48,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51876, } }, DownstreamScheme = "http", @@ -81,7 +81,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) @@ -109,7 +109,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51876, } }, DownstreamScheme = "http", @@ -140,7 +140,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index c95d6323..db617718 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -4,13 +4,10 @@ using System.Diagnostics; using System.IO; using System.Net; using System.Text; -using System.Threading; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using TestStack.BDDfy; using Xunit; @@ -275,7 +272,11 @@ namespace Ocelot.AcceptanceTests private void GivenIWaitForTheConfigToReplicateToOcelot() { - Thread.Sleep(10000); + var stopWatch = Stopwatch.StartNew(); + while (stopWatch.ElapsedMilliseconds < 10000) + { + //do nothing! + } } private void GivenTheConsulConfigurationIs(FileConfiguration config) diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 1e389881..0f30ba73 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using Ocelot.Middleware; using Shouldly; @@ -30,7 +32,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_pre_query_string_builder_middleware() { - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { AuthorisationMiddleware = async (ctx, next) => { @@ -61,7 +63,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, "")) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -73,7 +75,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_authorisation_middleware() { - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { AuthorisationMiddleware = async (ctx, next) => { @@ -104,7 +106,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, "")) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -116,7 +118,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_authentication_middleware() { - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { AuthenticationMiddleware = async (ctx, next) => { @@ -147,7 +149,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, "")) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -159,7 +161,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_pre_error_middleware() { - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { PreErrorResponderMiddleware = async (ctx, next) => { @@ -190,7 +192,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, "")) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -202,7 +204,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_pre_authorisation_middleware() { - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { PreAuthorisationMiddleware = async (ctx, next) => { @@ -233,7 +235,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, "")) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -245,7 +247,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_pre_http_authentication_middleware() { - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { PreAuthenticationMiddleware = async (ctx, next) => { @@ -276,7 +278,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, "")) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -285,12 +287,59 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + + [Fact(Skip = "This is just an example to show how you could hook into Ocelot pipeline with your own middleware. At the moment you must use Response.OnCompleted callback and cannot change the response :( I will see if this can be changed one day!")] + public void should_fix_issue_237() + { + Func callback = state => + { + var httpContext = (HttpContext)state; + + if (httpContext.Response.StatusCode > 400) + { + Debug.WriteLine("COUNT CALLED"); + Console.WriteLine("COUNT CALLED"); + } + + return Task.CompletedTask; + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/west", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41880, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41880", 200, "/test")) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunningWithMiddleareBeforePipeline(callback)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + private void ThenTheCounterIs(int expected) { _counter.ShouldBe(expected); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode) + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string basePath) { _builder = new WebHostBuilder() .UseUrls(url) @@ -300,9 +349,19 @@ namespace Ocelot.AcceptanceTests .UseUrls(url) .Configure(app => { + app.UsePathBase(basePath); app.Run(context => { - context.Response.StatusCode = statusCode; + + if(string.IsNullOrEmpty(basePath)) + { + context.Response.StatusCode = statusCode; + } + else if(context.Request.Path.Value != basePath) + { + context.Response.StatusCode = 404;; + } + return Task.CompletedTask; }); }) @@ -316,5 +375,24 @@ namespace Ocelot.AcceptanceTests _builder?.Dispose(); _steps.Dispose(); } + + public class FakeMiddleware + { + private readonly RequestDelegate _next; + private readonly Func _callback; + + public FakeMiddleware(RequestDelegate next, Func callback) + { + _next = next; + _callback = callback; + } + + public async Task Invoke(HttpContext context) + { + await _next(context); + + context.Response.OnCompleted(_callback, context); + } + } } } diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 46d7cd40..e0bd67c8 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51871, } }, UpstreamPathTemplate = "/", @@ -54,7 +54,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Laz")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Laz")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenIAddAHeader("Laz", "D")) @@ -80,7 +80,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51871, } }, UpstreamPathTemplate = "/", @@ -93,7 +93,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Location", "http://www.bbc.co.uk/")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Location", "http://www.bbc.co.uk/")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index ab7ae7ce..396eb403 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,53 +1,48 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - Ocelot.AcceptanceTests - Exe - Ocelot.AcceptanceTests - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + Ocelot.AcceptanceTests + Exe + Ocelot.AcceptanceTests + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index bf86455d..e4f8a026 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51872, } }, UpstreamPathTemplate = "/", @@ -57,7 +57,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura")) .Given(x => _steps.GivenThereIsAConfiguration(configuration)) .Given(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -92,7 +92,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51872, } }, UpstreamPathTemplate = "/", @@ -122,7 +122,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 444133d9..6ac271c9 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -39,7 +39,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51873, } }, DownstreamScheme = "http", @@ -50,7 +50,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51873")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -73,7 +73,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51873, } }, DownstreamScheme = "http", @@ -85,7 +85,7 @@ namespace Ocelot.AcceptanceTests var requestId = Guid.NewGuid().ToString(); - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51873")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId)) @@ -108,7 +108,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51873, } }, DownstreamScheme = "http", @@ -124,7 +124,7 @@ namespace Ocelot.AcceptanceTests var requestId = Guid.NewGuid().ToString(); - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51873")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId)) diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 5fc72b27..ea748a04 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -298,7 +298,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51874, } }, UpstreamPathTemplate = "/vacancy/", @@ -315,7 +315,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51874, } }, UpstreamPathTemplate = "/vacancy/{vacancyId}", @@ -326,7 +326,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51874", "/api/v1/vacancy/1", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1")) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index b33778e7..6a6fa3c4 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -24,6 +26,8 @@ using Ocelot.ServiceDiscovery; using Shouldly; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using Ocelot.AcceptanceTests.Caching; +using Butterfly.Client.AspNetCore; +using Butterfly.Client.Tracing; namespace Ocelot.AcceptanceTests { @@ -103,6 +107,78 @@ namespace Ocelot.AcceptanceTests _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(); + } + /// /// 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. /// @@ -152,7 +228,6 @@ namespace Ocelot.AcceptanceTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(_baseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -189,7 +264,6 @@ namespace Ocelot.AcceptanceTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(_baseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -227,7 +301,6 @@ namespace Ocelot.AcceptanceTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(_baseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -256,7 +329,6 @@ namespace Ocelot.AcceptanceTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(_baseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -311,7 +383,7 @@ namespace Ocelot.AcceptanceTests /// /// 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(OcelotMiddlewareConfiguration ocelotMiddlewareConfig) + public void GivenOcelotIsRunning(OcelotPipelineConfiguration ocelotPipelineConfig) { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -348,7 +420,7 @@ namespace Ocelot.AcceptanceTests }) .Configure(a => { - a.UseOcelot(ocelotMiddlewareConfig).Wait(); + a.UseOcelot(ocelotPipelineConfig).Wait(); })); _ocelotClient = _ocelotServer.CreateClient(); @@ -527,7 +599,6 @@ namespace Ocelot.AcceptanceTests _response.StatusCode.ShouldBe(expectedHttpStatusCode); } - public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) { var responseStatusCode = (int)_response.StatusCode; @@ -554,5 +625,51 @@ namespace Ocelot.AcceptanceTests { _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.AcceptanceTests/UpstreamHostTests.cs b/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs index 18aa6c16..16e123a7 100644 --- a/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs +++ b/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs @@ -39,7 +39,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51875, } }, UpstreamPathTemplate = "/", @@ -49,7 +49,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -75,7 +75,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51875, } }, UpstreamPathTemplate = "/", @@ -101,7 +101,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -142,7 +142,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51875, } }, UpstreamPathTemplate = "/", @@ -152,7 +152,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -192,7 +192,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51875, } }, UpstreamPathTemplate = "/", @@ -202,7 +202,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -227,7 +227,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51879, + Port = 51875, } }, UpstreamPathTemplate = "/", @@ -237,7 +237,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index ac2b0191..00e25330 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -18,7 +18,7 @@ - + diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index 853fd7e5..d7f4434b 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -17,7 +17,7 @@ namespace Ocelot.Benchmarks Add(StatisticColumn.AllStatistics); } - [Setup] + [GlobalSetup] public void SetUp() { _urlPathMatcher = new RegExUrlMatcher(); @@ -37,4 +37,4 @@ namespace Ocelot.Benchmarks _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index cddd4368..3b2f6a35 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -74,13 +74,19 @@ namespace Ocelot.IntegrationTests } [Fact] - public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_memory_with_no_webhostbuilder_registered() + public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config() { _httpClient = new HttpClient(); _ocelotBaseUrl = "http://localhost:5011"; _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - var configuration = new FileConfiguration(); + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + BaseUrl = _ocelotBaseUrl + } + }; this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl)) @@ -457,7 +463,6 @@ namespace Ocelot.IntegrationTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(baseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -610,7 +615,6 @@ namespace Ocelot.IntegrationTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(_ocelotBaseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -651,7 +655,6 @@ namespace Ocelot.IntegrationTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(baseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(x => { diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index 1517e879..e93e9ee4 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -59,7 +59,7 @@ namespace Ocelot.IntegrationTests } } - [Fact] + [Fact(Skip = "This tests is flakey at the moment so ignoring will be fixed long term see https://github.com/TomPallister/Ocelot/issues/245")] public void should_persist_command_to_five_servers() { var configuration = new FileConfiguration @@ -118,7 +118,7 @@ namespace Ocelot.IntegrationTests ThenTheCommandIsReplicatedToAllStateMachines(command); } - [Fact] + [Fact(Skip = "This tests is flakey at the moment so ignoring will be fixed long term see https://github.com/TomPallister/Ocelot/issues/245")] public void should_persist_command_to_five_servers_when_using_administration_api() { var configuration = new FileConfiguration diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 5b360358..f8533a04 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -109,7 +109,6 @@ namespace Ocelot.IntegrationTests config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); - config.AddOcelotBaseUrl(_ocelotBaseUrl); config.AddEnvironmentVariables(); }) .ConfigureServices(x => diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index a7c5bc61..4e728d3d 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -22,29 +22,27 @@ namespace Ocelot.ManualTest .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("configuration.json") - .AddEnvironmentVariables() - .AddOcelotBaseUrl("http://localhost:5000"); + .AddEnvironmentVariables(); }) .ConfigureServices(s => { - - s.AddAuthentication() + s.AddAuthentication() .AddJwtBearer("TestKey", x => { x.Authority = "test"; x.Audience = "test"; }); - s.AddOcelot() - .AddCacheManager(x => - { - x.WithDictionaryHandle(); - }) - .AddOpenTracing(option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - }) - .AddAdministration("/administration", "secret"); + s.AddOcelot() + .AddCacheManager(x => + { + x.WithDictionaryHandle(); + }) + .AddOpenTracing(option => + { + option.CollectorUrl = "http://localhost:9618"; + option.Service = "Ocelot.ManualTest"; + }) + .AddAdministration("/administration", "secret"); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 93bf6ac6..ba34d4e5 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -8,7 +8,7 @@ "DownstreamHostAndPorts": [ { "Host": "localhost", - "Port": 5001 + "Port": 5007 } ], "HttpHandlerOptions": { @@ -99,7 +99,7 @@ "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, - "UseTracing": true + "UseTracing": false }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -121,7 +121,7 @@ "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, - "UseTracing": true + "UseTracing": false }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index 20cd82ef..07368127 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,6 +1,11 @@ -namespace Ocelot.UnitTests.Authentication +using Ocelot.Configuration; +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Authentication { using System.Collections.Generic; + using System.IO; + using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -8,6 +13,7 @@ using Ocelot.Authentication.Middleware; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; using Ocelot.Responses; @@ -15,56 +21,75 @@ using TestStack.BDDfy; using Xunit; - public class AuthenticationMiddlewareTests : ServerHostedMiddlewareTest + public class AuthenticationMiddlewareTests { private OkResponse _downstreamRoute; + private AuthenticationMiddleware _middleware; + private Mock _factory; + private Mock _logger; + private OcelotRequestDelegate _next; + private DownstreamContext _downstreamContext; public AuthenticationMiddlewareTests() { - GivenTheTestServerIsConfigured(); + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); } [Fact] public void should_call_next_middleware_if_route_is_not_authenticated() { - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build()))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheUserIsAuthenticated()) + this.Given(x => GivenTheDownStreamRouteIs( + new DownstreamReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build())) + .And(x => GivenTheTestServerPipelineIsConfigured()) + .When(x => WhenICallTheMiddleware()) + .Then(x => ThenTheUserIsAuthenticated()) .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); + _next = async (context) => { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + MemoryStream stream = new MemoryStream(byteArray); + context.HttpContext.Response.Body = stream; + }; + _middleware = new AuthenticationMiddleware(_next, _factory.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + private void GivenTheTestServerPipelineIsConfigured() { - app.UseAuthenticationMiddleware(); - - app.Run(async x => - { - await x.Response.WriteAsync("The user is authenticated"); - }); + _next = async (context) => { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + MemoryStream stream = new MemoryStream(byteArray); + context.HttpContext.Response.Body = stream; + }; } private void ThenTheUserIsAuthenticated() { - var content = ResponseMessage.Content.ReadAsStringAsync().Result; + var content = _downstreamContext.HttpContext.Response.Body.AsString(); content.ShouldBe("The user is authenticated"); } - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.DownstreamReRoute = downstreamRoute; + } + } + + public static class StreamExtensions + { + public static string AsString(this Stream stream) + { + using(var reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + return text; + }; } } } diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index ea89accb..6f1d54f3 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -1,9 +1,9 @@ -namespace Ocelot.UnitTests.Authorization +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Authorization { using System.Collections.Generic; using System.Security.Claims; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Authorisation; using Ocelot.Authorisation.Middleware; @@ -14,55 +14,57 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Configuration; - public class AuthorisationMiddlewareTests : ServerHostedMiddlewareTest + public class AuthorisationMiddlewareTests { private readonly Mock _authService; private readonly Mock _authScopesService; - private OkResponse _downstreamRoute; + private Mock _loggerFactory; + private Mock _logger; + private readonly AuthorisationMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public AuthorisationMiddlewareTests() { _authService = new Mock(); _authScopesService = new Mock(); - - GivenTheTestServerIsConfigured(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _middleware = new AuthorisationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object); } [Fact] public void should_call_authorisation_service() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), - new ReRouteBuilder() + this.Given(x => x.GivenTheDownStreamRouteIs(new List(), + new DownstreamReRouteBuilder() .WithIsAuthorised(true) .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) + .Build())) .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_authService.Object); - services.AddSingleton(_authScopesService.Object); - services.AddSingleton(ScopedRepository.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + private void GivenTheDownStreamRouteIs(List templatePlaceholderNameAndValues, DownstreamReRoute downstreamReRoute) { - app.UseAuthorisationMiddleware(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamReRoute; } private void GivenTheAuthServiceReturns(Response expected) diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs index b435f7e7..0333268b 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs @@ -27,7 +27,25 @@ namespace Ocelot.UnitTests.Authorization { this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List { - new Claim("UserType", "registered") + new Claim("UserType", "registered"), + + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"UserType", "registered"} + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsAuthorised()) + .BDDfy(); + } + + [Fact] + public void should_authorise_user_multiple_claims_of_same_type() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("UserType", "guest"), + new Claim("UserType", "registered"), })))) .And(x => x.GivenARouteClaimsRequirement(new Dictionary { diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs index 70619c6b..4e9ef04d 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs @@ -1,4 +1,6 @@ -using Ocelot.Infrastructure.RequestData; +using Ocelot.Errors; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Middleware; namespace Ocelot.UnitTests.Cache { @@ -22,20 +24,37 @@ namespace Ocelot.UnitTests.Cache using Ocelot.Responses; using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; + using Microsoft.AspNetCore.Http; - public class OutputCacheMiddlewareRealCacheTests : ServerHostedMiddlewareTest + public class OutputCacheMiddlewareRealCacheTests { private IOcelotCache _cacheManager; private CachedResponse _response; - private IRequestScopedDataRepository _repo; + private OutputCacheMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + private Mock _loggerFactory; + private IRegionCreator _regionCreator; + private Mock _logger; public OutputCacheMiddlewareRealCacheTests() { - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); - - GivenTheTestServerIsConfigured(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _regionCreator = new RegionCreator(); + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => + { + x.WithDictionaryHandle(); + }); + _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.. + }; + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator); } [Fact] @@ -54,12 +73,16 @@ namespace Ocelot.UnitTests.Cache this.Given(x => x.GivenResponseIsNotCached(response)) .And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenThereAreNoErrors()) - .And(x => x.GivenThereIsADownstreamUrl()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheContentTypeHeaderIsCached()) .BDDfy(); } + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + private void ThenTheContentTypeHeaderIsCached() { var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken"); @@ -67,65 +90,25 @@ namespace Ocelot.UnitTests.Cache header.First().ShouldBe("application/json"); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => - { - x.WithDictionaryHandle(); - }); - - _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - - services.AddSingleton>(cacheManagerOutputCache); - services.AddSingleton>(_cacheManager); - - services.AddSingleton(); - - services.AddLogging(); - services.AddSingleton(_cacheManager); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseOutputCacheMiddleware(); - } - private void GivenResponseIsNotCached(HttpResponseMessage message) { - ScopedRepository - .Setup(x => x.Get("HttpResponseMessage")) - .Returns(new OkResponse(message)); + _downstreamContext.DownstreamResponse = message; } private void GivenTheDownstreamRouteIs() { - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithIsCached(true) .WithCacheOptions(new CacheOptions(100, "kanken")) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); - var downstreamRoute = new DownstreamRoute(new List(), reRoute); - - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(downstreamRoute)); + _downstreamContext.DownstreamReRoute = reRoute; } private void GivenThereAreNoErrors() { - ScopedRepository - .Setup(x => x.Get("OcelotMiddlewareError")) - .Returns(new OkResponse(false)); - } - - private void GivenThereIsADownstreamUrl() - { - ScopedRepository - .Setup(x => x.Get("DownstreamUrl")) - .Returns(new OkResponse("anything")); + _downstreamContext.Errors = new List(); } } } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 2a60e376..42c1e695 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,4 +1,6 @@ using System.Net; +using Ocelot.Errors; +using Ocelot.Middleware; namespace Ocelot.UnitTests.Cache { @@ -6,6 +8,7 @@ namespace Ocelot.UnitTests.Cache using System.Collections.Generic; using System.Net.Http; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Cache; @@ -13,26 +16,37 @@ namespace Ocelot.UnitTests.Cache 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; - public class OutputCacheMiddlewareTests : ServerHostedMiddlewareTest + public class OutputCacheMiddlewareTests { - private readonly Mock> _cacheManager; + private readonly Mock> _cacheManager; + private Mock _loggerFactory; + private Mock _logger; + private OutputCacheMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; private CachedResponse _response; + private IRegionCreator _regionCreator; public OutputCacheMiddlewareTests() { _cacheManager = new Mock>(); + _regionCreator = new RegionCreator(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); - - GivenTheTestServerIsConfigured(); + _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); } [Fact] @@ -41,7 +55,6 @@ namespace Ocelot.UnitTests.Cache var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", new Dictionary>()); this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereIsADownstreamUrl()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) .BDDfy(); @@ -53,24 +66,15 @@ namespace Ocelot.UnitTests.Cache this.Given(x => x.GivenResponseIsNotCached()) .And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenThereAreNoErrors()) - .And(x => x.GivenThereIsADownstreamUrl()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_cacheManager.Object); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseOutputCacheMiddleware(); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenThereIsACachedResponse(CachedResponse response) @@ -83,38 +87,30 @@ namespace Ocelot.UnitTests.Cache private void GivenResponseIsNotCached() { - ScopedRepository - .Setup(x => x.Get("HttpResponseMessage")) - .Returns(new OkResponse(new HttpResponseMessage())); + _downstreamContext.DownstreamResponse = new HttpResponseMessage(); } private void GivenTheDownstreamRouteIs() { var reRoute = new ReRouteBuilder() - .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken")) + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100, "kanken")) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); var downstreamRoute = new DownstreamRoute(new List(), reRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(downstreamRoute)); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } private void GivenThereAreNoErrors() { - ScopedRepository - .Setup(x => x.Get("OcelotMiddlewareError")) - .Returns(new OkResponse(false)); - } + _downstreamContext.Errors = new List(); - private void GivenThereIsADownstreamUrl() - { - ScopedRepository - .Setup(x => x.Get("DownstreamUrl")) - .Returns(new OkResponse("anything")); } private void ThenTheCacheGetIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index 44ab1dc0..be266f9e 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -1,9 +1,9 @@ -namespace Ocelot.UnitTests.Claims +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Claims { using System.Collections.Generic; - using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Claims; using Ocelot.Claims.Middleware; @@ -15,17 +15,29 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; - public class ClaimsBuilderMiddlewareTests : ServerHostedMiddlewareTest + public class ClaimsBuilderMiddlewareTests { private readonly Mock _addHeaders; private Response _downstreamRoute; + private Mock _loggerFactory; + private Mock _logger; + private readonly ClaimsBuilderMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public ClaimsBuilderMiddlewareTests() { _addHeaders = new Mock(); - - GivenTheTestServerIsConfigured(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _middleware = new ClaimsBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); } [Fact] @@ -33,11 +45,14 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToClaims(new List - { - new ClaimToThing("sub", "UserType", "|", 0) - }) + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToClaims(new List + { + new ClaimToThing("sub", "UserType", "|", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -48,25 +63,16 @@ .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_addHeaders.Object); - services.AddSingleton(ScopedRepository.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseClaimsBuilderMiddleware(); - } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } private void GivenTheAddClaimsToRequestReturns() diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs index a42dc2b6..e7e56178 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs @@ -20,7 +20,7 @@ namespace Ocelot.UnitTests.Configuration private readonly IConfigurationValidator _configurationValidator; private FileConfiguration _fileConfiguration; private Response _result; - private Mock _provider; + private readonly Mock _provider; public ConfigurationFluentValidationTests() { @@ -28,6 +28,373 @@ namespace Ocelot.UnitTests.Configuration _configurationValidator = new FileConfigurationFluentValidator(_provider.Object); } + [Fact] + public void configuration_is_valid_if_aggregates_are_valid() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_aggregates_are_duplicate_of_re_routes() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + UpstreamHost = "localhost" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /tom has duplicate aggregate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_aggregates_are_not_duplicate_of_re_routes() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Post" }, + Key = "Tom", + UpstreamHost = "localhost" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_aggregates_are_duplicate_of_aggregates() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/lol", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + }, + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "aggregate /tom has duplicate aggregate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_re_routes_dont_exist_for_aggregate() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ReRoutes for aggregateReRoute / either do not exist or do not have correct Key property")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_aggregate_has_re_routes_with_specific_request_id_keys() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + RequestIdKey = "should_fail", + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "aggregateReRoute / contains ReRoute with specific RequestIdKey, this is not possible with Aggregates")) + .BDDfy(); + } + [Fact] public void configuration_is_invalid_if_scheme_in_downstream_or_upstream_template() { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index b2ff9a00..8bb0ecd2 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Castle.Components.DictionaryAdapter; using Microsoft.Extensions.Options; using Moq; using Ocelot.Cache; @@ -81,6 +82,125 @@ namespace Ocelot.UnitTests.Configuration _downstreamAddressesCreator.Object); } + [Fact] + public void should_set_up_aggregate_re_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + UpstreamHost = "localhost" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + UpstreamHost = "localhost", + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + var expected = new List(); + + var lauraDownstreamReRoute = new DownstreamReRouteBuilder() + .WithUpstreamHost("localhost") + .WithKey("Laura") + .WithDownstreamPathTemplate("/") + .WithDownstreamScheme("http") + .WithUpstreamHttpMethod(new List() {"Get"}) + .WithDownstreamAddresses(new List() {new DownstreamHostAndPort("localhost", 51878)}) + .Build(); + + var lauraReRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List() { "Get" }) + .WithUpstreamHost("localhost") + .WithUpstreamPathTemplate("/laura") + .WithDownstreamReRoute(lauraDownstreamReRoute) + .Build(); + + expected.Add(lauraReRoute); + + var tomDownstreamReRoute = new DownstreamReRouteBuilder() + .WithUpstreamHost("localhost") + .WithKey("Tom") + .WithDownstreamPathTemplate("/") + .WithDownstreamScheme("http") + .WithUpstreamHttpMethod(new List() { "Get" }) + .WithDownstreamAddresses(new List() { new DownstreamHostAndPort("localhost", 51878) }) + .Build(); + + var tomReRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List() { "Get" }) + .WithUpstreamHost("localhost") + .WithUpstreamPathTemplate("/tom") + .WithDownstreamReRoute(tomDownstreamReRoute) + .Build(); + + expected.Add(tomReRoute); + + var aggregateReReRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate("/") + .WithUpstreamHost("localhost") + .WithDownstreamReRoute(lauraDownstreamReRoute) + .WithDownstreamReRoute(tomDownstreamReRoute) + .WithUpstreamHttpMethod(new List() { "Get" }) + .Build(); + + expected.Add(aggregateReReRoute); + + this.Given(x => x.GivenTheConfigIs(configuration)) + .And(x => x.GivenTheFollowingOptionsAreReturned(new ReRouteOptionsBuilder().Build())) + .And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig)) + .And(x => GivenTheDownstreamAddresses()) + .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) + .And(x => x.GivenTheConfigIsValid()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheServiceProviderCreatorIsCalledCorrectly()) + .Then(x => x.ThenTheReRoutesAre(expected)) + .BDDfy(); + } + [Fact] public void should_call_service_provider_config_creator() { @@ -234,6 +354,13 @@ namespace Ocelot.UnitTests.Configuration var reRouteOptions = new ReRouteOptionsBuilder() .Build(); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamAddresses(new List() {new DownstreamHostAndPort("127.0.0.1", 80)}) + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -261,8 +388,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamAddresses(new List(){new DownstreamHostAndPort("127.0.0.1", 80) }) - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) .Build() @@ -276,6 +402,13 @@ namespace Ocelot.UnitTests.Configuration var reRouteOptions = new ReRouteOptionsBuilder() .Build(); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("https") + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -297,8 +430,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamScheme("https") - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) .Build() @@ -312,6 +444,14 @@ namespace Ocelot.UnitTests.Configuration var reRouteOptions = new ReRouteOptionsBuilder() .Build(); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithUseServiceDiscovery(true) + .WithServiceName("ProductService") + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -341,11 +481,9 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUseServiceDiscovery(true) - .WithServiceName("ProductService") .Build() })) .BDDfy(); @@ -356,7 +494,15 @@ namespace Ocelot.UnitTests.Configuration { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); - + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithUseServiceDiscovery(false) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -378,10 +524,9 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUseServiceDiscovery(false) .Build() })) .BDDfy(); @@ -392,7 +537,15 @@ namespace Ocelot.UnitTests.Configuration { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); - + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -415,7 +568,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) @@ -430,6 +583,13 @@ namespace Ocelot.UnitTests.Configuration var reRouteOptions = new ReRouteOptionsBuilder() .Build(); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithRequestIdKey("blahhhh") + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -456,10 +616,9 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithRequestIdKey("blahhhh") .Build() })) .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly()) @@ -514,17 +673,23 @@ namespace Ocelot.UnitTests.Configuration .WithAllowedScopes(new List()) .Build(); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithAuthenticationOptions(authenticationOptions) + .WithClaimsToHeaders(new List + { + new ClaimToThing("CustomerId", "CustomerId", "", 0), + }) + .Build(); + var expected = new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithAuthenticationOptions(authenticationOptions) - .WithClaimsToHeaders(new List - { - new ClaimToThing("CustomerId", "CustomerId", "", 0), - }) .Build() }; @@ -554,13 +719,19 @@ namespace Ocelot.UnitTests.Configuration .WithAllowedScopes(new List()) .Build(); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithAuthenticationOptions(authenticationOptions) + .Build(); + var expected = new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithAuthenticationOptions(authenticationOptions) .Build() }; @@ -643,14 +814,16 @@ namespace Ocelot.UnitTests.Configuration var result = _config.Data.ReRoutes[i]; var expected = expectedReRoutes[i]; - result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); + result.DownstreamReRoute.Count.ShouldBe(expected.DownstreamReRoute.Count); + + result.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamReRoute[0].DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); result.UpstreamTemplatePattern?.Template.ShouldBe(expected.UpstreamTemplatePattern?.Template); - result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count); - result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); - result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); - result.RequestIdKey.ShouldBe(expected.RequestIdKey); + 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); } } @@ -659,8 +832,8 @@ namespace Ocelot.UnitTests.Configuration { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) { - var result = _config.Data.ReRoutes[i].AuthenticationOptions; - var expected = expectedReRoutes[i].AuthenticationOptions; + var result = _config.Data.ReRoutes[i].DownstreamReRoute[0].AuthenticationOptions; + var expected = expectedReRoutes[i].DownstreamReRoute[0].AuthenticationOptions; result.AllowedScopes.ShouldBe(expected.AllowedScopes); } } @@ -714,10 +887,10 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheQosOptionsAre(QoSOptions qosOptions) { - _config.Data.ReRoutes[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); + _config.Data.ReRoutes[0].DownstreamReRoute[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); - _config.Data.ReRoutes[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); - _config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); + _config.Data.ReRoutes[0].DownstreamReRoute[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); + _config.Data.ReRoutes[0].DownstreamReRoute[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); } private void ThenTheServiceProviderCreatorIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index ab5a9eea..cdc3f94d 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheConfigurationIsReturned() { - _getResult.Data.ReRoutes[0].DownstreamPathTemplate.Value.ShouldBe("initial"); + _getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("initial"); } private void WhenIGetTheConfiguration() @@ -83,13 +83,24 @@ namespace Ocelot.UnitTests.Configuration AdministrationPath = administrationPath; } - public List ReRoutes => new List + public List ReRoutes { - new ReRouteBuilder() - .WithDownstreamPathTemplate(_downstreamTemplatePath) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - }; + get + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate(_downstreamTemplatePath) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + return new List + { + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List {"Get"}) + .Build() + }; + } + } public string AdministrationPath {get;} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 54621aee..a5c18c22 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -1,7 +1,11 @@ -namespace Ocelot.UnitTests.DownstreamRouteFinder +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; + +namespace Ocelot.UnitTests.DownstreamRouteFinder { using System.Collections.Generic; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; @@ -13,22 +17,36 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; using Ocelot.Responses; + using Shouldly; using TestStack.BDDfy; using Xunit; - public class DownstreamRouteFinderMiddlewareTests : ServerHostedMiddlewareTest + public class DownstreamRouteFinderMiddlewareTests { - private readonly Mock _downstreamRouteFinder; + private readonly Mock _finder; private readonly Mock _provider; private Response _downstreamRoute; private IOcelotConfiguration _config; + private Mock _loggerFactory; + private Mock _logger; + private DownstreamRouteFinderMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + private readonly Mock _multiplexer; public DownstreamRouteFinderMiddlewareTests() { _provider = new Mock(); - _downstreamRouteFinder = new Mock(); - - GivenTheTestServerIsConfigured(); + _finder = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _multiplexer = new Mock(); + _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _provider.Object, _multiplexer.Object); } [Fact] @@ -36,11 +54,16 @@ { var config = new OcelotConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List {"Get"}) + .Build(); + this.Given(x => x.GivenTheDownStreamRouteFinderReturns( new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .And(x => GivenTheFollowingConfig(config)) @@ -49,6 +72,11 @@ .BDDfy(); } + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetType(); + } + private void GivenTheFollowingConfig(IOcelotConfiguration config) { _config = config; @@ -57,35 +85,18 @@ .ReturnsAsync(new OkResponse(_config)); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_downstreamRouteFinder.Object); - services.AddSingleton(_provider.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseDownstreamRouteFinderMiddleware(); - } - private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _downstreamRouteFinder + _finder .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_downstreamRoute); } private void ThenTheScopedDataRepositoryIsCalledCorrectly() { - ScopedRepository - .Verify(x => x.Add("DownstreamRoute", _downstreamRoute.Data), Times.Once()); - - ScopedRepository - .Verify(x => x.Add("ServiceProviderConfiguration", _config.ServiceProviderConfiguration), Times.Once()); + _downstreamContext.TemplatePlaceholderNameAndValues.ShouldBe(_downstreamRoute.Data.TemplatePlaceholderNameAndValues); + _downstreamContext.ServiceProviderConfiguration.ShouldBe(_config.ServiceProviderConfiguration); } } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 7b1a4b04..b7761c60 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -47,13 +47,23 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) .Build(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) @@ -64,7 +74,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build()) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) @@ -84,13 +99,23 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) .Build(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) @@ -101,7 +126,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build()) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) .WithUpstreamHttpMethod(new List { "Post" }) .Build() @@ -121,7 +150,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) @@ -135,7 +169,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() @@ -157,7 +195,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) @@ -171,7 +214,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() @@ -193,7 +240,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) @@ -206,7 +258,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() @@ -227,13 +283,23 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) @@ -246,7 +312,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() @@ -263,7 +333,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("somPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("somPath") + .WithUpstreamPathTemplate("somePath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) + .Build()) .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) @@ -292,7 +367,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get", "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) @@ -305,7 +385,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() @@ -326,7 +410,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List()) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) @@ -339,7 +428,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Post" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() @@ -360,7 +453,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) @@ -388,7 +486,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamHost("MATCH") + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) @@ -403,7 +507,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() @@ -425,7 +533,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) @@ -439,7 +552,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() @@ -459,12 +576,17 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamHost("MATCH") + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .WithUpstreamHost("MATCH") - .Build() }, string.Empty, serviceProviderConfig )) @@ -490,13 +612,24 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamPathTemplate("THENULLPATH") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("THENULLPATH") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamHost("MATCH") + .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) @@ -511,7 +644,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() @@ -592,7 +729,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheFollowingIsReturned(DownstreamRoute expected) { - _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value); _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index b7e80e87..ec4829a7 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,4 +1,6 @@ -namespace Ocelot.UnitTests.DownstreamUrlCreator +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.DownstreamUrlCreator { using System; using System.Collections.Generic; @@ -19,39 +21,49 @@ using TestStack.BDDfy; using Xunit; using Shouldly; + using Ocelot.DownstreamRouteFinder.Middleware; + using Microsoft.AspNetCore.Http; - public class DownstreamUrlCreatorMiddlewareTests : ServerHostedMiddlewareTest + public class DownstreamUrlCreatorMiddlewareTests { private readonly Mock _downstreamUrlTemplateVariableReplacer; private readonly Mock _urlBuilder; - private Response _downstreamRoute; private OkResponse _downstreamPath; - private HttpRequestMessage _downstreamRequest; + private Mock _loggerFactory; + private Mock _logger; + private DownstreamUrlCreatorMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public DownstreamUrlCreatorMiddlewareTests() { + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _downstreamUrlTemplateVariableReplacer = new Mock(); _urlBuilder = new Mock(); - - _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); + _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); + _next = async context => { + //do nothing + }; } [Fact] public void should_replace_scheme_and_path() { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithDownstreamScheme("https") + .Build(); + this.Given(x => x.GivenTheDownStreamRouteIs( new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") + .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") .Build()))) .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) @@ -60,31 +72,21 @@ .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(_urlBuilder.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseDownstreamUrlCreatorMiddleware(); + _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object, _urlBuilder.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } private void GivenTheDownstreamRequestUriIs(string uri) { - _downstreamRequest.RequestUri = new Uri(uri); + _downstreamContext.DownstreamRequest.RequestUri = new Uri(uri); } private void GivenTheUrlReplacerWillReturn(string path) @@ -97,7 +99,7 @@ private void ThenTheDownstreamRequestUriIs(string expectedUri) { - _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); + _downstreamContext.DownstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); } } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index de10cbb5..cd8cf3c8 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -29,6 +29,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new DownstreamRoute( new List(), new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -43,7 +46,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new DownstreamRoute( new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("/") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -56,7 +62,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer { this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("api") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("api") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -69,7 +78,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer { this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("api/") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("api/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -82,7 +94,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer { this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("api/product/products/") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("api/product/products/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -100,7 +115,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/products/{productId}/") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -118,7 +136,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/products/{productId}/variants") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -137,7 +158,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) @@ -157,8 +181,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}") - .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) @@ -172,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer private void WhenIReplaceTheTemplateVariables() { - _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); } private void ThenTheDownstreamUrlPathIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index 1ea5fcf8..c5180f74 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -1,10 +1,10 @@ +using Ocelot.Middleware; + namespace Ocelot.UnitTests.Errors { using System; using System.Net; using System.Threading.Tasks; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; using Ocelot.Errors.Middleware; using Ocelot.Logging; using Shouldly; @@ -14,18 +14,41 @@ namespace Ocelot.UnitTests.Errors using Ocelot.Configuration.Provider; using Moq; using Ocelot.Configuration; - using Rafty.Concensus; using Ocelot.Errors; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Infrastructure.RequestData; - public class ExceptionHandlerMiddlewareTests : ServerHostedMiddlewareTest + public class ExceptionHandlerMiddlewareTests { bool _shouldThrowAnException = false; private Mock _provider; + private Mock _repo; + private Mock _loggerFactory; + private Mock _logger; + private ExceptionHandlerMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + public ExceptionHandlerMiddlewareTests() { _provider = new Mock(); - GivenTheTestServerIsConfigured(); + _repo = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + await Task.CompletedTask; + + if (_shouldThrowAnException) + { + throw new Exception("BOOM"); + } + + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; + }; + _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _provider.Object, _repo.Object); } [Fact] @@ -99,6 +122,17 @@ namespace Ocelot.UnitTests.Errors .BDDfy(); } + private void WhenICallTheMiddlewareWithTheRequestIdKey(string key, string value) + { + _downstreamContext.HttpContext.Request.Headers.Add(key, value); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + private void GivenTheConfigThrows() { var ex = new Exception("outer", new Exception("inner")); @@ -108,7 +142,7 @@ namespace Ocelot.UnitTests.Errors private void ThenAnExceptionIsThrown() { - ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); } private void GivenTheConfigReturnsError() @@ -130,7 +164,7 @@ namespace Ocelot.UnitTests.Errors private void TheRequestIdIsSet(string key, string value) { - ScopedRepository.Verify(x => x.Add(key, value), Times.Once); + _repo.Verify(x => x.Add(key, value), Times.Once); } private void GivenTheConfigurationIs(IOcelotConfiguration config) @@ -140,31 +174,6 @@ namespace Ocelot.UnitTests.Errors .Setup(x => x.Get()).ReturnsAsync(response); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(_provider.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseExceptionHandlerMiddleware(); - app.Run(DownstreamExceptionSimulator); - } - - private async Task DownstreamExceptionSimulator(HttpContext context) - { - await Task.CompletedTask; - - if (_shouldThrowAnException) - { - throw new Exception("BOOM"); - } - - context.Response.StatusCode = (int)HttpStatusCode.OK; - } private void GivenAnExceptionWillNotBeThrownDownstream() { @@ -178,17 +187,18 @@ namespace Ocelot.UnitTests.Errors private void ThenTheResponseIsOk() { - ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.OK); + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(200); } private void ThenTheResponseIsError() { - ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); + + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); } private void TheRequestIdIsNotSet() { - ScopedRepository.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Never); + _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Never); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 0cfd4697..90a24eaa 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -1,36 +1,43 @@ using Xunit; -using Shouldly; using Ocelot.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Builder; using Ocelot.Headers.Middleware; using TestStack.BDDfy; -using System.Linq; -using System.Threading.Tasks; -using System; using Microsoft.AspNetCore.Http; using System.Collections.Generic; using Moq; using Ocelot.Configuration; using Ocelot.DownstreamRouteFinder; -using Ocelot.Responses; 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 { - public class HttpHeadersTransformationMiddlewareTests : ServerHostedMiddlewareTest + public class HttpHeadersTransformationMiddlewareTests { private Mock _preReplacer; private Mock _postReplacer; - + private Mock _loggerFactory; + private Mock _logger; + private HttpHeadersTransformationMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + public HttpHeadersTransformationMiddlewareTests() { _preReplacer = new Mock(); _postReplacer = new Mock(); - - GivenTheTestServerIsConfigured(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object); } [Fact] @@ -46,27 +53,34 @@ namespace Ocelot.UnitTests.Headers .BDDfy(); } + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + private void GivenTheDownstreamRequestIs() { - var request = new HttpRequestMessage(); - var response = new OkResponse(request); - ScopedRepository.Setup(x => x.Get("DownstreamRequest")).Returns(response); + _downstreamContext.DownstreamRequest = new HttpRequestMessage(); } private void GivenTheHttpResponseMessageIs() { - var httpResponseMessage = new HttpResponseMessage(); - var response = new OkResponse(httpResponseMessage); - ScopedRepository.Setup(x => x.Get("HttpResponseMessage")).Returns(response); + _downstreamContext.DownstreamResponse = new HttpResponseMessage(); } private void GivenTheReRouteHasPreFindAndReplaceSetUp() { var fAndRs = new List(); - var reRoute = new ReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs).WithDownstreamHeaderFindAndReplace(fAndRs).Build(); + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs) + .WithDownstreamHeaderFindAndReplace(fAndRs).Build()) + .Build(); + var dR = new DownstreamRoute(null, reRoute); - var response = new OkResponse(dR); - ScopedRepository.Setup(x => x.Get("DownstreamRoute")).Returns(response); + + _downstreamContext.TemplatePlaceholderNameAndValues = dR.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = dR.ReRoute.DownstreamReRoute[0]; + } private void ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly() @@ -81,21 +95,7 @@ namespace Ocelot.UnitTests.Headers private void GivenTheFollowingRequest() { - Client.DefaultRequestHeaders.Add("test", "test"); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(_preReplacer.Object); - services.AddSingleton(_postReplacer.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpHeadersTransformationMiddleware(); + _downstreamContext.HttpContext.Request.Headers.Add("test", "test"); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index f46c9482..2f3a54d3 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -1,13 +1,17 @@ -namespace Ocelot.UnitTests.Headers +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Headers { using System.Collections.Generic; using System.Net.Http; using Microsoft.AspNetCore.Builder; + 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; @@ -16,22 +20,28 @@ using TestStack.BDDfy; using Xunit; - public class HttpRequestHeadersBuilderMiddlewareTests : ServerHostedMiddlewareTest + public class HttpRequestHeadersBuilderMiddlewareTests { private readonly Mock _addHeaders; - private readonly HttpRequestMessage _downstreamRequest; private Response _downstreamRoute; + private Mock _loggerFactory; + private Mock _logger; + private HttpRequestHeadersBuilderMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public HttpRequestHeadersBuilderMiddlewareTests() { _addHeaders = new Mock(); - - _downstreamRequest = new HttpRequestMessage(); - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); + _downstreamContext.DownstreamRequest = new HttpRequestMessage(); } [Fact] @@ -39,11 +49,14 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToHeaders(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToHeaders(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -54,25 +67,16 @@ .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_addHeaders.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpRequestHeadersBuilderMiddleware(); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } private void GivenTheAddHeadersToDownstreamRequestReturnsOk() @@ -91,7 +95,7 @@ .Verify(x => x.SetHeadersOnDownstreamRequest( It.IsAny>(), It.IsAny>(), - _downstreamRequest), Times.Once); + _downstreamContext.DownstreamRequest), Times.Once); } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index e89d62b1..5d7fa8b6 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -12,7 +12,7 @@ namespace Ocelot.UnitTests.LoadBalancer { public class LoadBalancerFactoryTests { - private ReRoute _reRoute; + private DownstreamReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; private Mock _serviceProviderFactory; @@ -29,7 +29,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_return_no_load_balancer() { - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) .Build(); @@ -44,9 +44,9 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_return_round_robin_load_balancer() { - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithLoadBalancer("RoundRobin") - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(new List {"Get"}) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -60,9 +60,9 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_return_round_least_connection_balancer() { - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithLoadBalancer("LeastConnection") - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(new List {"Get"}) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -76,9 +76,9 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_call_service_provider() { - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithLoadBalancer("RoundRobin") - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(new List {"Get"}) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -97,17 +97,17 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheServiceProviderFactoryReturns() { _serviceProviderFactory - .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny())) .Returns(_serviceProvider.Object); } private void ThenTheServiceProviderIsCalledCorrectly() { _serviceProviderFactory - .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); } - private void GivenAReRoute(ReRoute reRoute) + private void GivenAReRoute(DownstreamReRoute reRoute) { _reRoute = reRoute; } @@ -122,4 +122,4 @@ namespace Ocelot.UnitTests.LoadBalancer _result.ShouldBeOfType(); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 06c80eae..f5604ab1 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -14,13 +14,13 @@ namespace Ocelot.UnitTests.LoadBalancer { public class LoadBalancerHouseTests { - private ReRoute _reRoute; + private DownstreamReRoute _reRoute; private ILoadBalancer _loadBalancer; private readonly LoadBalancerHouse _loadBalancerHouse; private Response _addResult; private Response _getResult; private string _key; - private Mock _factory; + private readonly Mock _factory; private ServiceProviderConfiguration _serviceProviderConfig; public LoadBalancerHouseTests() @@ -32,7 +32,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_store_load_balancer_on_first_request() { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build(); this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) .Then(x => x.ThenItIsAdded()) @@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_not_store_load_balancer_on_second_request() { - var reRoute = new ReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); + var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) @@ -53,8 +53,8 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_store_load_balancers_by_key() { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - var reRouteTwo = new ReRouteBuilder().WithReRouteKey("testtwo").Build(); + var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); + var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("FakeRoundRobinLoadBalancer").WithReRouteKey("testtwo").Build(); this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) .And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer())) @@ -68,7 +68,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_return_error_if_exception() { - var reRoute = new ReRouteBuilder().Build(); + var reRoute = new DownstreamReRouteBuilder().Build(); this.When(x => x.WhenWeGetTheLoadBalancer(reRoute)) .Then(x => x.ThenAnErrorIsReturned()) @@ -78,9 +78,9 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed() { - var reRoute = new ReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); + var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); - var reRouteTwo = new ReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build(); + var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build(); this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) @@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } - private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(ReRoute reRoute) + private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute) { _reRoute = reRoute; _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null)); @@ -117,7 +117,7 @@ namespace Ocelot.UnitTests.LoadBalancer } - private void GivenThereIsALoadBalancer(ReRoute reRoute, ILoadBalancer loadBalancer) + private void GivenThereIsALoadBalancer(DownstreamReRoute reRoute, ILoadBalancer loadBalancer) { _reRoute = reRoute; _loadBalancer = loadBalancer; @@ -125,7 +125,7 @@ namespace Ocelot.UnitTests.LoadBalancer _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; } - private void WhenWeGetTheLoadBalancer(ReRoute reRoute) + private void WhenWeGetTheLoadBalancer(DownstreamReRoute reRoute) { _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 2442e362..310c1818 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -1,14 +1,18 @@ +using Ocelot.Middleware; + namespace Ocelot.UnitTests.LoadBalancer { using System.Collections.Generic; using System.Net.Http; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Errors; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.Middleware; @@ -19,7 +23,7 @@ namespace Ocelot.UnitTests.LoadBalancer using TestStack.BDDfy; using Xunit; - public class LoadBalancerMiddlewareTests : ServerHostedMiddlewareTest + public class LoadBalancerMiddlewareTests { private readonly Mock _loadBalancerHouse; private readonly Mock _loadBalancer; @@ -29,6 +33,11 @@ namespace Ocelot.UnitTests.LoadBalancer private ErrorResponse _getHostAndPortError; private HttpRequestMessage _downstreamRequest; private ServiceProviderConfiguration _config; + private Mock _loggerFactory; + private Mock _logger; + private LoadBalancingMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public LoadBalancerMiddlewareTests() { @@ -36,28 +45,29 @@ namespace Ocelot.UnitTests.LoadBalancer _loadBalancer = new Mock(); _loadBalancerHouse = new Mock(); _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, ""); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _downstreamContext.DownstreamRequest = _downstreamRequest; } [Fact] public void should_call_scoped_data_repository_correctly() { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() + var downstreamRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + .Build(); var serviceProviderConfig = new ServiceProviderConfigurationBuilder() .Build(); this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) .And(x => GivenTheConfigurationIs(serviceProviderConfig)) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List())) .And(x => x.GivenTheLoadBalancerHouseReturns()) .And(x => x.GivenTheLoadBalancerReturns()) .When(x => x.WhenICallTheMiddleware()) @@ -68,17 +78,16 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_set_pipeline_error_if_cannot_get_load_balancer() { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() + var downstreamRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + .Build(); var serviceProviderConfig = new ServiceProviderConfigurationBuilder() .Build(); this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) .And(x => GivenTheConfigurationIs(serviceProviderConfig)) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List())) .And(x => x.GivenTheLoadBalancerHouseReturnsAnError()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()) @@ -88,17 +97,16 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_set_pipeline_error_if_cannot_get_least() { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() + var downstreamRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + .Build(); var serviceProviderConfig = new ServiceProviderConfigurationBuilder() .Build(); this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) .And(x => GivenTheConfigurationIs(serviceProviderConfig)) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List())) .And(x => x.GivenTheLoadBalancerHouseReturns()) .And(x => x.GivenTheLoadBalancerReturnsAnError()) .When(x => x.WhenICallTheMiddleware()) @@ -106,24 +114,16 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + private void WhenICallTheMiddleware() + { + _middleware = new LoadBalancingMiddleware(_next, _loggerFactory.Object, _loadBalancerHouse.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + private void GivenTheConfigurationIs(ServiceProviderConfiguration config) { _config = config; - ScopedRepository - .Setup(x => x.Get("ServiceProviderConfiguration")).Returns(new OkResponse(config)); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_loadBalancerHouse.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseLoadBalancingMiddleware(); + _downstreamContext.ServiceProviderConfiguration = config; } private void GivenTheDownStreamUrlIs(string downstreamUrl) @@ -147,18 +147,16 @@ namespace Ocelot.UnitTests.LoadBalancer .ReturnsAsync(new OkResponse(_hostAndPort)); } - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute, List placeholder) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = placeholder; + _downstreamContext.DownstreamReRoute = downstreamRoute; } private void GivenTheLoadBalancerHouseReturns() { _loadBalancerHouse - .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny())) .ReturnsAsync(new OkResponse(_loadBalancer.Object)); } @@ -170,40 +168,32 @@ namespace Ocelot.UnitTests.LoadBalancer }); _loadBalancerHouse - .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny())) .ReturnsAsync(_getLoadBalancerHouseError); } private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() { - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once); + _downstreamContext.IsError.ShouldBeTrue(); + _downstreamContext.Errors.ShouldBe(_getLoadBalancerHouseError.Errors); } private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() { - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny>()), Times.Once); + _downstreamContext.IsError.ShouldBeTrue(); + _downstreamContext.Errors.ShouldBe(It.IsAny>()); } private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() { - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + _downstreamContext.IsError.ShouldBeTrue(); + _downstreamContext.Errors.ShouldBe(_getHostAndPortError.Errors); - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once); } private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) { - _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); + _downstreamContext.DownstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); } } } diff --git a/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs index f22107a5..47e00ab9 100644 --- a/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs @@ -15,46 +15,62 @@ namespace Ocelot.UnitTests.Middleware { public class BaseUrlFinderTests { - private readonly BaseUrlFinder _baseUrlFinder; - private readonly Mock _config; - + private BaseUrlFinder _baseUrlFinder; + private IConfiguration _config; + private List> _data; private string _result; public BaseUrlFinderTests() { - _config = new Mock(); - _baseUrlFinder = new BaseUrlFinder(_config.Object); + _data = new List>(); } [Fact] public void should_use_default_base_url() { - this.Given(x => GivenTheConfigBaseUrlIs("")) - .And(x => GivenTheConfigBaseUrlIs("")) - .When(x => WhenIFindTheUrl()) - .Then(x => ThenTheUrlIs("http://localhost:5000")) - .BDDfy(); + this.When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:5000")) + .BDDfy(); } [Fact] - public void should_use_file_config_base_url() + public void should_use_memory_config_base_url() { - this.Given(x => GivenTheConfigBaseUrlIs("http://localhost:7000")) - .And(x => GivenTheConfigBaseUrlIs("http://baseurlfromconfig.com:5181")) + this.Given(x => GivenTheMemoryBaseUrlIs("http://baseurlfromconfig.com:5181")) .When(x => WhenIFindTheUrl()) .Then(x => ThenTheUrlIs("http://baseurlfromconfig.com:5181")) .BDDfy(); } - private void GivenTheConfigBaseUrlIs(string configValue) + [Fact] + public void should_use_file_config_base_url() { - var configSection = new ConfigurationSection(new ConfigurationRoot(new List{new MemoryConfigurationProvider(new MemoryConfigurationSource())}), ""); - configSection.Value = configValue; - _config.Setup(x => x.GetSection(It.IsAny())).Returns(configSection); + this.Given(x => GivenTheMemoryBaseUrlIs("http://localhost:7000")) + .And(x => GivenTheFileBaseUrlIs("http://baseurlfromconfig.com:5181")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://baseurlfromconfig.com:5181")) + .BDDfy(); + } + + private void GivenTheMemoryBaseUrlIs(string configValue) + { + _data.Add(new KeyValuePair("BaseUrl", configValue)); + } + + private void GivenTheFileBaseUrlIs(string configValue) + { + _data.Add(new KeyValuePair("GlobalConfiguration:BaseUrl", configValue)); } private void WhenIFindTheUrl() { + var source = new MemoryConfigurationSource(); + source.InitialData = _data; + var provider = new MemoryConfigurationProvider(source); + _config = new ConfigurationRoot(new List() { + provider + }); + _baseUrlFinder = new BaseUrlFinder(_config); _result = _baseUrlFinder.Find(); } diff --git a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs new file mode 100644 index 00000000..8a0e5670 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs @@ -0,0 +1,69 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class MultiplexerTests + { + private readonly Multiplexer _multiplexer; + private readonly DownstreamContext _context; + private ReRoute _reRoute; + private readonly OcelotRequestDelegate _pipeline; + private int _count; + private Mock _aggregator; + + public MultiplexerTests() + { + _aggregator = new Mock(); + _context = new DownstreamContext(new DefaultHttpContext()); + _pipeline = async context => { _count++; }; + _multiplexer = new Multiplexer(_aggregator.Object); + } + + [Fact] + public void should_multiplex() + { + var reRoute = new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).Build(); + + this.Given(x => GivenTheFollowing(reRoute)) + .When(x => WhenIMultiplex()) + .Then(x => ThePipelineIsCalled(2)) + .BDDfy(); + } + + [Fact] + public void should_not_multiplex() + { + var reRoute = new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).Build(); + + this.Given(x => GivenTheFollowing(reRoute)) + .When(x => WhenIMultiplex()) + .Then(x => ThePipelineIsCalled(1)) + .BDDfy(); + } + + private void GivenTheFollowing(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIMultiplex() + { + _multiplexer.Multiplex(_context, _reRoute, _pipeline).GetAwaiter().GetResult(); + + } + + private void ThePipelineIsCalled(int expected) + { + _count.ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs new file mode 100644 index 00000000..dbb0ae63 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -0,0 +1,87 @@ +namespace Ocelot.UnitTests.Middleware +{ + using System; + using System.Collections.Generic; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Hosting.Internal; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using Ocelot.Middleware.Pipeline; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class OcelotPiplineBuilderTests + { + private readonly IServiceCollection _services; + private IServiceProvider _serviceProvider; + private readonly IConfiguration _configRoot; + private IOcelotBuilder _ocelotBuilder; + private DownstreamContext _downstreamContext; + private int _counter; + + public OcelotPiplineBuilderTests() + { + _configRoot = new ConfigurationRoot(new List()); + _services = new ServiceCollection(); + _services.AddSingleton(); + _services.AddSingleton(_configRoot); + _services.AddOcelot(); + } + + [Fact] + public void should_build_generic() + { + this.When(x => WhenIUseAGeneric()) + .Then(x => ThenTheGenericIsInThePipeline()) + .BDDfy(); + } + + [Fact] + public void should_build_func() + { + this.When(x => WhenIUseAFunc()) + .Then(x => ThenTheFuncIsInThePipeline()) + .BDDfy(); + } + + private void WhenIUseAGeneric() + { + var provider = _services.BuildServiceProvider(); + IOcelotPipelineBuilder builder = new OcelotPipelineBuilder(provider); + builder = builder.UseMiddleware(); + var del = builder.Build(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + del.Invoke(_downstreamContext); + } + + private void ThenTheGenericIsInThePipeline() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + } + + private void WhenIUseAFunc() + { + _counter = 0; + var provider = _services.BuildServiceProvider(); + IOcelotPipelineBuilder builder = new OcelotPipelineBuilder(provider); + builder = builder.Use(async (ctx, next) => + { + _counter++; + await next.Invoke(); + }); + var del = builder.Build(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + del.Invoke(_downstreamContext); + } + + private void ThenTheFuncIsInThePipeline() + { + _counter.ShouldBe(1); + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(404); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs new file mode 100644 index 00000000..b5fabaee --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Errors; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Ocelot.UnitTests.Responder; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class SimpleJsonResponseAggregatorTests + { + private readonly SimpleJsonResponseAggregator _aggregator; + private List _downstreamContexts; + private DownstreamContext _upstreamContext; + private ReRoute _reRoute; + + public SimpleJsonResponseAggregatorTests() + { + _aggregator = new SimpleJsonResponseAggregator(); + } + + [Fact] + public void should_map_all_downstream_to_upstream_when_not_aggregate() + { + var billDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Bill").Build(); + + var downstreamReRoutes = new List + { + billDownstreamReRoute, + }; + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoutes(downstreamReRoutes) + .Build(); + + + var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, + DownstreamReRoute = billDownstreamReRoute, + Errors = new List { new AnyError() }, + DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk")), + }; + + var downstreamContexts = new List { billDownstreamContext }; + + this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + .And(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheDownstreamContext(downstreamContexts)) + .When(x => WhenIAggregate()) + .Then(x => ThenTheContentIs("Bill says hi")) + .And(x => ThenTheUpstreamContextIsMappedForNonAggregate()) + .BDDfy(); + } + + [Fact] + public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() + { + var billDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Bill").Build(); + + var georgeDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("George").Build(); + + var downstreamReRoutes = new List + { + billDownstreamReRoute, + georgeDownstreamReRoute + }; + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoutes(downstreamReRoutes) + .Build(); + + var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = + new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("Bill says hi")}, + DownstreamReRoute = billDownstreamReRoute + }; + + var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = + new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("George says hi")}, + DownstreamReRoute = georgeDownstreamReRoute + }; + + var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; + + var expected = "{\"Bill\":Bill says hi,\"George\":George says hi}"; + + this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + .And(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheDownstreamContext(downstreamContexts)) + .When(x => WhenIAggregate()) + .Then(x => ThenTheContentIs(expected)) + .And(x => ThenTheContentTypeIs("application/json")) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_any_downstreams_have_errored() + { + var billDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Bill").Build(); + + var georgeDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("George").Build(); + + var downstreamReRoutes = new List + { + billDownstreamReRoute, + georgeDownstreamReRoute + }; + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoutes(downstreamReRoutes) + .Build(); + + var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, + DownstreamReRoute = billDownstreamReRoute + }; + + var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Error") }, + DownstreamReRoute = georgeDownstreamReRoute, + Errors = new List() { new AnyError() } + }; + + var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; + + var expected = "Error"; + + this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + .And(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheDownstreamContext(downstreamContexts)) + .When(x => WhenIAggregate()) + .Then(x => ThenTheContentIs(expected)) + .And(x => ThenTheErrorIsMapped()) + .BDDfy(); + } + + private void ThenTheErrorIsMapped() + { + _upstreamContext.Errors.ShouldBe(_downstreamContexts[1].Errors); + _upstreamContext.DownstreamResponse.ShouldBe(_downstreamContexts[1].DownstreamResponse); + } + + private void GivenTheReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void GivenTheUpstreamContext(DownstreamContext upstreamContext) + { + _upstreamContext = upstreamContext; + } + + private void GivenTheDownstreamContext(List downstreamContexts) + { + _downstreamContexts = downstreamContexts; + } + + private void WhenIAggregate() + { + _aggregator.Aggregate(_reRoute, _upstreamContext, _downstreamContexts).GetAwaiter().GetResult(); + } + + private void ThenTheContentIs(string expected) + { + var content = _upstreamContext.DownstreamResponse.Content.ReadAsStringAsync() + .GetAwaiter() + .GetResult(); + + content.ShouldBe(expected); + } + + private void ThenTheContentTypeIs(string expected) + { + _upstreamContext.DownstreamResponse.Content.Headers.ContentType.MediaType.ShouldBe(expected); + } + + private void ThenTheUpstreamContextIsMappedForNonAggregate() + { + _upstreamContext.DownstreamRequest.ShouldBe(_downstreamContexts[0].DownstreamRequest); + _upstreamContext.DownstreamResponse.ShouldBe(_downstreamContexts[0].DownstreamResponse); + _upstreamContext.Errors.ShouldBe(_downstreamContexts[0].Errors); + } + } +} diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 7993644f..fcc2c7e2 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -50,6 +50,7 @@ + diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index b4c851d2..cfc0395d 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -1,4 +1,6 @@ -namespace Ocelot.UnitTests.QueryStrings +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.QueryStrings { using System.Collections.Generic; using System.Net.Http; @@ -16,22 +18,30 @@ using Xunit; using System.Security.Claims; using Microsoft.AspNetCore.Builder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Microsoft.AspNetCore.Http; - public class QueryStringBuilderMiddlewareTests : ServerHostedMiddlewareTest + public class QueryStringBuilderMiddlewareTests { private readonly Mock _addQueries; - private readonly HttpRequestMessage _downstreamRequest; - private Response _downstreamRoute; + private Mock _loggerFactory; + private Mock _logger; + private QueryStringBuilderMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public QueryStringBuilderMiddlewareTests() { + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; _addQueries = new Mock(); - - _downstreamRequest = new HttpRequestMessage(); - ScopedRepository.Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); + _downstreamContext.DownstreamRequest = new HttpRequestMessage(); + _middleware = new QueryStringBuilderMiddleware(_next, _loggerFactory.Object, _addQueries.Object); } [Fact] @@ -39,11 +49,14 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToQueries(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToQueries(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -54,17 +67,9 @@ .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_addQueries.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseQueryStringBuilderMiddleware(); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenTheAddHeadersToRequestReturnsOk() @@ -83,15 +88,13 @@ .Verify(x => x.SetQueriesOnDownstreamRequest( It.IsAny>(), It.IsAny>(), - _downstreamRequest), Times.Once); + _downstreamContext.DownstreamRequest), Times.Once); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } } } diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index a01a66cd..168781bc 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -1,11 +1,10 @@ -namespace Ocelot.UnitTests.RateLimit +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.RateLimit { using System.Collections.Generic; using System.Net.Http; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -13,27 +12,51 @@ using Ocelot.Logging; using Ocelot.RateLimit; using Ocelot.RateLimit.Middleware; - using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; + using Microsoft.Extensions.Caching.Memory; + using System.IO; - public class ClientRateLimitMiddlewareTests : ServerHostedMiddlewareTest + + public class ClientRateLimitMiddlewareTests { - private OkResponse _downstreamRoute; - private int responseStatusCode; + private int _responseStatusCode; + private IRateLimitCounterHandler _rateLimitCounterHandler; + private Mock _loggerFactory; + private Mock _logger; + private readonly ClientRateLimitMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + private readonly string _url; public ClientRateLimitMiddlewareTests() { - GivenTheTestServerIsConfigured(); + _url = "http://localhost:51879"; + var cacheEntryOptions = new MemoryCacheOptions(); + _rateLimitCounterHandler = new MemoryCacheRateLimitCounterHandler(new MemoryCache(cacheEntryOptions)); + var httpContext = new DefaultHttpContext(); + _downstreamContext = new DownstreamContext(httpContext); + _downstreamContext.HttpContext.Response.Body = new FakeStream(); + + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async (context) => { + }; + _middleware = new ClientRateLimitMiddleware(_next, _loggerFactory.Object, _rateLimitCounterHandler); } [Fact] public void should_call_middleware_and_ratelimiting() { var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429)) + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new RateLimitOptions(true, "ClientId", new List(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -49,8 +72,13 @@ public void should_call_middleware_withWhitelistClient() { var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429)) + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule("1s", 100, 3), 429)) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -60,31 +88,10 @@ .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddMemoryCache(); - services.AddSingleton(); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseRateLimiting(); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("This is ratelimit test"); - }); - } - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } private void WhenICallTheMiddlewareMultipleTime(int times) @@ -93,11 +100,12 @@ for (int i = 0; i < times; i++) { - var request = new HttpRequestMessage(new HttpMethod("GET"), Url); + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); request.Headers.Add("ClientId", clientId); + _downstreamContext.DownstreamRequest = request; - var response = Client.SendAsync(request); - responseStatusCode = (int)response.Result.StatusCode; + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; } } @@ -107,22 +115,58 @@ for (int i = 0; i < 10; i++) { - var request = new HttpRequestMessage(new HttpMethod("GET"), Url); + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); request.Headers.Add("ClientId", clientId); + _downstreamContext.DownstreamRequest = request; + _downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId); - var response = Client.SendAsync(request); - responseStatusCode = (int)response.Result.StatusCode; + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; } } private void ThenresponseStatusCodeIs429() { - responseStatusCode.ShouldBe(429); + _responseStatusCode.ShouldBe(429); } private void ThenresponseStatusCodeIs200() { - responseStatusCode.ShouldBe(200); + _responseStatusCode.ShouldBe(200); } } + + class FakeStream : Stream + { + public override void Flush() + { + throw new System.NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new System.NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new System.NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new System.NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + //do nothing + } + + public override bool CanRead { get; } + public override bool CanSeek { get; } + public override bool CanWrite => true; + public override long Length { get; } + public override long Position { get; set; } + } } diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index 0e62e6cd..5661ace6 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -1,4 +1,6 @@ -namespace Ocelot.UnitTests.Request +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Request { using System.Net.Http; using Microsoft.AspNetCore.Http; @@ -10,6 +12,8 @@ using TestStack.BDDfy; using Xunit; using Ocelot.Responses; + using Ocelot.DownstreamRouteFinder.Middleware; + using Shouldly; public class DownstreamRequestInitialiserMiddlewareTests { @@ -19,17 +23,16 @@ readonly Mock _httpRequest; - readonly Mock _next; + readonly Mock _next; readonly Mock _requestMapper; - readonly Mock _repo; - readonly Mock _loggerFactory; readonly Mock _logger; Response _mappedRequest; + private DownstreamContext _downstreamContext; public DownstreamRequestInitialiserMiddlewareTests() { @@ -37,8 +40,7 @@ _httpContext = new Mock(); _httpRequest = new Mock(); _requestMapper = new Mock(); - _repo = new Mock(); - _next = new Mock(); + _next = new Mock(); _logger = new Mock(); _loggerFactory = new Mock(); @@ -49,8 +51,9 @@ _middleware = new DownstreamRequestInitialiserMiddleware( _next.Object, _loggerFactory.Object, - _repo.Object, _requestMapper.Object); + + _downstreamContext = new DownstreamContext(_httpContext.Object); } [Fact] @@ -104,7 +107,7 @@ private void WhenTheMiddlewareIsInvoked() { - _middleware.Invoke(_httpContext.Object).GetAwaiter().GetResult(); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void ThenTheContexRequestIsMappedToADownstreamRequest() @@ -114,29 +117,28 @@ private void ThenTheDownstreamRequestIsStored() { - _repo.Verify(r => r.Add("DownstreamRequest", _mappedRequest.Data), Times.Once); + _downstreamContext.DownstreamRequest.ShouldNotBeNull(); } private void ThenTheDownstreamRequestIsNotStored() { - _repo.Verify(r => r.Add("DownstreamRequest", It.IsAny()), Times.Never); + _downstreamContext.DownstreamRequest.ShouldBeNull(); } private void ThenAPipelineErrorIsStored() { - _repo.Verify(r => r.Add("OcelotMiddlewareError", true), Times.Once); - _repo.Verify(r => r.Add("OcelotMiddlewareErrors", _mappedRequest.Errors), Times.Once); + _downstreamContext.IsError.ShouldBeTrue(); + _downstreamContext.Errors.ShouldBe(_mappedRequest.Errors); } private void ThenTheNextMiddlewareIsInvoked() { - _next.Verify(n => n(_httpContext.Object), Times.Once); + _next.Verify(n => n(_downstreamContext), Times.Once); } private void ThenTheNextMiddlewareIsNotInvoked() { - _next.Verify(n => n(It.IsAny()), Times.Never); + _next.Verify(n => n(It.IsAny()), Times.Never); } - } } diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs deleted file mode 100644 index b4d6acdc..00000000 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ /dev/null @@ -1,152 +0,0 @@ -namespace Ocelot.UnitTests.Request -{ - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Infrastructure.RequestData; - using Ocelot.Logging; - using Ocelot.Request.Builder; - using Ocelot.Request.Middleware; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - using Ocelot.Requester.QoS; - using Ocelot.Configuration; - using Microsoft.AspNetCore.Builder; - using Ocelot.Errors; - - public class HttpRequestBuilderMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _requestBuilder; - private readonly Mock _scopedRepository; - private readonly Mock _qosProviderHouse; - private readonly HttpRequestMessage _downstreamRequest; - private OkResponse _request; - private OkResponse _downstreamUrl; - private OkResponse _downstreamRoute; - - public HttpRequestBuilderMiddlewareTests() - { - _qosProviderHouse = new Mock(); - _requestBuilder = new Mock(); - _scopedRepository = new Mock(); - - _downstreamRequest = new HttpRequestMessage(); - - _scopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_scoped_data_repository_correctly() - { - - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true,false)) - .Build()); - - this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) - .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse(new NoQoSProvider()))) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider(), false, false, "", false))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_call_scoped_data_repository_QosProviderError() - { - - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) - .Build()); - - this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) - .And(x => x.GivenTheQosProviderHouseReturns(new ErrorResponse(It.IsAny()))) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider(), false, false, "", false))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedDataRepositoryQosProviderError()) - .BDDfy(); - } - - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_qosProviderHouse.Object); - services.AddSingleton(_requestBuilder.Object); - services.AddSingleton(_scopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpRequestBuilderMiddleware(); - } - - private void GivenTheDownStreamUrlIs(string downstreamUrl) - { - _downstreamUrl = new OkResponse(downstreamUrl); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamUrl); - } - - private void GivenTheQosProviderHouseReturns(Response qosProvider) - { - _qosProviderHouse - .Setup(x => x.Get(It.IsAny())) - .Returns(qosProvider); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheRequestBuilderReturns(Ocelot.Request.Request request) - { - _request = new OkResponse(request); - - _requestBuilder - .Setup(x => x.Build(It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(_request); - } - - private void ThenTheScopedDataRepositoryIsCalledCorrectly() - { - _scopedRepository - .Verify(x => x.Add("Request", _request.Data), Times.Once()); - } - - private void ThenTheScopedDataRepositoryQosProviderError() - { - _scopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once()); - } - } -} diff --git a/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs deleted file mode 100644 index dbfc8643..00000000 --- a/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace Ocelot.UnitTests.Request -{ - using System.Net.Http; - - using Ocelot.Request.Builder; - using Ocelot.Requester.QoS; - using Ocelot.Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class HttpRequestCreatorTests - { - private readonly IRequestCreator _requestCreator; - private readonly bool _isQos; - private readonly IQoSProvider _qoSProvider; - private readonly HttpRequestMessage _requestMessage; - private readonly bool _useCookieContainer; - private readonly bool _allowAutoRedirect; - private Response _response; - private string _reRouteKey; - private readonly bool _useTracing; - - public HttpRequestCreatorTests() - { - _requestCreator = new HttpRequestCreator(); - _isQos = true; - _qoSProvider = new NoQoSProvider(); - _useCookieContainer = false; - _allowAutoRedirect = false; - - _requestMessage = new HttpRequestMessage(); - } - - [Fact] - public void ShouldBuildRequest() - { - this.When(x => x.WhenIBuildARequest()) - .Then(x => x.ThenTheRequestContainsTheRequestMessage()) - .Then(x => x.ThenTheRequestContainsTheIsQos()) - .Then(x => x.ThenTheRequestContainsTheQosProvider()) - .Then(x => x.ThenTheRequestContainsUseCookieContainer()) - .Then(x => x.ThenTheRequestContainsUseTracing()) - .Then(x => x.ThenTheRequestContainsAllowAutoRedirect()) - .BDDfy(); - } - - private void WhenIBuildARequest() - { - _response = _requestCreator.Build(_requestMessage, - _isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect, _reRouteKey, _useTracing) - .GetAwaiter() - .GetResult(); - } - - private void ThenTheRequestContainsTheRequestMessage() - { - _response.Data.HttpRequestMessage.ShouldBe(_requestMessage); - } - - private void ThenTheRequestContainsTheIsQos() - { - _response.Data.IsQos.ShouldBe(_isQos); - } - - private void ThenTheRequestContainsTheQosProvider() - { - _response.Data.QosProvider.ShouldBe(_qoSProvider); - } - - private void ThenTheRequestContainsUseCookieContainer() - { - _response.Data.UseCookieContainer.ShouldBe(_useCookieContainer); - } - - private void ThenTheRequestContainsUseTracing() - { - _response.Data.IsTracing.ShouldBe(_useTracing); - } - - private void ThenTheRequestContainsAllowAutoRedirect() - { - _response.Data.AllowAutoRedirect.ShouldBe(_allowAutoRedirect); - } - } -} diff --git a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs index bd3130de..40276547 100644 --- a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs @@ -1,13 +1,15 @@ -namespace Ocelot.UnitTests.RequestId +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.RequestId { + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Primitives; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Infrastructure.RequestData; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; @@ -19,22 +21,32 @@ using TestStack.BDDfy; using Xunit; - public class ReRouteRequestIdMiddlewareTests : ServerHostedMiddlewareTest + public class ReRouteRequestIdMiddlewareTests { private readonly HttpRequestMessage _downstreamRequest; private Response _downstreamRoute; private string _value; private string _key; + private Mock _loggerFactory; + private Mock _logger; + private readonly ReRouteRequestIdMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + private readonly Mock _repo; public ReRouteRequestIdMiddlewareTests() { _downstreamRequest = new HttpRequestMessage(); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); + _repo = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + context.HttpContext.Response.Headers.Add("LSRequestId", context.HttpContext.TraceIdentifier); + }; + _middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object); + _downstreamContext.DownstreamRequest = _downstreamRequest; } [Fact] @@ -42,8 +54,11 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -62,10 +77,13 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List {"Get"}) + .Build()) + .WithUpstreamHttpMethod(new List {"Get"}) + .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .And(x => GivenThereIsNoGlobalRequestId()) @@ -79,10 +97,13 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List {"Get"}) + .Build()) + .WithUpstreamHttpMethod(new List {"Get"}) + .Build()); var requestId = Guid.NewGuid().ToString(); @@ -100,10 +121,13 @@ { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List {"Get"}) + .Build()) + .WithUpstreamHttpMethod(new List {"Get"}) + .Build()); var requestId = Guid.NewGuid().ToString(); @@ -116,67 +140,57 @@ .BDDfy(); } + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + private void GivenThereIsNoGlobalRequestId() { - ScopedRepository.Setup(x => x.Get("RequestId")).Returns(new OkResponse(null)); + _repo.Setup(x => x.Get("RequestId")).Returns(new OkResponse(null)); } private void GivenTheRequestIdWasSetGlobally() { - ScopedRepository.Setup(x => x.Get("RequestId")).Returns(new OkResponse("alreadyset")); + _repo.Setup(x => x.Get("RequestId")).Returns(new OkResponse("alreadyset")); } private void ThenTheRequestIdIsSaved() { - ScopedRepository.Verify(x => x.Add("RequestId", _value), Times.Once); + _repo.Verify(x => x.Add("RequestId", _value), Times.Once); } private void ThenTheRequestIdIsUpdated() { - ScopedRepository.Verify(x => x.Update("RequestId", _value), Times.Once); + _repo.Verify(x => x.Update("RequestId", _value), Times.Once); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseRequestIdMiddleware(); - - app.Run(x => - { - x.Response.Headers.Add("LSRequestId", x.TraceIdentifier); - return Task.CompletedTask; - }); - } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } private void GivenTheRequestIdIsAddedToTheRequest(string key, string value) { _key = key; _value = value; - Client.DefaultRequestHeaders.TryAddWithoutValidation(_key, _value); + _downstreamContext.HttpContext.Request.Headers.TryAdd(_key, _value); } private void ThenTheTraceIdIsAnything() { - ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldNotBeNullOrEmpty(); + StringValues value; + _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out value); + value.First().ShouldNotBeNullOrEmpty(); } private void ThenTheTraceIdIs(string expected) { - ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldBe(expected); + StringValues value; + _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out value); + value.First().ShouldBe(expected); } } } diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs index f78917d4..8df95ec1 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs @@ -1,6 +1,9 @@ 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; @@ -14,7 +17,7 @@ namespace Ocelot.UnitTests.Requester private readonly DelegatingHandlerHandlerHouse _house; private Mock _factory; private readonly Mock _provider; - private Ocelot.Request.Request _request; + private DownstreamReRoute _request; private Response _result; public DelegatingHandlerHandlerHouseTests() @@ -27,9 +30,10 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_create_and_store_provider() { - var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "key", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - this.Given(x => GivenTheRequest(request)) + this.Given(x => GivenTheRequest(reRoute)) .And(x => GivenTheProviderReturns()) .When(x => WhenIGet()) .Then(x => ThenTheFactoryIsCalled(1)) @@ -40,9 +44,10 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_get_provider() { - var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "key", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - this.Given(x => GivenTheRequest(request)) + this.Given(x => GivenTheRequest(reRoute)) .And(x => GivenTheProviderReturns()) .And(x => WhenIGet()) .And(x => GivenTheFactoryIsCleared()) @@ -55,15 +60,34 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_return_error() { - var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "key", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - this.Given(x => GivenTheRequest(request)) + 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(); @@ -72,7 +96,7 @@ namespace Ocelot.UnitTests.Requester private void GivenTheProviderThrows() { - _factory.Setup(x => x.Get(It.IsAny())).Throws(); + _factory.Setup(x => x.Get(It.IsAny())).Throws(); } private void GivenTheFactoryIsCleared() @@ -90,14 +114,19 @@ namespace Ocelot.UnitTests.Requester _result = _house.Get(_request); } - private void GivenTheRequest(Ocelot.Request.Request request) + private void GivenTheRequest(DownstreamReRoute request) { _request = request; } private void GivenTheProviderReturns() { - _factory.Setup(x => x.Get(It.IsAny())).Returns(_provider.Object); + _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) diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index e64df1b9..e6942681 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -2,8 +2,13 @@ using System; using System.Collections.Generic; using System.Net.Http; using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Errors; using Ocelot.Logging; using Ocelot.Requester; +using Ocelot.Requester.QoS; +using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -14,17 +19,29 @@ namespace Ocelot.UnitTests.Requester { private readonly DelegatingHandlerHandlerProviderFactory _factory; private Mock _loggerFactory; - private Ocelot.Request.Request _request; - private IDelegatingHandlerHandlerProvider _provider; + private DownstreamReRoute _request; + private Response _provider; private readonly Mock _allRoutesProvider; + private readonly Mock _qosProviderHouse; + private readonly Mock _tracingFactory; public DelegatingHandlerHandlerProviderFactoryTests() { + _tracingFactory = new Mock(); + _qosProviderHouse = new Mock(); _allRoutesProvider = new Mock(); _loggerFactory = new Mock(); - _factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, null); + _factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, _tracingFactory.Object, _qosProviderHouse.Object); } + private void GivenTheQosProviderHouseReturns(Response qosProvider) + { + _qosProviderHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(qosProvider); + } + + [Fact] public void should_all_from_all_routes_provider_and_qos() { @@ -34,9 +51,11 @@ namespace Ocelot.UnitTests.Requester () => new FakeDelegatingHandler(1) }; - var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); - this.Given(x => GivenTheFollowingRequest(request)) + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheAllRoutesProviderReturns(handlers)) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) @@ -48,9 +67,10 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_return_provider_with_no_delegates() { - var request = new Ocelot.Request.Request(new HttpRequestMessage(), false, null, true, true, "", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); - this.Given(x => GivenTheFollowingRequest(request)) + this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheAllRoutesProviderReturns()) .When(x => WhenIGet()) .Then(x => ThenNoDelegatesAreInTheProvider()) @@ -60,9 +80,11 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_return_provider_with_qos_delegate() { - var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); - this.Given(x => GivenTheFollowingRequest(request)) + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheAllRoutesProviderReturns()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(1)) @@ -70,9 +92,28 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void should_return_error() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosProviderHouseReturns(new ErrorResponse(It.IsAny()))) + .And(x => GivenTheAllRoutesProviderReturns()) + .When(x => WhenIGet()) + .Then(x => ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorIsReturned() + { + _provider.IsError.ShouldBeTrue(); + } + private void ThenTheDelegatesAreAddedCorrectly() { - var delegates = _provider.Get(); + var delegates = _provider.Data.Get(); var del = delegates[0].Invoke(); var handler = (FakeDelegatingHandler) del; handler.Order.ShouldBe(0); @@ -94,7 +135,7 @@ namespace Ocelot.UnitTests.Requester private void ThenItIsPolly(int i) { - var delegates = _provider.Get(); + var delegates = _provider.Data.Get(); var del = delegates[i].Invoke(); del.ShouldBeOfType(); } @@ -102,10 +143,10 @@ namespace Ocelot.UnitTests.Requester private void ThenThereIsDelegatesInProvider(int count) { _provider.ShouldNotBeNull(); - _provider.Get().Count.ShouldBe(count); + _provider.Data.Get().Count.ShouldBe(count); } - private void GivenTheFollowingRequest(Ocelot.Request.Request request) + private void GivenTheFollowingRequest(DownstreamReRoute request) { _request = request; } @@ -118,7 +159,7 @@ namespace Ocelot.UnitTests.Requester private void ThenNoDelegatesAreInTheProvider() { _provider.ShouldNotBeNull(); - _provider.Get().Count.ShouldBe(0); + _provider.Data.Get().Count.ShouldBe(0); } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 6081590a..b5d7e1e6 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Net.Http; using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; using Ocelot.Requester; using Ocelot.Responses; using Shouldly; @@ -18,7 +20,7 @@ namespace Ocelot.UnitTests.Requester private IHttpClientBuilder _builderResult; private IHttpClient _httpClient; private HttpResponseMessage _response; - private Ocelot.Request.Request _request; + private DownstreamReRoute _request; public HttpClientBuilderTests() { @@ -62,13 +64,16 @@ namespace Ocelot.UnitTests.Requester private void GivenARequest() { - _request = new Ocelot.Request.Request(null, false, null, false, false, "", false); + var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + + _request = reRoute; } private void GivenTheHouseReturns() { _house - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse(_provider.Object)); } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index 29271671..6b01a8c1 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -1,13 +1,14 @@ -using Microsoft.Extensions.DependencyInjection; -using Moq; +using Moq; using Ocelot.Logging; using Ocelot.Requester; -using Ocelot.Requester.QoS; using Ocelot.Responses; using System; using System.Collections.Generic; using System.Net.Http; -using System.Text; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware; using TestStack.BDDfy; using Xunit; using Shouldly; @@ -21,7 +22,7 @@ namespace Ocelot.UnitTests.Requester private Mock _provider; private Response _response; private readonly HttpClientHttpRequester _httpClientRequester; - private Ocelot.Request.Request _request; + private DownstreamContext _request; private Mock _loggerFactory; private Mock _logger; @@ -30,7 +31,7 @@ namespace Ocelot.UnitTests.Requester _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.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(_provider.Object)); _logger = new Mock(); _loggerFactory = new Mock(); _loggerFactory @@ -43,7 +44,16 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_request_correctly() { - this.Given(x=>x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }, false, new NoQoSProvider(), false, false, "", false))) + var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + + var context = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamReRoute = reRoute, + DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }, + }; + + this.Given(x=>x.GivenTheRequestIs(context)) .When(x=>x.WhenIGetResponse()) .Then(x => x.ThenTheResponseIsCalledCorrectly()) .BDDfy(); @@ -52,13 +62,22 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_request_unable_to_complete_request() { - this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }, false, new NoQoSProvider(), false, false, "", false))) + var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + + var context = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamReRoute = reRoute, + DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }, + }; + + this.Given(x => x.GivenTheRequestIs(context)) .When(x => x.WhenIGetResponse()) .Then(x => x.ThenTheResponseIsCalledError()) .BDDfy(); } - private void GivenTheRequestIs(Ocelot.Request.Request request) + private void GivenTheRequestIs(DownstreamContext request) { _request = request; } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 5658984a..154beee8 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,81 +1,73 @@ +using Ocelot.Configuration.Builder; +using Ocelot.Middleware; + namespace Ocelot.UnitTests.Requester { + using Microsoft.AspNetCore.Http; using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Logging; using Ocelot.Requester; using Ocelot.Requester.Middleware; - using Ocelot.Requester.QoS; using Ocelot.Responses; using TestStack.BDDfy; using Xunit; + using Shouldly; - public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest + public class HttpRequesterMiddlewareTests { private readonly Mock _requester; private OkResponse _response; - private OkResponse _request; + private Mock _loggerFactory; + private Mock _logger; + private readonly HttpRequesterMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; public HttpRequesterMiddlewareTests() { _requester = new Mock(); - - GivenTheTestServerIsConfigured(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _middleware = new HttpRequesterMiddleware(_next, _loggerFactory.Object, _requester.Object); } [Fact] - public void should_call_scoped_data_repository_correctly() + public void should_call_services_correctly() { - this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false, "", false))) + this.Given(x => x.GivenTheRequestIs()) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) - .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_requester.Object); - services.AddSingleton(ScopedRepository.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + private void GivenTheRequestIs() { - app.UseHttpRequesterMiddleware(); - } - - private void GivenTheRequestIs(Ocelot.Request.Request request) - { - _request = new OkResponse(request); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_request); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _downstreamContext.DownstreamReRoute = new DownstreamReRouteBuilder().Build(); } private void GivenTheRequesterReturns(HttpResponseMessage response) { _response = new OkResponse(response); _requester - .Setup(x => x.GetResponse(It.IsAny())) + .Setup(x => x.GetResponse(It.IsAny())) .ReturnsAsync(_response); } - private void GivenTheScopedRepoReturns() - { - ScopedRepository - .Setup(x => x.Add(It.IsAny(), _response.Data)) - .Returns(new OkResponse()); - } - private void ThenTheScopedRepoIsCalledCorrectly() { - ScopedRepository - .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); + _downstreamContext.DownstreamResponse.ShouldBe(_response.Data); } } } diff --git a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs index 2c595f6c..bc10e89e 100644 --- a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs @@ -13,7 +13,7 @@ namespace Ocelot.UnitTests.Requester public class QoSProviderFactoryTests { private readonly IQoSProviderFactory _factory; - private ReRoute _reRoute; + private DownstreamReRoute _reRoute; private IQoSProvider _result; private Mock _loggerFactory; private Mock _logger; @@ -31,7 +31,7 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_return_no_qos_provider() { - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "get" }) .WithIsQos(false) .Build(); @@ -51,7 +51,7 @@ namespace Ocelot.UnitTests.Requester .WithExceptionsAllowedBeforeBreaking(100) .Build(); - var reRoute = new ReRouteBuilder() + var reRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "get" }) .WithIsQos(true) .WithQosOptions(qosOptions) @@ -63,7 +63,7 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void GivenAReRoute(ReRoute reRoute) + private void GivenAReRoute(DownstreamReRoute reRoute) { _reRoute = reRoute; } diff --git a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs index b3c91c5b..487a3509 100644 --- a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs +++ b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs @@ -15,7 +15,7 @@ namespace Ocelot.UnitTests.Requester private readonly QosProviderHouse _qosProviderHouse; private Response _addResult; private Response _getResult; - private ReRoute _reRoute; + private DownstreamReRoute _reRoute; private readonly Mock _factory; public QosProviderHouseTests() @@ -27,7 +27,7 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_store_qos_provider_on_first_request() { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) .Then(x => x.ThenItIsAdded()) @@ -37,7 +37,7 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_not_store_qos_provider_on_first_request() { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) .When(x => x.WhenWeGetTheQoSProvider(reRoute)) @@ -48,8 +48,8 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_store_qos_providers_by_key() { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - var reRouteTwo = new ReRouteBuilder().WithReRouteKey("testTwo").Build(); + var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build(); + var reRouteTwo = new DownstreamReRouteBuilder().WithReRouteKey("testTwo").Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) .And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider())) @@ -63,7 +63,7 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_return_error_if_no_qos_provider_with_key() { - var reRoute = new ReRouteBuilder().Build(); + var reRoute = new DownstreamReRouteBuilder().Build(); this.When(x => x.WhenWeGetTheQoSProvider(reRoute)) .Then(x => x.ThenAnErrorIsReturned()) @@ -73,9 +73,9 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed() { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build(); - var reRouteTwo = new ReRouteBuilder().WithReRouteKey("test").WithIsQos(true).Build(); + var reRouteTwo = new DownstreamReRouteBuilder().WithReRouteKey("test").WithIsQos(true).Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) .When(x => x.WhenWeGetTheQoSProvider(reRoute)) @@ -85,7 +85,7 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(ReRoute reRoute) + private void WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(DownstreamReRoute reRoute) { _reRoute = reRoute; _factory.Setup(x => x.Get(_reRoute)).Returns(new FakePollyQoSProvider()); @@ -112,7 +112,7 @@ namespace Ocelot.UnitTests.Requester } - private void GivenThereIsAQoSProvider(ReRoute reRoute, IQoSProvider qoSProvider) + private void GivenThereIsAQoSProvider(DownstreamReRoute reRoute, IQoSProvider qoSProvider) { _reRoute = reRoute; _qoSProvider = qoSProvider; @@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Requester _getResult = _qosProviderHouse.Get(reRoute); } - private void WhenWeGetTheQoSProvider(ReRoute reRoute) + private void WhenWeGetTheQoSProvider(DownstreamReRoute reRoute) { _getResult = _qosProviderHouse.Get(reRoute); } diff --git a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs new file mode 100644 index 00000000..e8196966 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs @@ -0,0 +1,27 @@ +using Butterfly.Client.Tracing; +using Moq; +using Ocelot.Requester; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class TracingHandlerFactoryTests + { + private TracingHandlerFactory _factory; + private Mock _tracer; + + public TracingHandlerFactoryTests() + { + _tracer = new Mock(); + _factory = new TracingHandlerFactory(_tracer.Object); + } + + [Fact] + public void should_return() + { + var handler = _factory.Get(); + handler.ShouldBeOfType(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index ce98a972..fb0720b4 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,32 +1,45 @@ -namespace Ocelot.UnitTests.Responder +using System.Collections.Generic; +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Responder { - using System.Collections.Generic; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Errors; using Ocelot.Logging; - using Ocelot.Requester; using Ocelot.Responder; using Ocelot.Responder.Middleware; using Ocelot.Responses; using TestStack.BDDfy; using Xunit; - public class ResponderMiddlewareTests : ServerHostedMiddlewareTest + public class ResponderMiddlewareTests { private readonly Mock _responder; private readonly Mock _codeMapper; private OkResponse _response; + private Mock _loggerFactory; + private Mock _logger; + private readonly ResponderMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + public ResponderMiddlewareTests() { _responder = new Mock(); _codeMapper = new Mock(); - - GivenTheTestServerIsConfigured(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + //do nothing + }; + _middleware = new ResponderMiddleware(_next, _responder.Object, _loggerFactory.Object, _codeMapper.Object); } [Fact] @@ -39,7 +52,6 @@ .BDDfy(); } - [Fact] public void should_return_any_errors() { @@ -50,33 +62,19 @@ .BDDfy(); } - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + private void WhenICallTheMiddleware() { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_codeMapper.Object); - services.AddSingleton(_responder.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseResponderMiddleware(); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) { - _response = new OkResponse(response); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_response); + _downstreamContext.DownstreamResponse = response; } private void GivenThereAreNoPipelineErrors() { - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(false)); + _downstreamContext.Errors = new List(); } private void ThenThereAreNoErrors() @@ -86,11 +84,7 @@ private void GivenThereArePipelineErrors(Error error) { - ScopedRepository - .Setup(x => x.Get("OcelotMiddlewareError")) - .Returns(new OkResponse(true)); - ScopedRepository.Setup(x => x.Get>("OcelotMiddlewareErrors")) - .Returns(new OkResponse>(new List() { error })); + _downstreamContext.Errors = new List(){error}; } } } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 625ffaa3..f8c60bb7 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -16,7 +16,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private ServiceProviderConfiguration _serviceConfig; private IServiceDiscoveryProvider _result; private readonly ServiceDiscoveryProviderFactory _factory; - private ReRoute _reRoute; + private DownstreamReRoute _reRoute; private Mock _loggerFactory; public ServiceProviderFactoryTests() @@ -31,7 +31,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery var serviceConfig = new ServiceProviderConfigurationBuilder() .Build(); - var reRoute = new ReRouteBuilder().Build(); + var reRoute = new DownstreamReRouteBuilder().Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) .When(x => x.WhenIGetTheServiceProvider()) @@ -51,7 +51,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery new DownstreamHostAndPort("abc.com", 80) }; - var reRoute = new ReRouteBuilder().WithDownstreamAddresses(downstreamAddresses).Build(); + var reRoute = new DownstreamReRouteBuilder().WithDownstreamAddresses(downstreamAddresses).Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) .When(x => x.WhenIGetTheServiceProvider()) @@ -60,6 +60,23 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_return_consul_service_provider() + { + var reRoute = new DownstreamReRouteBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .Build(); + + var serviceConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) { var result = (ConfigurationServiceProvider)_result; @@ -75,24 +92,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery } } - [Fact] - public void should_return_consul_service_provider() - { - var reRoute = new ReRouteBuilder() - .WithServiceName("product") - .WithUseServiceDiscovery(true) - .Build(); - - var serviceConfig = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) + private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) { _serviceConfig = serviceConfig; _reRoute = reRoute;