Feature/#623 (#632)

* #630 only set status code if response hasnt started, otherwise exception

* #623 made {RemoteIpAddress} available as placeholder so you can do x-forwarded-for

* #623 local address different on mac, windows and linux for integration test
This commit is contained in:
Tom Pallister 2018-09-24 08:22:44 +01:00 committed by GitHub
parent 54cdc74293
commit aa14b2f877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 487 additions and 112 deletions

View File

@ -81,6 +81,7 @@ Placeholders
Ocelot allows placeholders that can be used in header transformation. Ocelot allows placeholders that can be used in header transformation.
{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP.
{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. {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.
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. {TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
@ -120,6 +121,17 @@ finally if you are using a load balancer with Ocelot you will get multiple downs
"AllowAutoRedirect": false, "AllowAutoRedirect": false,
}, },
X-Forwarded-For
^^^^^^^^^^^^^^^
An example of using {RemoteIpAddress} placeholder...
.. code-block:: json
"UpstreamHeaderTransform": {
"X-Forwarded-For": "{RemoteIpAddress}"
}
Future Future
^^^^^^ ^^^^^^

View File

@ -1,22 +1,29 @@
using System.Collections.Generic; namespace Ocelot.Headers
{
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Infrastructure;
using Logging;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Middleware;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
namespace Ocelot.Headers
{
public class AddHeadersToRequest : IAddHeadersToRequest public class AddHeadersToRequest : IAddHeadersToRequest
{ {
private readonly IClaimsParser _claimsParser; private readonly IClaimsParser _claimsParser;
private readonly IPlaceholders _placeholders;
private readonly IOcelotLogger _logger;
public AddHeadersToRequest(IClaimsParser claimsParser) public AddHeadersToRequest(IClaimsParser claimsParser, IPlaceholders placeholders, IOcelotLoggerFactory factory)
{ {
_logger = factory.CreateLogger<AddHeadersToRequest>();
_claimsParser = claimsParser; _claimsParser = claimsParser;
_placeholders = placeholders;
} }
public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest) public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest)
@ -46,6 +53,7 @@ namespace Ocelot.Headers
public void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context) public void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context)
{ {
var requestHeader = context.Request.Headers; var requestHeader = context.Request.Headers;
foreach (var header in headers) foreach (var header in headers)
{ {
if (requestHeader.ContainsKey(header.Key)) if (requestHeader.ContainsKey(header.Key))
@ -53,8 +61,23 @@ namespace Ocelot.Headers
requestHeader.Remove(header.Key); requestHeader.Remove(header.Key);
} }
if (header.Value.StartsWith("{") && header.Value.EndsWith("}"))
{
var value = _placeholders.Get(header.Value);
if (value.IsError)
{
_logger.LogWarning($"Unable to add header to response {header.Key}: {header.Value}");
continue;
}
requestHeader.Add(header.Key, new StringValues(value.Data));
}
else
{
requestHeader.Add(header.Key, header.Value); requestHeader.Add(header.Key, header.Value);
} }
} }
} }
} }
}

View File

@ -1,15 +1,13 @@
namespace Ocelot.Headers
{
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Headers
{
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
{ {
private readonly IPlaceholders _placeholders; private readonly IPlaceholders _placeholders;
@ -19,8 +17,11 @@ namespace Ocelot.Headers
_placeholders = placeholders; _placeholders = placeholders;
} }
public Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request) public Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs)
{ {
var response = context.DownstreamResponse;
var request = context.DownstreamRequest;
foreach (var f in fAndRs) foreach (var f in fAndRs)
{ {
var dict = response.Headers.ToDictionary(x => x.Key); var dict = response.Headers.ToDictionary(x => x.Key);

View File

@ -1,14 +1,12 @@
namespace Ocelot.Headers
{
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Headers
{
public interface IHttpResponseHeaderReplacer public interface IHttpResponseHeaderReplacer
{ {
Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest httpRequestMessage); Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs);
} }
} }

View File

@ -45,7 +45,7 @@ namespace Ocelot.Headers.Middleware
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); _postReplacer.Replace(context, postFAndRs);
_addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
} }

View File

