Merge pull request #36 from TomPallister/develop

Feature/issue 209 upstream host based routing (#216)
This commit is contained in:
geffzhang 2018-02-02 21:23:44 +08:00 committed by GitHub
commit eca3290f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 581 additions and 41 deletions

View File

@ -3,7 +3,7 @@ Routing
Ocelot's primary functionality is to take incomeing http requests and forward them on Ocelot's primary functionality is to take incomeing http requests and forward them on
to a downstream service. At the moment in the form of another http request (in the future to a downstream service. At the moment in the form of another http request (in the future
this could be any transport mechanism.). this could be any transport mechanism).
Ocelot's describes the routing of one request to another as a ReRoute. In order to get Ocelot's describes the routing of one request to another as a ReRoute. In order to get
anything working in Ocelot you need to set up a ReRoute in the configuration. anything working in Ocelot you need to set up a ReRoute in the configuration.
@ -110,3 +110,33 @@ The catch all has a lower priority than any other ReRoute. If you also have the
"UpstreamPathTemplate": "/", "UpstreamPathTemplate": "/",
"UpstreamHttpMethod": [ "Get" ] "UpstreamHttpMethod": [ "Get" ]
} }
Upstream Host
^^^^^^^^^^^^^
This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute.
In order to use this feature please add the following to your config.
.. code-block:: json
{
"DownstreamPathTemplate": "/",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "10.0.10.1",
"Port": 80,
}
],
"UpstreamPathTemplate": "/",
"UpstreamHttpMethod": [ "Get" ],
"UpstreamHost": "somedomain.com"
}
The ReRoute above will only be matched when the host header value is somedomain.com.
If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and
preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set.
This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ .

View File

@ -10,7 +10,7 @@ namespace Ocelot.Configuration.Builder
public class ReRouteBuilder public class ReRouteBuilder
{ {
private AuthenticationOptions _authenticationOptions; private AuthenticationOptions _authenticationOptions;
private string _loadBalancerKey; private string _reRouteKey;
private string _downstreamPathTemplate; private string _downstreamPathTemplate;
private string _upstreamTemplate; private string _upstreamTemplate;
private UpstreamPathTemplate _upstreamTemplatePattern; private UpstreamPathTemplate _upstreamTemplatePattern;
@ -36,6 +36,7 @@ namespace Ocelot.Configuration.Builder
private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace; private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace;
private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace; private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace;
private readonly List<DownstreamHostAndPort> _downstreamAddresses; private readonly List<DownstreamHostAndPort> _downstreamAddresses;
private string _upstreamHost;
public ReRouteBuilder() public ReRouteBuilder()
{ {
@ -48,6 +49,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteBuilder WithUpstreamHost(string upstreamAddresses)
{
_upstreamHost = upstreamAddresses;
return this;
}
public ReRouteBuilder WithLoadBalancer(string loadBalancer) public ReRouteBuilder WithLoadBalancer(string loadBalancer)
{ {
_loadBalancer = loadBalancer; _loadBalancer = loadBalancer;
@ -150,9 +157,9 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteBuilder WithReRouteKey(string loadBalancerKey) public ReRouteBuilder WithReRouteKey(string reRouteKey)
{ {
_loadBalancerKey = loadBalancerKey; _reRouteKey = reRouteKey;
return this; return this;
} }
@ -204,6 +211,7 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRoute Build() public ReRoute Build()
{ {
return new ReRoute( return new ReRoute(
@ -223,7 +231,7 @@ namespace Ocelot.Configuration.Builder
_fileCacheOptions, _fileCacheOptions,
_downstreamScheme, _downstreamScheme,
_loadBalancer, _loadBalancer,
_loadBalancerKey, _reRouteKey,
_useQos, _useQos,
_qosOptions, _qosOptions,
_enableRateLimiting, _enableRateLimiting,
@ -233,7 +241,8 @@ namespace Ocelot.Configuration.Builder
_serviceName, _serviceName,
_upstreamHeaderFindAndReplace, _upstreamHeaderFindAndReplace,
_downstreamHeaderFindAndReplace, _downstreamHeaderFindAndReplace,
_downstreamAddresses); _downstreamAddresses,
_upstreamHost);
} }
} }
} }

View File

@ -166,6 +166,7 @@ namespace Ocelot.Configuration.Creator
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.WithUpstreamHost(fileReRoute.UpstreamHost)
.Build(); .Build();
return reRoute; return reRoute;

View File

@ -42,5 +42,6 @@ namespace Ocelot.Configuration.File
public FileHttpHandlerOptions HttpHandlerOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public bool UseServiceDiscovery {get;set;} public bool UseServiceDiscovery {get;set;}
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;} public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
public string UpstreamHost { get; set; }
} }
} }

