mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-19 23:32:49 +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
|
||||
^^^^^^^^^^^^
|
||||
|
||||
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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user