@ -1,47 +1,38 @@
namespace Ocelot.Infrastructure
{
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Infrastructure
{
public class Placeholders : IPlaceholders public class Placeholders : IPlaceholders
{ {
private readonly Dictionary<string, Func<Response<string>>> _placeholders; private readonly Dictionary<string, Func<Response<string>>> _placeholders;
private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders; private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders;
private readonly IBaseUrlFinder _finder; private readonly IBaseUrlFinder _finder;
private readonly IRequestScopedDataRepository _repo; private readonly IRequestScopedDataRepository _repo;
private readonly IHttpContextAccessor _httpContextAccessor;
public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo) public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo, IHttpContextAccessor httpContextAccessor)
{ {
_repo = repo; _repo = repo;
_httpContextAccessor = httpContextAccessor;
_finder = finder; _finder = finder;
_placeholders = new Dictionary<string, Func<Response<string>>>(); _placeholders = new Dictionary<string, Func<Response<string>>>
_placeholders.Add("{BaseUrl}", () => new OkResponse<string>(_finder.Find()));
_placeholders.Add("{TraceId}", () => {
var traceId = _repo.Get<string>("TraceId");
if(traceId.IsError)
{ {
return new ErrorResponse<string>(traceId.Errors); { "{BaseUrl}", GetBaseUrl() },
} { "{TraceId}", GetTraceId() },
{ "{RemoteIpAddress}", GetRemoteIpAddress() }
return new OkResponse<string>(traceId.Data); };
});
_requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>(); _requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>
_requestPlaceholders.Add("{DownstreamBaseUrl}", x => {
var downstreamUrl = $"{x.Scheme}://{x.Host}";
if(x.Port != 80 && x.Port != 443)
{ {
downstreamUrl = $"{downstreamUrl}:{x.Port}"; { "{DownstreamBaseUrl}", GetDownstreamBaseUrl() }
} };
return $"{downstreamUrl}/";
});
} }
public Response<string> Get(string key) public Response<string> Get(string key)
@ -67,5 +58,56 @@ namespace Ocelot.Infrastructure
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key)); return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
} }
private Func<Response<string>> GetRemoteIpAddress()
{
return () =>
{
// this can blow up so adding try catch and return error
try
{
var remoteIdAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
return new OkResponse<string>(remoteIdAddress);
}
catch (Exception e)
{
return new ErrorResponse<string>(new CouldNotFindPlaceholderError("{RemoteIpAddress}"));
}
};
}
private Func<DownstreamRequest, string> GetDownstreamBaseUrl()
{
return x =>
{
var downstreamUrl = $"{x.Scheme}://{x.Host}";
if (x.Port != 80 && x.Port != 443)
{
downstreamUrl = $"{downstreamUrl}:{x.Port}";
}
return $"{downstreamUrl}/";
};
}
private Func<Response<string>> GetTraceId()
{
return () =>
{
var traceId = _repo.Get<string>("TraceId");
if (traceId.IsError)
{
return new ErrorResponse<string>(traceId.Errors);
}
return new OkResponse<string>(traceId.Data);
};
}
private Func<Response<string>> GetBaseUrl()
{
return () => new OkResponse<string>(_finder.Find());
}
} }
} }

View File

@ -4,6 +4,7 @@ namespace Ocelot.AcceptanceTests
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;

View File

@ -0,0 +1,199 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Ocelot.IntegrationTests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using System.Linq;
using System.Net;
public class HeaderTests : IDisposable
{
private readonly HttpClient _httpClient;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private IWebHost _downstreamBuilder;
private HttpResponseMessage _response;
public HeaderTests()
{
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5005";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
}
[Fact]
public void should_pass_remote_ip_address_if_as_x_forwarded_for_header()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 6773,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHeaderTransform = new Dictionary<string,string>
{
{"X-Forwarded-For", "{RemoteIpAddress}"}
},
HttpHandlerOptions = new FileHttpHandlerOptions
{
AllowAutoRedirect = false
}
}
}
};
this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:6773", 200, "X-Forwarded-For"))
.And(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenXForwardedForIsSet())
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string headerKey)
{
_downstreamBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if (context.Request.Headers.TryGetValue(headerKey, out var values))
{
var result = values.First();
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(result);
}
});
})
.Build();
_downstreamBuilder.Start();
}
private void GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
config.AddJsonFile("ocelot.json", false, false);
config.AddEnvironmentVariables();
})
.ConfigureServices(x =>
{
x.AddOcelot();
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
_builder = _webHostBuilder.Build();
_builder.Start();
}
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{
var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
var text = File.ReadAllText(configurationPath);
configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
text = File.ReadAllText(configurationPath);
}
public async Task WhenIGetUrlOnTheApiGateway(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
_response = await _httpClient.SendAsync(request);
}
private void ThenTheStatusCodeShouldBe(HttpStatusCode code)
{
_response.StatusCode.ShouldBe(code);
}
private void ThenXForwardedForIsSet()
{
var windowsOrMac = "::1";
var linux = "127.0.0.1";
var header = _response.Content.ReadAsStringAsync().Result;
bool passed = false;
if(header == windowsOrMac || header == linux)
{
passed = true;
}
passed.ShouldBeTrue();
}
public void Dispose()
{
_builder?.Dispose();
_httpClient?.Dispose();
_downstreamBuilder?.Dispose();
}
}
}

