diff --git a/src/Ocelot/Configuration/Provider/YamlOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs similarity index 50% rename from src/Ocelot/Configuration/Provider/YamlOcelotConfigurationProvider.cs rename to src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index 2a92f786..4b6c5fd2 100644 --- a/src/Ocelot/Configuration/Provider/YamlOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -7,12 +7,12 @@ namespace Ocelot.Configuration.Provider /// /// Register as singleton /// - public class YamlOcelotConfigurationProvider : IOcelotConfigurationProvider + public class OcelotConfigurationProvider : IOcelotConfigurationProvider { private readonly IOcelotConfigurationRepository _repo; private readonly IOcelotConfigurationCreator _creator; - public YamlOcelotConfigurationProvider(IOcelotConfigurationRepository repo, + public OcelotConfigurationProvider(IOcelotConfigurationRepository repo, IOcelotConfigurationCreator creator) { _repo = repo; @@ -21,28 +21,28 @@ namespace Ocelot.Configuration.Provider public Response Get() { - var config = _repo.Get(); + var repoConfig = _repo.Get(); - if (config.IsError) + if (repoConfig.IsError) { - return new ErrorResponse(config.Errors); + return new ErrorResponse(repoConfig.Errors); } - if (config.Data == null) + if (repoConfig.Data == null) { - var configuration = _creator.Create(); + var creatorConfig = _creator.Create(); - if (configuration.IsError) + if (creatorConfig.IsError) { - return new ErrorResponse(configuration.Errors); + return new ErrorResponse(creatorConfig.Errors); } - _repo.AddOrReplace(configuration.Data); + _repo.AddOrReplace(creatorConfig.Data); - return new OkResponse(configuration.Data); + return new OkResponse(creatorConfig.Data); } - return new OkResponse(config.Data); + return new OkResponse(repoConfig.Data); } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 6ed8a88d..0b943b19 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -34,7 +34,7 @@ namespace Ocelot.DependencyInjection // ocelot services. services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs b/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs new file mode 100644 index 00000000..798c4577 --- /dev/null +++ b/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs @@ -0,0 +1,27 @@ +namespace Ocelot.Middleware +{ + using System; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + + public class OcelotMiddlewareConfiguration + { + public Func, Task> PreHttpResponderMiddleware { get; set; } + public Func, Task> PostHttpResponderMiddleware { get; set; } + public Func, Task> PreDownstreamRouteFinderMiddleware { get; set; } + public Func, Task> PostDownstreamRouteFinderMiddleware { get; set; } + public Func, Task> PreAuthenticationMiddleware { get; set; } + public Func, Task> PostAuthenticationMiddleware { get; set; } + public Func, Task> PreClaimsBuilderMiddleware { get; set; } + public Func, Task> PostClaimsBuilderMiddleware { get; set; } + public Func, Task> PreAuthorisationMiddleware { get; set; } + public Func, Task> PostAuthorisationMiddleware { get; set; } + public Func, Task> PreHttpRequestHeadersBuilderMiddleware { get; set; } + public Func, Task> PostHttpRequestHeadersBuilderMiddleware { get; set; } + public Func, Task> PreDownstreamUrlCreatorMiddleware { get; set; } + public Func, Task> PostDownstreamUrlCreatorMiddleware { get; set; } + public Func, Task> PreHttpRequestBuilderMiddleware { get; set; } + public Func, Task> PostHttpRequestBuilderMiddleware { get; set; } + public Func, Task> PreHttpRequesterMiddleware { get; set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 866ac4e8..8fe0877f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -9,8 +9,11 @@ using Ocelot.Responder.Middleware; namespace Ocelot.Middleware { + using System; + using System.Threading.Tasks; using Authorisation.Middleware; using ClaimsBuilder.Middleware; + using Microsoft.AspNetCore.Http; public static class OcelotMiddlewareExtensions { @@ -36,5 +39,70 @@ namespace Ocelot.Middleware return builder; } + + public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + { + builder.UseIfNotNull(middlewareConfiguration.PreHttpResponderMiddleware); + + builder.UseHttpResponderMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostHttpResponderMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreDownstreamRouteFinderMiddleware); + + builder.UseDownstreamRouteFinderMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostDownstreamRouteFinderMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware); + + builder.UseAuthenticationMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostAuthenticationMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreClaimsBuilderMiddleware); + + builder.UseClaimsBuilderMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostClaimsBuilderMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreAuthorisationMiddleware); + + builder.UseAuthorisationMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostAuthorisationMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreHttpRequestHeadersBuilderMiddleware); + + builder.UseHttpRequestHeadersBuilderMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostHttpRequestHeadersBuilderMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreDownstreamUrlCreatorMiddleware); + + builder.UseDownstreamUrlCreatorMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostDownstreamUrlCreatorMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreHttpRequestBuilderMiddleware); + + builder.UseHttpRequestBuilderMiddleware(); + + builder.UseIfNotNull(middlewareConfiguration.PostHttpRequestBuilderMiddleware); + + builder.UseIfNotNull(middlewareConfiguration.PreHttpRequesterMiddleware); + + builder.UseHttpRequesterMiddleware(); + + return builder; + } + + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) + { + if (middleware != null) + { + builder.Use(middleware); + } + } } } diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs new file mode 100644 index 00000000..d7b680a5 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Ocelot.Configuration.Yaml; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using YamlDotNet.Serialization; + +namespace Ocelot.AcceptanceTests +{ + using System.Linq; + using System.Threading.Tasks; + using DependencyInjection; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Primitives; + using Middleware; + using ScopedData; + + public class CustomMiddlewareTests : IDisposable + { + private TestServer _server; + private HttpClient _client; + private HttpResponseMessage _response; + private readonly string _configurationPath; + private StringContent _postContent; + private IWebHost _builder; + + // Sadly we need to change this when we update the netcoreapp version to make the test update the config correctly + private double _netCoreAppVersion = 1.4; + + public CustomMiddlewareTests() + { + _configurationPath = $"configuration.yaml"; + } + + [Fact] + public void response_should_come_from_pre_http_responder_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + PreHttpResponderMiddleware = async (ctx, next) => + { + await ctx.Response.WriteAsync("PreHttpResponderMiddleware"); + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://localhost:41879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + })) + .And(x => x.GivenTheApiGatewayIsRunning(configuration)) + .When(x => x.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheResponseBodyShouldBe("PreHttpResponderMiddleware")) + .BDDfy(); + } + + [Fact] + public void response_should_come_from_pre_http_requester_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + PreHttpRequesterMiddleware = async (ctx, next) => + { + var service = ctx.RequestServices.GetService(); + service.Add("Response", + new HttpResponseMessage {Content = new StringContent("PreHttpRequesterMiddleware")}); + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://localhost:41879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + })) + .And(x => x.GivenTheApiGatewayIsRunning(configuration)) + .When(x => x.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheResponseBodyShouldBe("PreHttpRequesterMiddleware")) + .BDDfy(); + } + + + + /// + /// This is annoying cos it should be in the constructor but we need to set up the yaml file before calling startup so its a step. + /// + private void GivenTheApiGatewayIsRunning(OcelotMiddlewareConfiguration ocelotMiddlewareConfig) + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddYamlFile("configuration.yaml") + .AddEnvironmentVariables(); + + var configuration = builder.Build(); + + _server = new TestServer(new WebHostBuilder() + .UseConfiguration(configuration) + .ConfigureServices(s => + { + s.AddOcelotYamlConfiguration(configuration); + s.AddOcelot(); + }) + .ConfigureLogging(l => + { + l.AddConsole(configuration.GetSection("Logging")); + l.AddDebug(); + }) + .Configure(a => + { + a.UseOcelot(ocelotMiddlewareConfig); + })); + + _client = _server.CreateClient(); + } + + private void GivenThereIsAConfiguration(YamlConfiguration yamlConfiguration) + { + var serializer = new Serializer(); + + if (File.Exists(_configurationPath)) + { + File.Delete(_configurationPath); + } + + using (TextWriter writer = File.CreateText(_configurationPath)) + { + serializer.Serialize(writer, yamlConfiguration); + } + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _builder.Start(); + } + + private void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _client.GetAsync(url).Result; + } + + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + private void ThenTheResponseBodyShouldBe(string expectedBody) + { + _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); + } + + public void Dispose() + { + _builder?.Dispose(); + _client.Dispose(); + _server.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/configuration.yaml b/test/Ocelot.AcceptanceTests/configuration.yaml index 29e93d20..feba804e 100644 --- a/test/Ocelot.AcceptanceTests/configuration.yaml +++ b/test/Ocelot.AcceptanceTests/configuration.yaml @@ -1,13 +1,7 @@ ReRoutes: -- DownstreamTemplate: http://localhost:51876/ +- DownstreamTemplate: http://localhost:41879/ UpstreamTemplate: / - UpstreamHttpMethod: Post - AuthenticationOptions: - Provider: IdentityServer - ProviderRootUrl: http://localhost:51888 - ScopeName: api - AdditionalScopes: [] - ScopeSecret: secret - ProxyRequestHeaders: - - CustomerId: Claims[CustomerId] - + UpstreamHttpMethod: Get + AddHeadersToRequest: {} + AddClaimsToRequest: {} + RouteClaimsRequirement: {} diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 56f49099..0ff6cf15 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -4,7 +4,7 @@ "buildOptions": { "copyToOutput": { "include": [ - "configuration.yaml" + "middlewareConfiguration.yaml" ] } }, diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 39425e9d..921118bd 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -39,7 +39,7 @@ "preserveCompilationContext": true, "copyToOutput": { "include": [ - "configuration.yaml" + "middlewareConfiguration.yaml" ] } }, @@ -57,7 +57,7 @@ "Areas/**/Views", "appsettings.json", "web.config", - "configuration.yaml" + "middlewareConfiguration.yaml" ] }, diff --git a/test/Ocelot.ManualTest/web.config b/test/Ocelot.ManualTest/web.config index dc0514fc..894788ec 100644 --- a/test/Ocelot.ManualTest/web.config +++ b/test/Ocelot.ManualTest/web.config @@ -1,5 +1,5 @@  - +