mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 09:35:27 +08:00 
			
		
		
		
	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:
		@@ -81,6 +81,7 @@ Placeholders
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
{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.
 | 
			
		||||
@@ -120,6 +121,17 @@ finally if you are using a load balancer with Ocelot you will get multiple downs
 | 
			
		||||
        "AllowAutoRedirect": false,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
X-Forwarded-For
 | 
			
		||||
^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
An example of using {RemoteIpAddress} placeholder...
 | 
			
		||||
 | 
			
		||||
.. code-block:: json
 | 
			
		||||
 | 
			
		||||
  "UpstreamHeaderTransform": {
 | 
			
		||||
        "X-Forwarded-For": "{RemoteIpAddress}"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
Future
 | 
			
		||||
^^^^^^
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,29 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using Ocelot.Infrastructure.Claims.Parser;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Configuration.Creator;
 | 
			
		||||
using Ocelot.Request.Middleware;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Headers
 | 
			
		||||
namespace Ocelot.Headers
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using Infrastructure;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using Ocelot.Configuration;
 | 
			
		||||
    using Ocelot.Infrastructure.Claims.Parser;
 | 
			
		||||
    using Ocelot.Responses;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Microsoft.Extensions.Primitives;
 | 
			
		||||
    using Ocelot.Configuration.Creator;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
    using Ocelot.Request.Middleware;
 | 
			
		||||
 | 
			
		||||
    public class AddHeadersToRequest : IAddHeadersToRequest
 | 
			
		||||
    {
 | 
			
		||||
        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;
 | 
			
		||||
            _placeholders = placeholders;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
        {
 | 
			
		||||
            var requestHeader = context.Request.Headers;
 | 
			
		||||
 | 
			
		||||
            foreach (var header in headers)
 | 
			
		||||
            {
 | 
			
		||||
                if (requestHeader.ContainsKey(header.Key))
 | 
			
		||||
@@ -53,8 +61,23 @@ namespace Ocelot.Headers
 | 
			
		||||
                    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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,13 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using Ocelot.Infrastructure;
 | 
			
		||||
using Ocelot.Infrastructure.Extensions;
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using Ocelot.Middleware.Multiplexer;
 | 
			
		||||
using Ocelot.Request.Middleware;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Headers
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using Ocelot.Configuration;
 | 
			
		||||
    using Ocelot.Infrastructure;
 | 
			
		||||
    using Ocelot.Infrastructure.Extensions;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
    using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
    public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IPlaceholders _placeholders;
 | 
			
		||||
@@ -19,8 +17,11 @@ namespace Ocelot.Headers
 | 
			
		||||
            _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)
 | 
			
		||||
            {
 | 
			
		||||
                var dict = response.Headers.ToDictionary(x => x.Key);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,12 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using Ocelot.Middleware.Multiplexer;
 | 
			
		||||
using Ocelot.Request.Middleware;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Headers
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using Ocelot.Configuration;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
    using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
    public interface IHttpResponseHeaderReplacer
 | 
			
		||||
    {
 | 
			
		||||
        Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest httpRequestMessage);
 | 
			
		||||
        Response Replace(DownstreamContext context, List<HeaderFindAndReplace> fAndRs);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ namespace Ocelot.Headers.Middleware
 | 
			
		||||
 | 
			
		||||
            var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
 | 
			
		||||
 | 
			
		||||
            _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
 | 
			
		||||
            _postReplacer.Replace(context, postFAndRs);
 | 
			
		||||
 | 
			
		||||
            _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +1,38 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using Ocelot.Request.Middleware;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Infrastructure
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
    using Ocelot.Request.Middleware;
 | 
			
		||||
    using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
    public class Placeholders : IPlaceholders
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Dictionary<string, Func<Response<string>>> _placeholders;
 | 
			
		||||
        private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders;
 | 
			
		||||
        private readonly IBaseUrlFinder _finder;
 | 
			
		||||
        private readonly IRequestScopedDataRepository _repo;
 | 
			
		||||
        private readonly IHttpContextAccessor _httpContextAccessor;
 | 
			
		||||
 | 
			
		||||
        public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo)
 | 
			
		||||
        public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo, IHttpContextAccessor httpContextAccessor)
 | 
			
		||||
        {
 | 
			
		||||
            _repo = repo;
 | 
			
		||||
            _httpContextAccessor = httpContextAccessor;
 | 
			
		||||
            _finder = finder;
 | 
			
		||||
            _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)
 | 
			
		||||
            _placeholders = new Dictionary<string, Func<Response<string>>>
 | 
			
		||||
            {
 | 
			
		||||
                    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.Add("{DownstreamBaseUrl}", x => {
 | 
			
		||||
                var downstreamUrl = $"{x.Scheme}://{x.Host}";
 | 
			
		||||
 | 
			
		||||
                if(x.Port != 80 && x.Port != 443)
 | 
			
		||||
            _requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>
 | 
			
		||||
            {
 | 
			
		||||
                    downstreamUrl = $"{downstreamUrl}:{x.Port}";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return $"{downstreamUrl}/";
 | 
			
		||||
            });
 | 
			
		||||
                { "{DownstreamBaseUrl}", GetDownstreamBaseUrl() }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Response<string> Get(string key)
 | 
			
		||||
@@ -67,5 +58,56 @@ namespace Ocelot.Infrastructure
 | 
			
		||||
 | 
			
		||||
            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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ namespace Ocelot.AcceptanceTests
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Net;
 | 
			
		||||
    using System.Net.Sockets;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Ocelot.Configuration.File;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										199
									
								
								test/Ocelot.IntegrationTests/HeaderTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								test/Ocelot.IntegrationTests/HeaderTests.cs
									
									
									
									
									
										Normal 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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -70,6 +70,28 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
                .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]
 | 
			
		||||
        public void should_use_base_url_placeholder()
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,9 @@ using Ocelot.Request.Middleware;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.Headers
 | 
			
		||||
{
 | 
			
		||||
    using Ocelot.Infrastructure;
 | 
			
		||||
    using Ocelot.Logging;
 | 
			
		||||
 | 
			
		||||
    public class AddHeadersToRequestClaimToThingTests
 | 
			
		||||
    {
 | 
			
		||||
        private readonly AddHeadersToRequest _addHeadersToRequest;
 | 
			
		||||
@@ -24,11 +27,15 @@ namespace Ocelot.UnitTests.Headers
 | 
			
		||||
        private List<ClaimToThing> _configuration;
 | 
			
		||||
        private Response _result;
 | 
			
		||||
        private Response<string> _claimValue;
 | 
			
		||||
        private Mock<IPlaceholders> _placeholders;
 | 
			
		||||
        private Mock<IOcelotLoggerFactory> _factory;
 | 
			
		||||
 | 
			
		||||
        public AddHeadersToRequestClaimToThingTests()
 | 
			
		||||
        {
 | 
			
		||||
            _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"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,56 @@
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Moq;
 | 
			
		||||
using Ocelot.Configuration.Creator;
 | 
			
		||||
using Ocelot.Headers;
 | 
			
		||||
using Ocelot.Infrastructure.Claims.Parser;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.Headers
 | 
			
		||||
namespace Ocelot.UnitTests.Headers
 | 
			
		||||
{
 | 
			
		||||
    using Ocelot.Infrastructure;
 | 
			
		||||
    using Ocelot.Logging;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Moq;
 | 
			
		||||
    using Ocelot.Configuration.Creator;
 | 
			
		||||
    using Ocelot.Headers;
 | 
			
		||||
    using Ocelot.Infrastructure.Claims.Parser;
 | 
			
		||||
    using Responder;
 | 
			
		||||
    using Responses;
 | 
			
		||||
    using Shouldly;
 | 
			
		||||
    using TestStack.BDDfy;
 | 
			
		||||
    using Xunit;
 | 
			
		||||
 | 
			
		||||
    public class AddHeadersToRequestPlainTests
 | 
			
		||||
    {
 | 
			
		||||
        private readonly AddHeadersToRequest _addHeadersToRequest;
 | 
			
		||||
        private HttpContext _context;
 | 
			
		||||
        private AddHeader _addedHeader;
 | 
			
		||||
        private readonly Mock<IPlaceholders> _placeholders;
 | 
			
		||||
        private Mock<IOcelotLoggerFactory> _factory;
 | 
			
		||||
        private readonly Mock<IOcelotLogger> _logger;
 | 
			
		||||
 | 
			
		||||
        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]
 | 
			
		||||
@@ -38,9 +71,23 @@ namespace Ocelot.UnitTests.Headers
 | 
			
		||||
                .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()
 | 
			
		||||
        {
 | 
			
		||||
            _context = new DefaultHttpContext();
 | 
			
		||||
            _context = new DefaultHttpContext
 | 
			
		||||
            {
 | 
			
		||||
                Request =
 | 
			
		||||
                {
 | 
			
		||||
                    Headers =
 | 
			
		||||
                    {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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.ToString().ShouldBe(_addedHeader.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheHeaderGetsTakenOverToTheRequestHeaders(string expected)
 | 
			
		||||
        {
 | 
			
		||||
            var requestHeaders = _context.Request.Headers;
 | 
			
		||||
            var value = requestHeaders[_addedHeader.Key];
 | 
			
		||||
            value.ToString().ShouldBe(expected);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,7 @@ namespace Ocelot.UnitTests.Headers
 | 
			
		||||
 | 
			
		||||
        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()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,22 @@
 | 
			
		||||
using Xunit;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Ocelot.Headers;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using Moq;
 | 
			
		||||
using Ocelot.Infrastructure;
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Ocelot.Middleware.Multiplexer;
 | 
			
		||||
using Ocelot.Request.Middleware;
 | 
			
		||||
 | 
			
		||||
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 Shouldly;
 | 
			
		||||
    using TestStack.BDDfy;
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using Ocelot.Headers;
 | 
			
		||||
    using Ocelot.Configuration;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using Ocelot.Responses;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Net;
 | 
			
		||||
    using Moq;
 | 
			
		||||
 | 
			
		||||
    public class HttpResponseHeaderReplacerTests
 | 
			
		||||
    {
 | 
			
		||||
        private DownstreamResponse _response;
 | 
			
		||||
@@ -27,12 +27,14 @@ namespace Ocelot.UnitTests.Headers
 | 
			
		||||
        private DownstreamRequest _request;
 | 
			
		||||
        private Mock<IBaseUrlFinder> _finder;
 | 
			
		||||
        private Mock<IRequestScopedDataRepository> _repo;
 | 
			
		||||
        private Mock<IHttpContextAccessor> _accessor;
 | 
			
		||||
 | 
			
		||||
        public HttpResponseHeaderReplacerTests()
 | 
			
		||||
        {
 | 
			
		||||
            _accessor = new Mock<IHttpContextAccessor>();
 | 
			
		||||
            _repo = new Mock<IRequestScopedDataRepository>();
 | 
			
		||||
            _finder = new Mock<IBaseUrlFinder>();
 | 
			
		||||
            _placeholders = new Placeholders(_finder.Object, _repo.Object);
 | 
			
		||||
            _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object);
 | 
			
		||||
            _replacer = new HttpResponseHeaderReplacer(_placeholders);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -261,7 +263,8 @@ namespace Ocelot.UnitTests.Headers
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,31 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Moq;
 | 
			
		||||
using Ocelot.Infrastructure;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using Ocelot.Request.Middleware;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.Infrastructure
 | 
			
		||||
{
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Net;
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using Moq;
 | 
			
		||||
    using Ocelot.Infrastructure;
 | 
			
		||||
    using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
    using Ocelot.Request.Middleware;
 | 
			
		||||
    using Ocelot.Responses;
 | 
			
		||||
    using Shouldly;
 | 
			
		||||
    using Xunit;
 | 
			
		||||
 | 
			
		||||
    public class PlaceholdersTests
 | 
			
		||||
    {
 | 
			
		||||
        private IPlaceholders _placeholders;
 | 
			
		||||
        private Mock<IBaseUrlFinder> _finder;
 | 
			
		||||
        private Mock<IRequestScopedDataRepository> _repo;
 | 
			
		||||
        private readonly IPlaceholders _placeholders;
 | 
			
		||||
        private readonly Mock<IBaseUrlFinder> _finder;
 | 
			
		||||
        private readonly Mock<IRequestScopedDataRepository> _repo;
 | 
			
		||||
        private readonly Mock<IHttpContextAccessor> _accessor;
 | 
			
		||||
 | 
			
		||||
        public PlaceholdersTests()
 | 
			
		||||
        {
 | 
			
		||||
            _accessor = new Mock<IHttpContextAccessor>();
 | 
			
		||||
            _repo = new Mock<IRequestScopedDataRepository>();
 | 
			
		||||
            _finder = new Mock<IBaseUrlFinder>();
 | 
			
		||||
            _placeholders = new Placeholders(_finder.Object, _repo.Object);
 | 
			
		||||
            _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
@@ -33,6 +37,15 @@ namespace Ocelot.UnitTests.Infrastructure
 | 
			
		||||
            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]
 | 
			
		||||
        public void should_return_key_does_not_exist()
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user