View File

@ -33,15 +33,17 @@ namespace Ocelot.Configuration
string serviceName, string serviceName,
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace, List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace, List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
List<DownstreamHostAndPort> downstreamAddresses) List<DownstreamHostAndPort> downstreamAddresses,
string upstreamHost)
{ {
DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace; UpstreamHost = upstreamHost;
UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace; DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace ?? new List<HeaderFindAndReplace>();
UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace ?? new List<HeaderFindAndReplace>();
ServiceName = serviceName; ServiceName = serviceName;
UseServiceDiscovery = useServiceDiscovery; UseServiceDiscovery = useServiceDiscovery;
ReRouteKey = reRouteKey; ReRouteKey = reRouteKey;
LoadBalancer = loadBalancer; LoadBalancer = loadBalancer;
DownstreamAddresses = downstreamAddresses; DownstreamAddresses = downstreamAddresses ?? new List<DownstreamHostAndPort>();
DownstreamPathTemplate = downstreamPathTemplate; DownstreamPathTemplate = downstreamPathTemplate;
UpstreamPathTemplate = upstreamPathTemplate; UpstreamPathTemplate = upstreamPathTemplate;
UpstreamHttpMethod = upstreamHttpMethod; UpstreamHttpMethod = upstreamHttpMethod;
@ -53,12 +55,9 @@ namespace Ocelot.Configuration
RequestIdKey = requestIdKey; RequestIdKey = requestIdKey;
IsCached = isCached; IsCached = isCached;
CacheOptions = cacheOptions; CacheOptions = cacheOptions;
ClaimsToQueries = claimsToQueries ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
?? new List<ClaimToThing>(); ClaimsToClaims = claimsToClaims ?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims ClaimsToHeaders = claimsToHeaders ?? new List<ClaimToThing>();
?? new List<ClaimToThing>();
ClaimsToHeaders = claimsToHeaders
?? new List<ClaimToThing>();
DownstreamScheme = downstreamScheme; DownstreamScheme = downstreamScheme;
IsQos = isQos; IsQos = isQos;
QosOptionsOptions = qosOptions; QosOptionsOptions = qosOptions;
@ -94,6 +93,6 @@ namespace Ocelot.Configuration
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;} public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;}
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace {get;private set;} public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace {get;private set;}
public List<DownstreamHostAndPort> DownstreamAddresses {get;private set;} public List<DownstreamHostAndPort> DownstreamAddresses {get;private set;}
public string UpstreamHost { get; private set; }
} }
} }

View File

