mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 18:32:51 +08:00
downstreambaseurl placeholder for multiple location value redirects (#207)
This commit is contained in:
parent
d0eee70c46
commit
f572d1b0ca
@ -39,9 +39,10 @@ Add the following to a ReRoute in configuration.json in order to replace http://
|
|||||||
Placeholders
|
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.
|
{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
|
Handling 302 Redirects
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -67,4 +68,30 @@ or you could use the BaseUrl placeholder.
|
|||||||
"AllowAutoRedirect": false,
|
"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,24 +1,55 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Infrastructure.Extensions;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Headers
|
namespace Ocelot.Headers
|
||||||
{
|
{
|
||||||
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
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)
|
foreach (var f in fAndRs)
|
||||||
{
|
{
|
||||||
|
//if the response headers contain a matching find and replace
|
||||||
if(response.Headers.TryGetValues(f.Key, out var values))
|
if(response.Headers.TryGetValues(f.Key, out var values))
|
||||||
|
{
|
||||||
|
//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);
|
var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace);
|
||||||
response.Headers.Remove(f.Key);
|
response.Headers.Remove(f.Key);
|
||||||
response.Headers.Add(f.Key, replaced);
|
response.Headers.Add(f.Key, replaced);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new OkResponse();
|
return new OkResponse();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ namespace Ocelot.Headers
|
|||||||
{
|
{
|
||||||
public interface IHttpResponseHeaderReplacer
|
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;
|
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;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string LastCharAsForwardSlash(this string source)
|
||||||
|
{
|
||||||
|
if(source.EndsWith('/'))
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{source}/";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -126,6 +126,42 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.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)
|
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()
|
public void should_call_pre_and_post_header_transforms()
|
||||||
{
|
{
|
||||||
this.Given(x => GivenTheFollowingRequest())
|
this.Given(x => GivenTheFollowingRequest())
|
||||||
|
.And(x => GivenTheDownstreamRequestIs())
|
||||||
.And(x => GivenTheReRouteHasPreFindAndReplaceSetUp())
|
.And(x => GivenTheReRouteHasPreFindAndReplaceSetUp())
|
||||||
.And(x => GivenTheHttpResponseMessageIs())
|
.And(x => GivenTheHttpResponseMessageIs())
|
||||||
.When(x => WhenICallTheMiddleware())
|
.When(x => WhenICallTheMiddleware())
|
||||||
@ -45,6 +46,13 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
.BDDfy();
|
.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()
|
private void GivenTheHttpResponseMessageIs()
|
||||||
{
|
{
|
||||||
var httpResponseMessage = new HttpResponseMessage();
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
@ -68,7 +76,7 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
|
|
||||||
private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()
|
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()
|
private void GivenTheFollowingRequest()
|
||||||
|
@ -16,6 +16,7 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
private HttpResponseHeaderReplacer _replacer;
|
private HttpResponseHeaderReplacer _replacer;
|
||||||
private List<HeaderFindAndReplace> _headerFindAndReplaces;
|
private List<HeaderFindAndReplace> _headerFindAndReplaces;
|
||||||
private Response _result;
|
private Response _result;
|
||||||
|
private HttpRequestMessage _request;
|
||||||
|
|
||||||
public HttpResponseHeaderReplacerTests()
|
public HttpResponseHeaderReplacerTests()
|
||||||
{
|
{
|
||||||
@ -52,6 +53,143 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
.BDDfy();
|
.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()
|
private void ThenTheHeadersAreNotReplaced()
|
||||||
{
|
{
|
||||||
@ -75,7 +213,13 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
|
|
||||||
private void WhenICallTheReplacer()
|
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()
|
private void ThenTheHeadersAreReplaced()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user