View File

@ -70,6 +70,28 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_create_with_add_headers_to_request()
{
const string key = "X-Forwarded-For";
const string value = "{RemoteIpAddress}";
var reRoute = new FileReRoute
{
UpstreamHeaderTransform = new Dictionary<string, string>
{
{key, value},
}
};
var expected = new AddHeader(key, value);
this.Given(x => GivenTheReRoute(reRoute))
.When(x => WhenICreate())
.Then(x => ThenTheFollowingAddHeaderToUpstreamIsReturned(expected))
.BDDfy();
}
[Fact] [Fact]
public void should_use_base_url_placeholder() public void should_use_base_url_placeholder()
{ {

View File

@ -15,6 +15,9 @@ using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
using Ocelot.Infrastructure;
using Ocelot.Logging;
public class AddHeadersToRequestClaimToThingTests public class AddHeadersToRequestClaimToThingTests
{ {
private readonly AddHeadersToRequest _addHeadersToRequest; private readonly AddHeadersToRequest _addHeadersToRequest;
@ -24,11 +27,15 @@ namespace Ocelot.UnitTests.Headers
private List<ClaimToThing> _configuration; private List<ClaimToThing> _configuration;
private Response _result; private Response _result;
private Response<string> _claimValue; private Response<string> _claimValue;
private Mock<IPlaceholders> _placeholders;
private Mock<IOcelotLoggerFactory> _factory;
public AddHeadersToRequestClaimToThingTests() public AddHeadersToRequestClaimToThingTests()
{ {
_parser = new Mock<IClaimsParser>(); _parser = new Mock<IClaimsParser>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object); _placeholders = new Mock<IPlaceholders>();
_factory = new Mock<IOcelotLoggerFactory>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object, _placeholders.Object, _factory.Object);
_downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }

View File

@ -1,23 +1,56 @@
using Microsoft.AspNetCore.Http; namespace Ocelot.UnitTests.Headers
{
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Microsoft.AspNetCore.Http;
using Moq; using Moq;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Responder;
using Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.UnitTests.Headers
{
public class AddHeadersToRequestPlainTests public class AddHeadersToRequestPlainTests
{ {
private readonly AddHeadersToRequest _addHeadersToRequest; private readonly AddHeadersToRequest _addHeadersToRequest;
private HttpContext _context; private HttpContext _context;
private AddHeader _addedHeader; private AddHeader _addedHeader;
private readonly Mock<IPlaceholders> _placeholders;
private Mock<IOcelotLoggerFactory> _factory;
private readonly Mock<IOcelotLogger> _logger;
public AddHeadersToRequestPlainTests() public AddHeadersToRequestPlainTests()
{ {
_addHeadersToRequest = new AddHeadersToRequest(Mock.Of<IClaimsParser>()); _placeholders = new Mock<IPlaceholders>();
_factory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_factory.Setup(x => x.CreateLogger<AddHeadersToRequest>()).Returns(_logger.Object);
_addHeadersToRequest = new AddHeadersToRequest(Mock.Of<IClaimsParser>(), _placeholders.Object, _factory.Object);
}
[Fact]
public void should_log_error_if_cannot_find_placeholder()
{
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError()));
this.Given(_ => GivenHttpRequestWithoutHeaders())
.When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}"))
.Then(_ => ThenAnErrorIsLogged("X-Forwarded-For", "{RemoteIdAddress}"))
.BDDfy();
}
[Fact]
public void should_add_placeholder_to_downstream_request()
{
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new OkResponse<string>("replaced"));
this.Given(_ => GivenHttpRequestWithoutHeaders())
.When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}"))
.Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders("replaced"))
.BDDfy();
} }
[Fact] [Fact]
@ -38,9 +71,23 @@ namespace Ocelot.UnitTests.Headers
.BDDfy(); .BDDfy();
} }
private void ThenAnErrorIsLogged(string key, string value)
{
_logger.Verify(x => x.LogWarning($"Unable to add header to response {key}: {value}"), Times.Once);
}
private void GivenHttpRequestWithoutHeaders() private void GivenHttpRequestWithoutHeaders()
{ {
_context = new DefaultHttpContext(); _context = new DefaultHttpContext
{
Request =
{
Headers =
{
}
}
};
} }
private void GivenHttpRequestWithHeader(string headerKey, string headerValue) private void GivenHttpRequestWithHeader(string headerKey, string headerValue)
@ -71,5 +118,12 @@ namespace Ocelot.UnitTests.Headers
value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null."); value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null.");
value.ToString().ShouldBe(_addedHeader.Value); value.ToString().ShouldBe(_addedHeader.Value);
} }
private void ThenTheHeaderGetsTakenOverToTheRequestHeaders(string expected)
{
var requestHeaders = _context.Request.Headers;
var value = requestHeaders[_addedHeader.Key];
value.ToString().ShouldBe(expected);
}
} }
} }