@ -39,7 +39,8 @@ namespace Ocelot.Configuration.Validator
private static bool IsNotDuplicateIn(FileReRoute reRoute, List<FileReRoute> reRoutes) private static bool IsNotDuplicateIn(FileReRoute reRoute, List<FileReRoute> reRoutes)
{ {
var matchingReRoutes = reRoutes.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate).ToList(); var matchingReRoutes = reRoutes
.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)).ToList();
if(matchingReRoutes.Count == 1) if(matchingReRoutes.Count == 1)
{ {

View File

@ -13,44 +13,56 @@ namespace Ocelot.DownstreamRouteFinder.Finder
public class DownstreamRouteFinder : IDownstreamRouteFinder public class DownstreamRouteFinder : IDownstreamRouteFinder
{ {
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
private readonly IPlaceholderNameAndValueFinder __placeholderNameAndValueFinder; private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder;
public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder)
{ {
_urlMatcher = urlMatcher; _urlMatcher = urlMatcher;
__placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
} }
public Response<DownstreamRoute> FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration) public Response<DownstreamRoute> FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration, string upstreamHost)
{ {
var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())).OrderByDescending(x => x.UpstreamTemplatePattern.Priority); var downstreamRoutes = new List<DownstreamRoute>();
var applicableReRoutes = configuration.ReRoutes
.Where(r => RouteIsApplicableToThisRequest(r, httpMethod, upstreamHost))
.OrderByDescending(x => x.UpstreamTemplatePattern.Priority);
foreach (var reRoute in applicableReRoutes) foreach (var reRoute in applicableReRoutes)
{ {
if (path == reRoute.UpstreamTemplatePattern.Template)
{
return GetPlaceholderNamesAndValues(path, reRoute);
}
var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template); var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template);
if (urlMatch.Data.Match) if (urlMatch.Data.Match)
{ {
return GetPlaceholderNamesAndValues(path, reRoute); downstreamRoutes.Add(GetPlaceholderNamesAndValues(path, reRoute));
} }
} }
if (downstreamRoutes.Any())
{
var notNullOption = downstreamRoutes.FirstOrDefault(x => !string.IsNullOrEmpty(x.ReRoute.UpstreamHost));
var nullOption = downstreamRoutes.FirstOrDefault(x => string.IsNullOrEmpty(x.ReRoute.UpstreamHost));
return notNullOption != null ? new OkResponse<DownstreamRoute>(notNullOption) : new OkResponse<DownstreamRoute>(nullOption);
}
return new ErrorResponse<DownstreamRoute>(new List<Error> return new ErrorResponse<DownstreamRoute>(new List<Error>
{ {
new UnableToFindDownstreamRouteError() new UnableToFindDownstreamRouteError()
}); });
} }
private OkResponse<DownstreamRoute> GetPlaceholderNamesAndValues(string path, ReRoute reRoute) private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost)
{ {
var templatePlaceholderNameAndValues = __placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value); return reRoute.UpstreamHttpMethod.Count == 0 || reRoute.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower()) && !(!string.IsNullOrEmpty(reRoute.UpstreamHost) && reRoute.UpstreamHost != upstreamHost);
}
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute)); private DownstreamRoute GetPlaceholderNamesAndValues(string path, ReRoute reRoute)
{
var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value);
return new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute);
} }
} }
} }

View File

@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder
{ {
public interface IDownstreamRouteFinder public interface IDownstreamRouteFinder
{ {
Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration); Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration, string upstreamHost);
} }
} }

View File

@ -36,6 +36,8 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
{ {
var upstreamUrlPath = context.Request.Path.ToString(); var upstreamUrlPath = context.Request.Path.ToString();
var upstreamHost = context.Request.Headers["Host"];
var configuration = await _configProvider.Get(); var configuration = await _configProvider.Get();
if(configuration.IsError) if(configuration.IsError)
@ -49,7 +51,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath);
var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method, configuration.Data); var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method, configuration.Data, upstreamHost);
if (downstreamRoute.IsError) if (downstreamRoute.IsError)
{ {

View File

@ -0,0 +1,290 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class UpstreamHostTests : IDisposable
{
private IWebHost _builder;
private readonly Steps _steps;
private string _downstreamPath;
public UpstreamHostTests()
{
_steps = new Steps();
}
[Fact]
public void should_return_response_200_with_simple_url_and_hosts_match()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "localhost"
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_return_response_200_with_simple_url_and_hosts_match_multiple_re_routes()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "localhost"
},
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 50000,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "DONTMATCH"
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_return_response_200_with_simple_url_and_hosts_match_multiple_re_routes_reversed()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 50000,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "DONTMATCH"
},
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "localhost"
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_return_response_200_with_simple_url_and_hosts_match_multiple_re_routes_reversed_with_no_host_first()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 50000,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
},
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "localhost"
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_return_response_404_with_simple_url_and_hosts_dont_match()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHost = "127.0.0.20:5000"
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{
_builder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if(_downstreamPath != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
})
.Build();
_builder.Start();
}
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath)
{
_downstreamPath.ShouldBe(expectedDownstreamPath);
}
public void Dispose()
{
_builder?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -269,6 +269,46 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void configuration_is_valid_with_duplicate_reroutes_all_verbs_but_different_hosts()
{
this.Given(x => x.GivenAConfiguration(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/products/",
UpstreamPathTemplate = "/asdf/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "bb.co.uk"
}
},
UpstreamHost = "host1"
},
new FileReRoute
{
DownstreamPathTemplate = "/www/test/",
UpstreamPathTemplate = "/asdf/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "bb.co.uk"
}
},
UpstreamHost = "host1"
}
}
}))
.When(x => x.WhenIValidateTheConfiguration())
.Then(x => x.ThenTheResultIsValid())
.BDDfy();
}
[Fact] [Fact]
public void configuration_is_not_valid_with_duplicate_reroutes_specific_verbs() public void configuration_is_not_valid_with_duplicate_reroutes_specific_verbs()
{ {

View File

@ -75,7 +75,7 @@
{ {
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_downstreamRouteFinder _downstreamRouteFinder
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IOcelotConfiguration>())) .Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IOcelotConfiguration>(), It.IsAny<string>()))
.Returns(_downstreamRoute); .Returns(_downstreamRoute);
} }

