diff --git a/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs b/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs index 4c27ec37..12698ed4 100644 --- a/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs +++ b/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs @@ -4,5 +4,6 @@ { public string DownstreamTemplate { get; set; } public string UpstreamTemplate { get; set; } + public string UpstreamHttpMethod { get; set; } } } diff --git a/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/DownstreamRouteFinder.cs b/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/DownstreamRouteFinder.cs index 0cc2f18e..f02000c3 100644 --- a/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/DownstreamRouteFinder.cs +++ b/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/DownstreamRouteFinder.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.Options; using Ocelot.Library.Infrastructure.Responses; using Ocelot.Library.Infrastructure.UrlMatcher; @@ -16,9 +18,9 @@ namespace Ocelot.Library.Infrastructure.DownstreamRouteFinder _urlMatcher = urlMatcher; } - public Response FindDownstreamRoute(string upstreamUrlPath) + public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) { - foreach (var template in _configuration.Value.ReRoutes) + foreach (var template in _configuration.Value.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase))) { var urlMatch = _urlMatcher.Match(upstreamUrlPath, template.UpstreamTemplate); diff --git a/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/IDownstreamRouteFinder.cs b/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/IDownstreamRouteFinder.cs index 1394b3af..4b8f7105 100644 --- a/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/IDownstreamRouteFinder.cs +++ b/src/Ocelot.Library/Infrastructure/DownstreamRouteFinder/IDownstreamRouteFinder.cs @@ -4,6 +4,6 @@ namespace Ocelot.Library.Infrastructure.DownstreamRouteFinder { public interface IDownstreamRouteFinder { - Response FindDownstreamRoute(string upstreamUrlPath); + Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); } } diff --git a/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs new file mode 100644 index 00000000..a4c4738a --- /dev/null +++ b/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Library.Infrastructure.DownstreamRouteFinder; +using Ocelot.Library.Infrastructure.Responder; + +namespace Ocelot.Library.Middleware +{ + public class DownstreamRouteFinderMiddleware + { + private readonly RequestDelegate _next; + private readonly IDownstreamRouteFinder _downstreamRouteFinder; + private readonly IHttpResponder _responder; + + public DownstreamRouteFinderMiddleware(RequestDelegate next, + IDownstreamRouteFinder downstreamRouteFinder, + IHttpResponder responder) + { + _next = next; + _downstreamRouteFinder = downstreamRouteFinder; + _responder = responder; + } + + public async Task Invoke(HttpContext context) + { + var upstreamUrlPath = context.Request.Path.ToString(); + + var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); + + if (downstreamRoute.IsError) + { + await _responder.CreateNotFoundResponse(context); + return; + } + + context.Items.Add("DownstreamRoute", downstreamRoute.Data); + + await _next.Invoke(context); + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs b/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs new file mode 100644 index 00000000..ec454bf4 --- /dev/null +++ b/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Library.Middleware +{ + public static class DownstreamRouteFinderMiddlewareExtensions + { + public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs new file mode 100644 index 00000000..30e41802 --- /dev/null +++ b/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Library.Infrastructure.DownstreamRouteFinder; +using Ocelot.Library.Infrastructure.UrlTemplateReplacer; + +namespace Ocelot.Library.Middleware +{ + public class DownstreamUrlCreatorMiddleware + { + private readonly RequestDelegate _next; + private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer; + + public DownstreamUrlCreatorMiddleware(RequestDelegate next, + IDownstreamUrlTemplateVariableReplacer urlReplacer) + { + _next = next; + _urlReplacer = urlReplacer; + } + + public async Task Invoke(HttpContext context) + { + var downstreamRoute = GetDownstreamRouteFromOwinItems(context); + + var downstreamUrl = _urlReplacer.ReplaceTemplateVariables(downstreamRoute); + + context.Items.Add("DownstreamUrl", downstreamUrl); + + await _next.Invoke(context); + } + + private DownstreamRoute GetDownstreamRouteFromOwinItems(HttpContext context) + { + object obj; + DownstreamRoute downstreamRoute = null; + if (context.Items.TryGetValue("DownstreamRoute", out obj)) + { + downstreamRoute = (DownstreamRoute) obj; + } + return downstreamRoute; + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs b/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs new file mode 100644 index 00000000..0ba3e58c --- /dev/null +++ b/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Library.Middleware +{ + public static class DownstreamUrlCreatorMiddlewareExtensions + { + public static IApplicationBuilder UserDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs new file mode 100644 index 00000000..d2b4b1e1 --- /dev/null +++ b/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Library.Infrastructure.Requester; +using Ocelot.Library.Infrastructure.Responder; + +namespace Ocelot.Library.Middleware +{ + public class HttpRequesterMiddleware + { + private readonly RequestDelegate _next; + private readonly IHttpRequester _requester; + private readonly IHttpResponder _responder; + + public HttpRequesterMiddleware(RequestDelegate next, + IHttpRequester requester, + IHttpResponder responder) + { + _next = next; + _requester = requester; + _responder = responder; + } + + public async Task Invoke(HttpContext context) + { + var downstreamUrl = GetDownstreamUrlFromOwinItems(context); + + var response = await _requester + .GetResponse(context.Request.Method, downstreamUrl, context.Request.Body, + context.Request.Headers, context.Request.Cookies, context.Request.Query, context.Request.ContentType); + + await _responder.CreateResponse(context, response); + + await _next.Invoke(context); + } + + private string GetDownstreamUrlFromOwinItems(HttpContext context) + { + object obj; + string downstreamUrl = null; + if (context.Items.TryGetValue("DownstreamUrl", out obj)) + { + downstreamUrl = (string) obj; + } + return downstreamUrl; + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/HttpRequesterMiddlewareExtensions.cs b/src/Ocelot.Library/Middleware/HttpRequesterMiddlewareExtensions.cs new file mode 100644 index 00000000..8aead7bc --- /dev/null +++ b/src/Ocelot.Library/Middleware/HttpRequesterMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Library.Middleware +{ + public static class HttpRequesterMiddlewareExtensions + { + public static IApplicationBuilder UseHttpRequesterMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/ProxyExtensions.cs b/src/Ocelot.Library/Middleware/ProxyExtensions.cs deleted file mode 100644 index 83777448..00000000 --- a/src/Ocelot.Library/Middleware/ProxyExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Library.Middleware -{ - public static class ProxyExtensions - { - public static IApplicationBuilder UseProxy(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/ProxyMiddleware.cs b/src/Ocelot.Library/Middleware/ProxyMiddleware.cs deleted file mode 100644 index 4540b25b..00000000 --- a/src/Ocelot.Library/Middleware/ProxyMiddleware.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Ocelot.Library.Infrastructure.Configuration; -using Ocelot.Library.Infrastructure.DownstreamRouteFinder; -using Ocelot.Library.Infrastructure.Requester; -using Ocelot.Library.Infrastructure.Responder; -using Ocelot.Library.Infrastructure.UrlTemplateReplacer; - -namespace Ocelot.Library.Middleware -{ - public class ProxyMiddleware - { - private readonly RequestDelegate _next; - private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer; - private readonly IOptions _configuration; - private readonly IDownstreamRouteFinder _downstreamRouteFinder; - private readonly IHttpRequester _requester; - private readonly IHttpResponder _responder; - - public ProxyMiddleware(RequestDelegate next, - IDownstreamUrlTemplateVariableReplacer urlReplacer, - IOptions configuration, - IDownstreamRouteFinder downstreamRouteFinder, - IHttpRequester requester, - IHttpResponder responder) - { - _next = next; - _urlReplacer = urlReplacer; - _configuration = configuration; - _downstreamRouteFinder = downstreamRouteFinder; - _requester = requester; - _responder = responder; - } - - public async Task Invoke(HttpContext context) - { - var upstreamUrlPath = context.Request.Path.ToString(); - - var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath); - - if (downstreamRoute.IsError) - { - await _responder.CreateNotFoundResponse(context); - return; - } - - var downstreamUrl = _urlReplacer.ReplaceTemplateVariables(downstreamRoute.Data); - - var response = await _requester - .GetResponse(context.Request.Method, downstreamUrl, context.Request.Body, - context.Request.Headers, context.Request.Cookies, context.Request.Query, context.Request.ContentType); - - await _responder.CreateResponse(context, response); - - await _next.Invoke(context); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Startup.cs b/src/Ocelot/Startup.cs index 5b1fff8e..1142231f 100644 --- a/src/Ocelot/Startup.cs +++ b/src/Ocelot/Startup.cs @@ -51,7 +51,11 @@ namespace Ocelot loggerFactory.AddDebug(); - app.UseProxy(); + app.UseDownstreamRouteFinderMiddleware(); + + app.UserDownstreamUrlCreatorMiddleware(); + + app.UseHttpRequesterMiddleware(); } } } diff --git a/test/Ocelot.AcceptanceTests/OcelotTests.cs b/test/Ocelot.AcceptanceTests/OcelotTests.cs index 2d2a348b..4d57adf2 100644 --- a/test/Ocelot.AcceptanceTests/OcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/OcelotTests.cs @@ -50,7 +50,8 @@ namespace Ocelot.AcceptanceTests new ReRoute { DownstreamTemplate = "http://localhost:51879/", - UpstreamTemplate = "/" + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get" } } })) @@ -72,7 +73,8 @@ namespace Ocelot.AcceptanceTests new ReRoute { DownstreamTemplate = "http://localhost:51879/", - UpstreamTemplate = "/" + UpstreamTemplate = "/", + UpstreamHttpMethod = "Post" } } })) diff --git a/test/Ocelot.AcceptanceTests/configuration.yaml b/test/Ocelot.AcceptanceTests/configuration.yaml index 97b35e41..8dda884b 100644 --- a/test/Ocelot.AcceptanceTests/configuration.yaml +++ b/test/Ocelot.AcceptanceTests/configuration.yaml @@ -1,3 +1,4 @@ ReRoutes: - DownstreamTemplate: http://localhost:51879/ UpstreamTemplate: / + HttpMethod: Get diff --git a/test/Ocelot.UnitTests/Requester/RequesterTests.cs b/test/Ocelot.UnitTests/Configuration/Requester/RequesterTests.cs similarity index 100% rename from test/Ocelot.UnitTests/Requester/RequesterTests.cs rename to test/Ocelot.UnitTests/Configuration/Requester/RequesterTests.cs diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 6d115a66..a1c23bee 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -21,6 +21,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private Response _response; private Library.Infrastructure.Configuration.Configuration _configuration; private UrlMatch _match; + private string _upstreamHttpMethod; public DownstreamRouteFinderTests() { @@ -39,11 +40,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRoute() { UpstreamTemplate = "someUpstreamPath", - DownstreamTemplate = "someDownstreamPath" + DownstreamTemplate = "someDownstreamPath", + UpstreamHttpMethod = "Get" } } })) .And(x => x.GivenTheUrlMatcherReturns(new UrlMatch(true, new List(), "someDownstreamPath"))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), "someDownstreamPath"))) @@ -51,6 +54,36 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_return_correct_route_for_http_verb() + { + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheConfigurationIs(new Library.Infrastructure.Configuration.Configuration + { + ReRoutes = new List + { + new ReRoute() + { + UpstreamTemplate = "someUpstreamPath", + DownstreamTemplate = "someDownstreamPath", + UpstreamHttpMethod = "Get" + }, + new ReRoute() + { + UpstreamTemplate = "someUpstreamPath", + DownstreamTemplate = "someDownstreamPathForAPost", + UpstreamHttpMethod = "Post" + } + } + })) + .And(x => x.GivenTheUrlMatcherReturns(new UrlMatch(true, new List(), "someDownstreamPathForAPost"))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), "someDownstreamPathForAPost"))) + .BDDfy(); + } + [Fact] public void should_not_return_route() { @@ -62,11 +95,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRoute() { UpstreamTemplate = "somePath", - DownstreamTemplate = "somPath" + DownstreamTemplate = "somPath", + UpstreamHttpMethod = "Get" } } })) .And(x => x.GivenTheUrlMatcherReturns(new UrlMatch(false, new List(), null))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( x => x.ThenAnErrorResponseIsReturned()) @@ -74,6 +109,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) + { + _upstreamHttpMethod = upstreamHttpMethod; + } + private void ThenAnErrorResponseIsReturned() { _result.IsError.ShouldBeTrue(); @@ -108,7 +148,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath); + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 7b0acc6e..6e5a6811 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -25,7 +25,8 @@ "Shouldly": "2.8.0", "TestStack.BDDfy": "4.3.1", "YamlDotNet": "3.9.0", - "Moq": "4.6.38-alpha" + "Moq": "4.6.38-alpha", + "Microsoft.AspNetCore.TestHost": "1.0.0" }, "frameworks": {