View File

@ -109,7 +109,7 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()
{ {
_postReplacer.Verify(x => x.Replace(It.IsAny<DownstreamResponse>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<DownstreamRequest>()), Times.Once); _postReplacer.Verify(x => x.Replace(It.IsAny<DownstreamContext>(), It.IsAny<List<HeaderFindAndReplace>>()), Times.Once);
} }
private void GivenTheFollowingRequest() private void GivenTheFollowingRequest()

View File

@ -1,3 +1,10 @@
namespace Ocelot.UnitTests.Headers
{
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Request.Middleware;
using Xunit; using Xunit;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -9,14 +16,7 @@ using Ocelot.Responses;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Moq; using Moq;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers
{
public class HttpResponseHeaderReplacerTests public class HttpResponseHeaderReplacerTests
{ {
private DownstreamResponse _response; private DownstreamResponse _response;
@ -27,12 +27,14 @@ namespace Ocelot.UnitTests.Headers
private DownstreamRequest _request; private DownstreamRequest _request;
private Mock<IBaseUrlFinder> _finder; private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo; private Mock<IRequestScopedDataRepository> _repo;
private Mock<IHttpContextAccessor> _accessor;
public HttpResponseHeaderReplacerTests() public HttpResponseHeaderReplacerTests()
{ {
_accessor = new Mock<IHttpContextAccessor>();
_repo = new Mock<IRequestScopedDataRepository>(); _repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>(); _finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object); _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object);
_replacer = new HttpResponseHeaderReplacer(_placeholders); _replacer = new HttpResponseHeaderReplacer(_placeholders);
} }
@ -261,7 +263,8 @@ namespace Ocelot.UnitTests.Headers
private void WhenICallTheReplacer() private void WhenICallTheReplacer()
{ {
_result = _replacer.Replace(_response, _headerFindAndReplaces, _request); var context = new DownstreamContext(new DefaultHttpContext()) {DownstreamResponse = _response, DownstreamRequest = _request};
_result = _replacer.Replace(context, _headerFindAndReplaces);
} }
private void ThenTheHeaderShouldBe(string key, string value) private void ThenTheHeaderShouldBe(string key, string value)

View File

@ -1,4 +1,8 @@
namespace Ocelot.UnitTests.Infrastructure
{
using Microsoft.AspNetCore.Http;
using System; using System;
using System.Net;
using System.Net.Http; using System.Net.Http;
using Moq; using Moq;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
@ -9,19 +13,19 @@ using Ocelot.Responses;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
namespace Ocelot.UnitTests.Infrastructure
{
public class PlaceholdersTests public class PlaceholdersTests
{ {
private IPlaceholders _placeholders; private readonly IPlaceholders _placeholders;
private Mock<IBaseUrlFinder> _finder; private readonly Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo; private readonly Mock<IRequestScopedDataRepository> _repo;
private readonly Mock<IHttpContextAccessor> _accessor;
public PlaceholdersTests() public PlaceholdersTests()
{ {
_accessor = new Mock<IHttpContextAccessor>();
_repo = new Mock<IRequestScopedDataRepository>(); _repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>(); _finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object); _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object);
} }
[Fact] [Fact]
@ -33,6 +37,15 @@ namespace Ocelot.UnitTests.Infrastructure
result.Data.ShouldBe(baseUrl); result.Data.ShouldBe(baseUrl);
} }
[Fact]
public void should_return_remote_ip_address()
{
var httpContext = new DefaultHttpContext(){Connection = { RemoteIpAddress = IPAddress.Any}};
_accessor.Setup(x => x.HttpContext).Returns(httpContext);
var result = _placeholders.Get("{RemoteIpAddress}");
result.Data.ShouldBe(httpContext.Connection.RemoteIpAddress.ToString());
}
[Fact] [Fact]
public void should_return_key_does_not_exist() public void should_return_key_does_not_exist()
{ {