View File

@ -26,6 +26,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private OcelotConfiguration _config; private OcelotConfiguration _config;
private Response<UrlMatch> _match; private Response<UrlMatch> _match;
private string _upstreamHttpMethod; private string _upstreamHttpMethod;
private string _upstreamHost;
public DownstreamRouteFinderTests() public DownstreamRouteFinderTests()
{ {
@ -210,7 +211,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.Build() .Build()
))) )))
.And(x => x.ThenTheUrlMatcherIsNotCalled())
.BDDfy(); .BDDfy();
} }
@ -370,12 +370,161 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .And(x => x.GivenTheUpstreamHttpMethodIs("Post"))
.When(x => x.WhenICallTheFinder()) .When(x => x.WhenICallTheFinder())
.Then( .Then(x => x.ThenAnErrorResponseIsReturned())
x => x.ThenAnErrorResponseIsReturned())
.And(x => x.ThenTheUrlMatcherIsNotCalled()) .And(x => x.ThenTheUrlMatcherIsNotCalled())
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_route_when_host_matches()
{
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"))
.And(x => GivenTheUpstreamHostIs("MATCH"))
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(
new OkResponse<List<PlaceholderNameAndValue>>(
new List<PlaceholderNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.WithUpstreamHost("MATCH")
.Build()
}, string.Empty, serviceProviderConfig
))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(
new List<PlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.Build()
)))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_route_when_upstreamhost_is_null()
{
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"))
.And(x => GivenTheUpstreamHostIs("MATCH"))
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(
new OkResponse<List<PlaceholderNameAndValue>>(
new List<PlaceholderNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.Build()
}, string.Empty, serviceProviderConfig
))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(
new List<PlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.Build()
)))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_not_return_route_when_host_doesnt_match()
{
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"))
.And(x => GivenTheUpstreamHostIs("DONTMATCH"))
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<PlaceholderNameAndValue>>(new List<PlaceholderNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.WithUpstreamHost("MATCH")
.Build()
}, string.Empty, serviceProviderConfig
))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder())
.Then(x => x.ThenAnErrorResponseIsReturned())
.And(x => x.ThenTheUrlMatcherIsNotCalled())
.BDDfy();
}
[Fact]
public void should_return_route_when_host_matches_but_null_host_on_same_path_first()
{
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"))
.And(x => GivenTheUpstreamHostIs("MATCH"))
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(
new OkResponse<List<PlaceholderNameAndValue>>(
new List<PlaceholderNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("THENULLPATH")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.Build(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.WithUpstreamHost("MATCH")
.Build()
}, string.Empty, serviceProviderConfig
))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(
new List<PlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1))
.Build()
)))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2))
.BDDfy();
}
private void GivenTheUpstreamHostIs(string upstreamHost)
{
_upstreamHost = upstreamHost;
}
private void GivenTheTemplateVariableAndNameFinderReturns(Response<List<PlaceholderNameAndValue>> response) private void GivenTheTemplateVariableAndNameFinderReturns(Response<List<PlaceholderNameAndValue>> response)
{ {
_finder _finder
@ -399,6 +548,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once);
} }
private void ThenTheUrlMatcherIsCalledCorrectly(int times)
{
_mockMatcher
.Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Exactly(times));
}
private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath)
{ {
_mockMatcher _mockMatcher
@ -432,7 +587,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void WhenICallTheFinder() private void WhenICallTheFinder()
{ {
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod, _config); _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod, _config, _upstreamHost);
} }
private void ThenTheFollowingIsReturned(DownstreamRoute expected) private void ThenTheFollowingIsReturned(DownstreamRoute expected)