mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-01 00:55:27 +08:00 
			
		
		
		
	downstreambaseurl placeholder for multiple location value redirects (#207)
This commit is contained in:
		| @@ -39,9 +39,10 @@ Add the following to a ReRoute in configuration.json in order to replace http:// | ||||
| Placeholders | ||||
| ^^^^^^^^^^^^ | ||||
|  | ||||
| Ocelot allows placeholders that can be used in header transformation. At the moment there is only one placeholder. | ||||
| Ocelot allows placeholders that can be used in header transformation. | ||||
|  | ||||
| {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. | ||||
| {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. | ||||
|  | ||||
| Handling 302 Redirects | ||||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||||
| @@ -67,4 +68,30 @@ or you could use the BaseUrl placeholder. | ||||
|         "AllowAutoRedirect": false, | ||||
|     }, | ||||
|  | ||||
| Ocelot will not try and replace the location header returned by the downstream service with its own URL. | ||||
| finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. | ||||
|  | ||||
| .. code-block:: json | ||||
|  | ||||
|     "DownstreamHeaderTransform": { | ||||
|         "Location": "{DownstreamBaseUrl}, {BaseUrl}" | ||||
|     }, | ||||
|      "HttpHandlerOptions": { | ||||
|         "AllowAutoRedirect": false, | ||||
|     }, | ||||
|  | ||||
| Future | ||||
| ^^^^^^ | ||||
|  | ||||
| Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. | ||||
| It would also be nice if it could multi find and replace e.g.  | ||||
|  | ||||
| .. code-block:: json | ||||
|  | ||||
|     "DownstreamHeaderTransform": { | ||||
|         "Location": "[{one,one},{two,two}" | ||||
|     }, | ||||
|      "HttpHandlerOptions": { | ||||
|         "AllowAutoRedirect": false, | ||||
|     }, | ||||
|  | ||||
| If anyone wants to have a go at this please help yourself!! | ||||
| @@ -1,22 +1,53 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Net.Http; | ||||
| using Ocelot.Configuration; | ||||
| using Ocelot.Infrastructure.Extensions; | ||||
| using Ocelot.Responses; | ||||
|  | ||||
| namespace Ocelot.Headers | ||||
| { | ||||
|     public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer | ||||
|     { | ||||
|         public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs) | ||||
|         private Dictionary<string, Func<HttpRequestMessage, string>> _placeholders; | ||||
|  | ||||
|         public HttpResponseHeaderReplacer() | ||||
|         { | ||||
|             _placeholders = new Dictionary<string, Func<HttpRequestMessage, string>>(); | ||||
|             _placeholders.Add("{DownstreamBaseUrl}", x => { | ||||
|                 var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}"; | ||||
|  | ||||
|                 if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443) | ||||
|                 { | ||||
|                     downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}"; | ||||
|                 } | ||||
|  | ||||
|                 return $"{downstreamUrl}/"; | ||||
|             }); | ||||
|         } | ||||
|         public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage httpRequestMessage) | ||||
|         { | ||||
|             foreach (var f in fAndRs) | ||||
|             { | ||||
|                 //if the response headers contain a matching find and replace | ||||
|                 if(response.Headers.TryGetValues(f.Key, out var values)) | ||||
|                 { | ||||
|                     var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); | ||||
|                     response.Headers.Remove(f.Key); | ||||
|                     response.Headers.Add(f.Key, replaced); | ||||
|                     //check to see if it is a placeholder in the find... | ||||
|                     if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder)) | ||||
|                     { | ||||
|                         //if it is we need to get the value of the placeholder | ||||
|                         var find = replacePlaceholder(httpRequestMessage); | ||||
|                         var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash()); | ||||
|                         response.Headers.Remove(f.Key); | ||||
|                         response.Headers.Add(f.Key, replaced); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); | ||||
|                         response.Headers.Remove(f.Key); | ||||
|                         response.Headers.Add(f.Key, replaced); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,6 @@ namespace Ocelot.Headers | ||||
| { | ||||
|     public interface IHttpResponseHeaderReplacer | ||||
|     { | ||||
|         Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs); | ||||
|         Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage httpRequestMessage); | ||||
|     } | ||||
| } | ||||
| @@ -36,7 +36,7 @@ namespace Ocelot.Headers.Middleware | ||||
|  | ||||
|             var postFAndRs = this.DownstreamRoute.ReRoute.DownstreamHeadersFindAndReplace; | ||||
|  | ||||
|             _postReplacer.Replace(HttpResponseMessage, postFAndRs); | ||||
|             _postReplacer.Replace(HttpResponseMessage, postFAndRs, DownstreamRequest); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -20,5 +20,14 @@ namespace Ocelot.Infrastructure.Extensions | ||||
|             return s; | ||||
|         } | ||||
|  | ||||
|         public static string LastCharAsForwardSlash(this string source) | ||||
|         { | ||||
|             if(source.EndsWith('/')) | ||||
|             { | ||||
|                 return source; | ||||
|             } | ||||
|  | ||||
|             return $"{source}/"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -126,6 +126,42 @@ namespace Ocelot.AcceptanceTests | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_fix_issue_205() | ||||
|         {    | ||||
|             var configuration = new FileConfiguration | ||||
|             { | ||||
|                 ReRoutes = new List<FileReRoute> | ||||
|                     { | ||||
|                         new FileReRoute | ||||
|                         { | ||||
|                             DownstreamPathTemplate = "/", | ||||
|                             DownstreamScheme = "http", | ||||
|                             DownstreamHost = "localhost", | ||||
|                             DownstreamPort = 6773, | ||||
|                             UpstreamPathTemplate = "/", | ||||
|                             UpstreamHttpMethod = new List<string> { "Get" }, | ||||
|                             DownstreamHeaderTransform = new Dictionary<string,string> | ||||
|                             { | ||||
|                                 {"Location", "{DownstreamBaseUrl}, {BaseUrl}"} | ||||
|                             }, | ||||
|                             HttpHandlerOptions = new FileHttpHandlerOptions | ||||
|                             { | ||||
|                                 AllowAutoRedirect = false | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|             }; | ||||
|  | ||||
|             this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) | ||||
|                 .And(x => _steps.GivenThereIsAConfiguration(configuration)) | ||||
|                 .And(x => _steps.GivenOcelotIsRunning()) | ||||
|                 .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||
|                 .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) | ||||
|                 .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey) | ||||
|         { | ||||
|   | ||||
| @@ -37,6 +37,7 @@ namespace Ocelot.UnitTests.Headers | ||||
|         public void should_call_pre_and_post_header_transforms() | ||||
|         { | ||||
|             this.Given(x => GivenTheFollowingRequest()) | ||||
|                 .And(x => GivenTheDownstreamRequestIs()) | ||||
|                 .And(x => GivenTheReRouteHasPreFindAndReplaceSetUp()) | ||||
|                 .And(x => GivenTheHttpResponseMessageIs()) | ||||
|                 .When(x => WhenICallTheMiddleware()) | ||||
| @@ -45,6 +46,13 @@ namespace Ocelot.UnitTests.Headers | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         private void GivenTheDownstreamRequestIs() | ||||
|         { | ||||
|             var request = new HttpRequestMessage(); | ||||
|             var response = new OkResponse<HttpRequestMessage>(request); | ||||
|             ScopedRepository.Setup(x => x.Get<HttpRequestMessage>("DownstreamRequest")).Returns(response); | ||||
|         } | ||||
|  | ||||
|         private void GivenTheHttpResponseMessageIs() | ||||
|         { | ||||
|             var httpResponseMessage = new HttpResponseMessage(); | ||||
| @@ -68,7 +76,7 @@ namespace Ocelot.UnitTests.Headers | ||||
|  | ||||
|         private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() | ||||
|         { | ||||
|             _postReplacer.Verify(x => x.Replace(It.IsAny<HttpResponseMessage>(), It.IsAny<List<HeaderFindAndReplace>>()), Times.Once); | ||||
|             _postReplacer.Verify(x => x.Replace(It.IsAny<HttpResponseMessage>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<HttpRequestMessage>()), Times.Once); | ||||
|         } | ||||
|  | ||||
|         private void GivenTheFollowingRequest() | ||||
|   | ||||
| @@ -16,6 +16,7 @@ namespace Ocelot.UnitTests.Headers | ||||
|         private HttpResponseHeaderReplacer _replacer; | ||||
|         private List<HeaderFindAndReplace> _headerFindAndReplaces; | ||||
|         private Response _result; | ||||
|         private HttpRequestMessage _request; | ||||
|  | ||||
|         public HttpResponseHeaderReplacerTests() | ||||
|         { | ||||
| @@ -52,6 +53,143 @@ namespace Ocelot.UnitTests.Headers | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_replace_downstream_base_url_with_ocelot_base_url() | ||||
|         { | ||||
|             var downstreamUrl = "http://downstream.com/"; | ||||
|  | ||||
|             var request = new HttpRequestMessage(); | ||||
|             request.RequestUri = new System.Uri(downstreamUrl); | ||||
|  | ||||
|             var response = new HttpResponseMessage(); | ||||
|             response.Headers.Add("Location", downstreamUrl); | ||||
|  | ||||
|             var fAndRs = new List<HeaderFindAndReplace>(); | ||||
|             fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); | ||||
|  | ||||
|             this.Given(x => GivenTheHttpResponse(response)) | ||||
|                 .And(x => GivenTheRequestIs(request)) | ||||
|                 .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) | ||||
|                 .When(x => WhenICallTheReplacer()) | ||||
|                 .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() | ||||
|         { | ||||
|             var downstreamUrl = "http://downstream.com/"; | ||||
|  | ||||
|             var request = new HttpRequestMessage(); | ||||
|             request.RequestUri = new System.Uri(downstreamUrl); | ||||
|  | ||||
|             var response = new HttpResponseMessage(); | ||||
|             response.Headers.Add("Location", downstreamUrl); | ||||
|  | ||||
|             var fAndRs = new List<HeaderFindAndReplace>(); | ||||
|             fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); | ||||
|  | ||||
|             this.Given(x => GivenTheHttpResponse(response)) | ||||
|                 .And(x => GivenTheRequestIs(request)) | ||||
|                 .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) | ||||
|                 .When(x => WhenICallTheReplacer()) | ||||
|                 .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() | ||||
|         { | ||||
|             var downstreamUrl = "http://downstream.com/test/product"; | ||||
|  | ||||
|             var request = new HttpRequestMessage(); | ||||
|             request.RequestUri = new System.Uri(downstreamUrl); | ||||
|  | ||||
|             var response = new HttpResponseMessage(); | ||||
|             response.Headers.Add("Location", downstreamUrl); | ||||
|  | ||||
|             var fAndRs = new List<HeaderFindAndReplace>(); | ||||
|             fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); | ||||
|  | ||||
|             this.Given(x => GivenTheHttpResponse(response)) | ||||
|                 .And(x => GivenTheRequestIs(request)) | ||||
|                 .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) | ||||
|                 .When(x => WhenICallTheReplacer()) | ||||
|                 .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() | ||||
|         { | ||||
|             var downstreamUrl = "http://downstream.com/test/product"; | ||||
|  | ||||
|             var request = new HttpRequestMessage(); | ||||
|             request.RequestUri = new System.Uri(downstreamUrl); | ||||
|  | ||||
|             var response = new HttpResponseMessage(); | ||||
|             response.Headers.Add("Location", downstreamUrl); | ||||
|  | ||||
|             var fAndRs = new List<HeaderFindAndReplace>(); | ||||
|             fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); | ||||
|  | ||||
|             this.Given(x => GivenTheHttpResponse(response)) | ||||
|                 .And(x => GivenTheRequestIs(request)) | ||||
|                 .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) | ||||
|                 .When(x => WhenICallTheReplacer()) | ||||
|                 .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/test/product")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() | ||||
|         { | ||||
|             var downstreamUrl = "http://downstream.com:123/test/product"; | ||||
|  | ||||
|             var request = new HttpRequestMessage(); | ||||
|             request.RequestUri = new System.Uri(downstreamUrl); | ||||
|  | ||||
|             var response = new HttpResponseMessage(); | ||||
|             response.Headers.Add("Location", downstreamUrl); | ||||
|  | ||||
|             var fAndRs = new List<HeaderFindAndReplace>(); | ||||
|             fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); | ||||
|  | ||||
|             this.Given(x => GivenTheHttpResponse(response)) | ||||
|                 .And(x => GivenTheRequestIs(request)) | ||||
|                 .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) | ||||
|                 .When(x => WhenICallTheReplacer()) | ||||
|                 .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() | ||||
|         { | ||||
|             var downstreamUrl = "http://downstream.com:123/test/product"; | ||||
|  | ||||
|             var request = new HttpRequestMessage(); | ||||
|             request.RequestUri = new System.Uri(downstreamUrl); | ||||
|  | ||||
|             var response = new HttpResponseMessage(); | ||||
|             response.Headers.Add("Location", downstreamUrl); | ||||
|  | ||||
|             var fAndRs = new List<HeaderFindAndReplace>(); | ||||
|             fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)); | ||||
|  | ||||
|             this.Given(x => GivenTheHttpResponse(response)) | ||||
|                 .And(x => GivenTheRequestIs(request)) | ||||
|                 .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) | ||||
|                 .When(x => WhenICallTheReplacer()) | ||||
|                 .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:321/test/product")) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         private void GivenTheRequestIs(HttpRequestMessage request) | ||||
|         { | ||||
|             _request = request; | ||||
|         } | ||||
|  | ||||
|         private void ThenTheHeadersAreNotReplaced() | ||||
|         { | ||||
| @@ -75,7 +213,13 @@ namespace Ocelot.UnitTests.Headers | ||||
|  | ||||
|         private void WhenICallTheReplacer() | ||||
|         { | ||||
|             _result = _replacer.Replace(_response, _headerFindAndReplaces); | ||||
|             _result = _replacer.Replace(_response, _headerFindAndReplaces, _request); | ||||
|         } | ||||
|  | ||||
|         private void ThenTheHeaderShouldBe(string key, string value) | ||||
|         { | ||||
|             var test = _response.Headers.GetValues(key); | ||||
|             test.First().ShouldBe(value); | ||||
|         } | ||||
|  | ||||
|          private void ThenTheHeadersAreReplaced() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tom Pallister
					Tom Pallister