From 58b82f0fc7a69324d1dcde35e84c4f04fe3647f5 Mon Sep 17 00:00:00 2001 From: Pitming Date: Fri, 8 Nov 2019 12:59:34 +0100 Subject: [PATCH 1/6] Add DownstreamHttpMethodCreatorMiddleware --- .../Builder/DownstreamReRouteBuilder.cs | 10 ++++++- .../Configuration/Creator/ReRoutesCreator.cs | 1 + src/Ocelot/Configuration/DownstreamReRoute.cs | 5 +++- src/Ocelot/Configuration/File/FileReRoute.cs | 1 + .../DownstreamHttpMethodCreatorMiddleware.cs | 27 +++++++++++++++++++ .../Request/Middleware/DownstreamRequest.cs | 4 ++- .../HttpRequestBuilderMiddlewareExtensions.cs | 4 ++- 7 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index 4b3e6ea3..ed978dde 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -41,6 +41,7 @@ namespace Ocelot.Configuration.Builder private List _addHeadersToUpstream; private bool _dangerousAcceptAnyServerCertificateValidator; private SecurityOptions _securityOptions; + private string _downstreamHttpMethod; public DownstreamReRouteBuilder() { @@ -56,6 +57,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithDownStreamHttpMethod(string method) + { + _downstreamHttpMethod = method; + return this; + } + public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) { _loadBalancerOptions = loadBalancerOptions; @@ -282,7 +289,8 @@ namespace Ocelot.Configuration.Builder _addHeadersToDownstream, _addHeadersToUpstream, _dangerousAcceptAnyServerCertificateValidator, - _securityOptions); + _securityOptions, + _downstreamHttpMethod); } } } diff --git a/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs b/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs index 4443e201..1cc463ef 100644 --- a/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs @@ -138,6 +138,7 @@ namespace Ocelot.Configuration.Creator .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) .WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator) .WithSecurityOptions(securityOptions) + .WithDownStreamHttpMethod(fileReRoute.DownstreamHttpMethod) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index e8dfade5..1c41f648 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -38,7 +38,8 @@ namespace Ocelot.Configuration List addHeadersToDownstream, List addHeadersToUpstream, bool dangerousAcceptAnyServerCertificateValidator, - SecurityOptions securityOptions) + SecurityOptions securityOptions, + string downstreamHttpMethod) { DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; @@ -72,6 +73,7 @@ namespace Ocelot.Configuration LoadBalancerKey = loadBalancerKey; AddHeadersToUpstream = addHeadersToUpstream; SecurityOptions = securityOptions; + DownstreamHttpMethod = downstreamHttpMethod; } public string Key { get; } @@ -106,5 +108,6 @@ namespace Ocelot.Configuration public List AddHeadersToUpstream { get; } public bool DangerousAcceptAnyServerCertificateValidator { get; } public SecurityOptions SecurityOptions { get; } + public string DownstreamHttpMethod { get; } } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index b15653ea..1bae31dd 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -29,6 +29,7 @@ namespace Ocelot.Configuration.File public string DownstreamPathTemplate { get; set; } public string UpstreamPathTemplate { get; set; } public List UpstreamHttpMethod { get; set; } + public string DownstreamHttpMethod { get; set; } public Dictionary AddHeadersToRequest { get; set; } public Dictionary UpstreamHeaderTransform { get; set; } public Dictionary DownstreamHeaderTransform { get; set; } diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs new file mode 100644 index 00000000..6689c0c1 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs @@ -0,0 +1,27 @@ +using Ocelot.Logging; +using Ocelot.Middleware; +using System.Threading.Tasks; + +namespace Ocelot.DownstreamUrlCreator.Middleware +{ + public class DownstreamHttpMethodCreatorMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + + public DownstreamHttpMethodCreatorMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) + { + _next = next; + } + + public async Task Invoke(DownstreamContext context) + { + if (context.DownstreamReRoute.DownstreamHttpMethod != null) + { + context.DownstreamRequest.Method = context.DownstreamReRoute.DownstreamHttpMethod; + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 256436e5..1715b387 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -24,7 +24,7 @@ namespace Ocelot.Request.Middleware public HttpRequestHeaders Headers { get; } - public string Method { get; } + public string Method { get; set; } public string OriginalString { get; } @@ -52,6 +52,8 @@ namespace Ocelot.Request.Middleware }; _request.RequestUri = uriBuilder.Uri; + _request.Method = new HttpMethod(Method); + _request.Content = Content; return _request; } diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs index 2d803e1e..6c43507c 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.Middleware.Pipeline; namespace Ocelot.Request.Middleware @@ -7,7 +8,8 @@ namespace Ocelot.Request.Middleware { public static IOcelotPipelineBuilder UseDownstreamRequestInitialiser(this IOcelotPipelineBuilder builder) { - return builder.UseMiddleware(); + return builder.UseMiddleware() + .UseMiddleware(); } } } From ec622dc0add3474e3288c94a328652cbc9e71f8d Mon Sep 17 00:00:00 2001 From: Pitming Date: Fri, 8 Nov 2019 13:08:01 +0100 Subject: [PATCH 2/6] Add DownstreamHttpMethodCreatorMiddleware --- .../Middleware/DownstreamMethodTransformerMiddleware.cs} | 8 ++++---- .../Middleware/Pipeline/OcelotPipelineExtensions.cs | 4 ++++ src/Ocelot/Request/Middleware/DownstreamRequest.cs | 1 - .../Middleware/HttpRequestBuilderMiddlewareExtensions.cs | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) rename src/Ocelot/{DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs => DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs} (68%) diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs b/src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs similarity index 68% rename from src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs rename to src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs index 6689c0c1..a17e9167 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamHttpMethodCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs @@ -2,14 +2,14 @@ using Ocelot.Middleware; using System.Threading.Tasks; -namespace Ocelot.DownstreamUrlCreator.Middleware +namespace Ocelot.DownstreamMethodTransformer.Middleware { - public class DownstreamHttpMethodCreatorMiddleware : OcelotMiddleware + public class DownstreamMethodTransformerMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - public DownstreamHttpMethodCreatorMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) + public DownstreamMethodTransformerMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) { _next = next; } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index d52cfb0d..02ee07f4 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -2,6 +2,7 @@ using Ocelot.Authorisation.Middleware; using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; +using Ocelot.DownstreamMethodTransformer.Middleware; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.Errors.Middleware; @@ -68,6 +69,9 @@ namespace Ocelot.Middleware.Pipeline // Initialises downstream request builder.UseDownstreamRequestInitialiser(); + //change Http Method + builder.UseMiddleware(); + // We check whether the request is ratelimit, and if there is no continue processing builder.UseRateLimiting(); diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 1715b387..24d96cfe 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -53,7 +53,6 @@ namespace Ocelot.Request.Middleware _request.RequestUri = uriBuilder.Uri; _request.Method = new HttpMethod(Method); - _request.Content = Content; return _request; } diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs index 6c43507c..e6eff7e0 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs @@ -9,7 +9,7 @@ namespace Ocelot.Request.Middleware public static IOcelotPipelineBuilder UseDownstreamRequestInitialiser(this IOcelotPipelineBuilder builder) { return builder.UseMiddleware() - .UseMiddleware(); + .UseMiddleware(); } } } From 6bd903bc8782eef72230684daaec7b1832d63d35 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 9 Feb 2020 15:37:27 +0000 Subject: [PATCH 3/6] tests passing --- samples/OcelotKube/ApiGateway/Startup.cs | 3 +- .../OcelotKube/DownstreamService/Startup.cs | 3 +- .../DownstreamMethodTransformerMiddleware.cs | 27 --------------- .../Pipeline/OcelotPipelineExtensions.cs | 4 --- .../DownstreamRequestInitialiserMiddleware.cs | 5 +++ .../HttpRequestBuilderMiddlewareExtensions.cs | 5 +-- ...streamRequestInitialiserMiddlewareTests.cs | 34 +++++++++++++++++-- 7 files changed, 41 insertions(+), 40 deletions(-) delete mode 100644 src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs diff --git a/samples/OcelotKube/ApiGateway/Startup.cs b/samples/OcelotKube/ApiGateway/Startup.cs index d7b2473c..3e37ffa3 100644 --- a/samples/OcelotKube/ApiGateway/Startup.cs +++ b/samples/OcelotKube/ApiGateway/Startup.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Ocelot.DependencyInjection; using Ocelot.Middleware; using Ocelot.Provider.Kubernetes; @@ -18,7 +19,7 @@ namespace ApiGateway } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { diff --git a/samples/OcelotKube/DownstreamService/Startup.cs b/samples/OcelotKube/DownstreamService/Startup.cs index 9a927a37..a8abb5d4 100644 --- a/samples/OcelotKube/DownstreamService/Startup.cs +++ b/samples/OcelotKube/DownstreamService/Startup.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -28,7 +29,7 @@ namespace DownstreamService } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { diff --git a/src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs b/src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs deleted file mode 100644 index a17e9167..00000000 --- a/src/Ocelot/DownstreamMethodTransformer/Middleware/DownstreamMethodTransformerMiddleware.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Threading.Tasks; - -namespace Ocelot.DownstreamMethodTransformer.Middleware -{ - public class DownstreamMethodTransformerMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - - public DownstreamMethodTransformerMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) - { - _next = next; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.DownstreamHttpMethod != null) - { - context.DownstreamRequest.Method = context.DownstreamReRoute.DownstreamHttpMethod; - } - - await _next.Invoke(context); - } - } -} diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index 02ee07f4..d52cfb0d 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -2,7 +2,6 @@ using Ocelot.Authorisation.Middleware; using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; -using Ocelot.DownstreamMethodTransformer.Middleware; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.Errors.Middleware; @@ -69,9 +68,6 @@ namespace Ocelot.Middleware.Pipeline // Initialises downstream request builder.UseDownstreamRequestInitialiser(); - //change Http Method - builder.UseMiddleware(); - // We check whether the request is ratelimit, and if there is no continue processing builder.UseRateLimiting(); diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index 442bb4b9..c9f6f859 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -34,6 +34,11 @@ namespace Ocelot.Request.Middleware context.DownstreamRequest = _creator.Create(downstreamRequest.Data); + if (!string.IsNullOrEmpty(context.DownstreamReRoute?.DownstreamHttpMethod)) + { + context.DownstreamRequest.Method = context.DownstreamReRoute.DownstreamHttpMethod; + } + await _next.Invoke(context); } } diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs index e6eff7e0..a2931cbb 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.Middleware.Pipeline; namespace Ocelot.Request.Middleware @@ -8,8 +6,7 @@ namespace Ocelot.Request.Middleware { public static IOcelotPipelineBuilder UseDownstreamRequestInitialiser(this IOcelotPipelineBuilder builder) { - return builder.UseMiddleware() - .UseMiddleware(); + return builder.UseMiddleware(); } } } diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index 93d963b6..aadc551c 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -1,6 +1,4 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Request +namespace Ocelot.UnitTests.Request { using Microsoft.AspNetCore.Http; using Moq; @@ -9,6 +7,8 @@ namespace Ocelot.UnitTests.Request using Ocelot.Request.Creator; using Ocelot.Request.Mapper; using Ocelot.Request.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.Middleware; using Ocelot.Responses; using Shouldly; using System.Net.Http; @@ -65,6 +65,24 @@ namespace Ocelot.UnitTests.Request .Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest()) .And(_ => ThenTheDownstreamRequestIsStored()) .And(_ => ThenTheNextMiddlewareIsInvoked()) + .And(_ => ThenTheDownstreamRequestMethodIs("GET")) + .BDDfy(); + } + + [Theory] + [InlineData("POST", "POST")] + [InlineData(null, "GET")] + [InlineData("", "GET")] + public void Should_map_downstream_reroute_method_to_downstream_request(string input, string expected) + { + this.Given(_ => GivenTheHttpContextContainsARequest()) + .And(_ => GivenTheDownstreamReRouteMethodIs(input)) + .And(_ => GivenTheMapperWillReturnAMappedRequest()) + .When(_ => WhenTheMiddlewareIsInvoked()) + .Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest()) + .And(_ => ThenTheDownstreamRequestIsStored()) + .And(_ => ThenTheNextMiddlewareIsInvoked()) + .And(_ => ThenTheDownstreamRequestMethodIs(expected)) .BDDfy(); } @@ -80,6 +98,16 @@ namespace Ocelot.UnitTests.Request .BDDfy(); } + private void GivenTheDownstreamReRouteMethodIs(string input) + { + _downstreamContext.DownstreamReRoute = new DownstreamReRouteBuilder().WithDownStreamHttpMethod(input).Build(); + } + + private void ThenTheDownstreamRequestMethodIs(string expected) + { + _downstreamContext.DownstreamRequest.Method.ShouldBe(expected); + } + private void GivenTheHttpContextContainsARequest() { _httpContext From 0c5e09d18f55d89aa2c279d2bd6c4ac04c480db9 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 9 Feb 2020 15:51:22 +0000 Subject: [PATCH 4/6] moved logic to request mapper and public setter gone --- src/Ocelot/Request/Mapper/IRequestMapper.cs | 3 +- src/Ocelot/Request/Mapper/RequestMapper.cs | 12 +++-- .../Request/Middleware/DownstreamRequest.cs | 2 +- .../DownstreamRequestInitialiserMiddleware.cs | 7 +-- ...streamRequestInitialiserMiddlewareTests.cs | 22 +++------ .../Request/Mapper/RequestMapperTests.cs | 47 ++++++++++++++----- 6 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/Ocelot/Request/Mapper/IRequestMapper.cs b/src/Ocelot/Request/Mapper/IRequestMapper.cs index d16a2d06..cd075c5d 100644 --- a/src/Ocelot/Request/Mapper/IRequestMapper.cs +++ b/src/Ocelot/Request/Mapper/IRequestMapper.cs @@ -1,12 +1,13 @@ namespace Ocelot.Request.Mapper { using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; using Ocelot.Responses; using System.Net.Http; using System.Threading.Tasks; public interface IRequestMapper { - Task> Map(HttpRequest request); + Task> Map(HttpRequest request, DownstreamReRoute downstreamReRoute); } } diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index f669c286..e050f1a6 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Primitives; + using Ocelot.Configuration; using Ocelot.Responses; using System; using System.Collections.Generic; @@ -15,14 +16,14 @@ { private readonly string[] _unsupportedHeaders = { "host" }; - public async Task> Map(HttpRequest request) + public async Task> Map(HttpRequest request, DownstreamReRoute downstreamReRoute) { try { var requestMessage = new HttpRequestMessage() { Content = await MapContent(request), - Method = MapMethod(request), + Method = MapMethod(request, downstreamReRoute), RequestUri = MapUri(request) }; @@ -71,8 +72,13 @@ } } - private HttpMethod MapMethod(HttpRequest request) + private HttpMethod MapMethod(HttpRequest request, DownstreamReRoute downstreamReRoute) { + if (!string.IsNullOrEmpty(downstreamReRoute?.DownstreamHttpMethod)) + { + return new HttpMethod(downstreamReRoute.DownstreamHttpMethod); + } + return new HttpMethod(request.Method); } diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 24d96cfe..cf973d94 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -24,7 +24,7 @@ namespace Ocelot.Request.Middleware public HttpRequestHeaders Headers { get; } - public string Method { get; set; } + public string Method { get; } public string OriginalString { get; } diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index c9f6f859..83a2ecb9 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -24,7 +24,7 @@ namespace Ocelot.Request.Middleware public async Task Invoke(DownstreamContext context) { - var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request); + var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request, context.DownstreamReRoute); if (downstreamRequest.IsError) { @@ -34,11 +34,6 @@ namespace Ocelot.Request.Middleware context.DownstreamRequest = _creator.Create(downstreamRequest.Data); - if (!string.IsNullOrEmpty(context.DownstreamReRoute?.DownstreamHttpMethod)) - { - context.DownstreamRequest.Method = context.DownstreamReRoute.DownstreamHttpMethod; - } - await _next.Invoke(context); } } diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index aadc551c..13bf024b 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -12,6 +12,7 @@ using Ocelot.Responses; using Shouldly; using System.Net.Http; + using Ocelot.Configuration; using TestStack.BDDfy; using Xunit; @@ -69,20 +70,16 @@ .BDDfy(); } - [Theory] - [InlineData("POST", "POST")] - [InlineData(null, "GET")] - [InlineData("", "GET")] - public void Should_map_downstream_reroute_method_to_downstream_request(string input, string expected) + [Fact] + public void Should_map_downstream_reroute_method_to_downstream_request() { this.Given(_ => GivenTheHttpContextContainsARequest()) - .And(_ => GivenTheDownstreamReRouteMethodIs(input)) .And(_ => GivenTheMapperWillReturnAMappedRequest()) .When(_ => WhenTheMiddlewareIsInvoked()) .Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest()) .And(_ => ThenTheDownstreamRequestIsStored()) .And(_ => ThenTheNextMiddlewareIsInvoked()) - .And(_ => ThenTheDownstreamRequestMethodIs(expected)) + .And(_ => ThenTheDownstreamRequestMethodIs("GET")) .BDDfy(); } @@ -98,11 +95,6 @@ .BDDfy(); } - private void GivenTheDownstreamReRouteMethodIs(string input) - { - _downstreamContext.DownstreamReRoute = new DownstreamReRouteBuilder().WithDownStreamHttpMethod(input).Build(); - } - private void ThenTheDownstreamRequestMethodIs(string expected) { _downstreamContext.DownstreamRequest.Method.ShouldBe(expected); @@ -120,7 +112,7 @@ _mappedRequest = new OkResponse(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk")); _requestMapper - .Setup(rm => rm.Map(It.IsAny())) + .Setup(rm => rm.Map(It.IsAny(), It.IsAny())) .ReturnsAsync(_mappedRequest); } @@ -129,7 +121,7 @@ _mappedRequest = new ErrorResponse(new UnmappableRequestError(new System.Exception("boooom!"))); _requestMapper - .Setup(rm => rm.Map(It.IsAny())) + .Setup(rm => rm.Map(It.IsAny(), It.IsAny())) .ReturnsAsync(_mappedRequest); } @@ -140,7 +132,7 @@ private void ThenTheContexRequestIsMappedToADownstreamRequest() { - _requestMapper.Verify(rm => rm.Map(_httpRequest.Object), Times.Once); + _requestMapper.Verify(rm => rm.Map(_httpRequest.Object, _downstreamContext.DownstreamReRoute), Times.Once); } private void ThenTheDownstreamRequestIsStored() diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index ac81a4d4..77091fc6 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -13,6 +13,8 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; using TestStack.BDDfy; using Xunit; @@ -27,6 +29,8 @@ private List> _inputHeaders = null; + private DownstreamReRoute _downstreamReRoute; + public RequestMapperTests() { _httpContext = new DefaultHttpContext(); @@ -82,6 +86,21 @@ .BDDfy(); } + [Theory] + [InlineData("", "GET")] + [InlineData(null, "GET")] + [InlineData("POST", "POST")] + public void Should_use_downstream_reroute_method_if_set(string input, string expected) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheDownstreamReRouteMethodIs(input)) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasMethod(expected)) + .BDDfy(); + } + [Fact] public void Should_map_all_headers() { @@ -154,16 +173,6 @@ .BDDfy(); } - private void GivenTheInputRequestHasNoContentLength() - { - _inputRequest.ContentLength = null; - } - - private void GivenTheInputRequestHasNoContentType() - { - _inputRequest.ContentType = null; - } - [Fact] public void Should_map_content_headers() { @@ -212,6 +221,22 @@ .BDDfy(); } + private void GivenTheDownstreamReRouteMethodIs(string input) + { + _downstreamReRoute = new DownstreamReRouteBuilder().WithDownStreamHttpMethod(input).Build(); + } + + private void GivenTheInputRequestHasNoContentLength() + { + _inputRequest.ContentLength = null; + } + + private void GivenTheInputRequestHasNoContentType() + { + _inputRequest.ContentType = null; + } + + private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() { _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); @@ -380,7 +405,7 @@ private async Task WhenMapped() { - _mappedRequest = await _requestMapper.Map(_inputRequest); + _mappedRequest = await _requestMapper.Map(_inputRequest, _downstreamReRoute); } private void ThenNoErrorIsReturned() From 7e807208812ffcc9cfb7929b2f2490b606adf39c Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 9 Feb 2020 16:15:39 +0000 Subject: [PATCH 5/6] acceptance tests --- test/Ocelot.AcceptanceTests/MethodTests.cs | 158 +++++++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 12 ++ 2 files changed, 170 insertions(+) create mode 100644 test/Ocelot.AcceptanceTests/MethodTests.cs diff --git a/test/Ocelot.AcceptanceTests/MethodTests.cs b/test/Ocelot.AcceptanceTests/MethodTests.cs new file mode 100644 index 00000000..abfbfecf --- /dev/null +++ b/test/Ocelot.AcceptanceTests/MethodTests.cs @@ -0,0 +1,158 @@ +namespace Ocelot.AcceptanceTests +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Net.Http; + using TestStack.BDDfy; + using Xunit; + + public class MethodTests : IDisposable + { + private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; + + public MethodTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_when_get_converted_to_post() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53171, + }, + }, + DownstreamHttpMethod = "POST", + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53171/", "/", "POST")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_get_converted_to_post_with_content() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53271, + }, + }, + DownstreamHttpMethod = "POST", + }, + }, + }; + + const string expected = "here is some content"; + var httpContent = new StringContent(expected); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53271/", "/", "POST")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", httpContent)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(_ => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_get_converted_to_get_with_content() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Post" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53272, + }, + }, + DownstreamHttpMethod = "GET", + }, + }, + }; + + const string expected = "here is some content"; + var httpContent = new StringContent(expected); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53272/", "/", "GET")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/", httpContent)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(_ => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string expected) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + if (context.Request.Method == expected) + { + context.Response.StatusCode = 200; + var reader = new StreamReader(context.Request.Body); + var body = await reader.ReadToEndAsync(); + await context.Response.WriteAsync(body); + } + else + { + context.Response.StatusCode = 500; + } + }); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 9bfb2091..60a770f7 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -901,6 +901,18 @@ _response = _ocelotClient.GetAsync(url).Result; } + public void WhenIGetUrlOnTheApiGateway(string url, HttpContent content) + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url) {Content = content}; + _response = _ocelotClient.SendAsync(httpRequestMessage).Result; + } + + public void WhenIPostUrlOnTheApiGateway(string url, HttpContent content) + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url) { Content = content }; + _response = _ocelotClient.SendAsync(httpRequestMessage).Result; + } + public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) { var request = _ocelotServer.CreateRequest(url); From 89b2decf5042afe0cc884e0a0f6caaae5e77ca9a Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 9 Feb 2020 16:52:59 +0000 Subject: [PATCH 6/6] added docs for http method transformation --- docs/features/configuration.rst | 1 + docs/features/methodtransformation.rst | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docs/features/methodtransformation.rst diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 7983af39..fe399740 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -24,6 +24,7 @@ Here is an example ReRoute configuration, You don't need to set all of these thi "UpstreamHttpMethod": [ "Get" ], + "DownstreamHttpMethod": "". "AddHeadersToRequest": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, diff --git a/docs/features/methodtransformation.rst b/docs/features/methodtransformation.rst new file mode 100644 index 00000000..b94ebce4 --- /dev/null +++ b/docs/features/methodtransformation.rst @@ -0,0 +1,28 @@ +HTTP Method Transformation +========================== + +Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service. + +This achieved by setting the following ReRoute configuration: + +.. code-block:: json + +{ + "DownstreamPathTemplate": "/{url}", + "UpstreamPathTemplate": "/{url}", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamHttpMethod": "POST", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 53271 + } + ], +} + +The key property here is DownstreamHttpMethod which is set as POST and the ReRoute will only match on GET as set by UpstreamHttpMethod. + +This feature can be useful when interacting with downstream apis that only support POST and you want to present some kind of RESTful interface. \ No newline at end of file