diff --git a/docs/features/routing.rst b/docs/features/routing.rst index abcfc7d28..e72b5ef80 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -215,4 +215,33 @@ Ocelot allow's you to specify a querystring as part of the DownstreamPathTemplat } } -In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! Please note you cannot use query string parameters to match routes in the UpstreamPathTemplate. +In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! + +Ocelot will also allow you to put query string parametrs in the UpstreamPathTemplate so you can match certain queries to certain services. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 50110 + } + ] + } + ], + "GlobalConfiguration": { + "UseServiceDiscovery": false + } + } + +In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this +but you must start with the matching parameter. Also in this example Ocelot will swap the unitId param from the query string and use it in the downstream request path. \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 27328213b..90ad721dd 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -14,7 +14,8 @@ public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator public UpstreamPathTemplate Create(IReRoute reRoute) { - var upstreamTemplate = reRoute.UpstreamPathTemplate; + var upstreamTemplate = reRoute.UpstreamPathTemplate; + var placeholders = new List(); @@ -30,9 +31,17 @@ public UpstreamPathTemplate Create(IReRoute reRoute) //hack to handle /{url} case if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) { - return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0); + return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false); } } + } + + var containsQueryString = false; + + if (upstreamTemplate.Contains("?")) + { + containsQueryString = true; + upstreamTemplate = upstreamTemplate.Replace("?", "\\?"); } foreach (var placeholder in placeholders) @@ -42,7 +51,7 @@ public UpstreamPathTemplate Create(IReRoute reRoute) if (upstreamTemplate == "/") { - return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority); + return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString); } if(upstreamTemplate.EndsWith("/")) @@ -54,7 +63,7 @@ public UpstreamPathTemplate Create(IReRoute reRoute) ? $"^{upstreamTemplate}{RegExMatchEndString}" : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - return new UpstreamPathTemplate(route, reRoute.Priority); + return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString); } private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index b654d32d2..9a90edaf9 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -21,7 +21,7 @@ public DownstreamRouteCreator(IQoSOptionsCreator qoSOptionsCreator) _cache = new ConcurrentDictionary>(); } - public Response Get(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost) { var serviceName = GetServiceName(upstreamUrlPath); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index b236c2912..7c7264dea 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -18,7 +18,7 @@ public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlacehold _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response Get(string path, string httpMethod, IInternalConfiguration configuration, string upstreamHost) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, IInternalConfiguration configuration, string upstreamHost) { var downstreamRoutes = new List(); @@ -28,11 +28,11 @@ public Response Get(string path, string httpMethod, IInternalCo foreach (var reRoute in applicableReRoutes) { - var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template); + var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern.Template, reRoute.UpstreamTemplatePattern.ContainsQueryString); if (urlMatch.Data.Match) { - downstreamRoutes.Add(GetPlaceholderNamesAndValues(path, reRoute)); + downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, reRoute)); } } @@ -44,7 +44,7 @@ public Response Get(string path, string httpMethod, IInternalCo return notNullOption != null ? new OkResponse(notNullOption) : new OkResponse(nullOption); } - return new ErrorResponse(new UnableToFindDownstreamRouteError(path, httpMethod)); + return new ErrorResponse(new UnableToFindDownstreamRouteError(upstreamUrlPath, httpMethod)); } private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost) @@ -53,9 +53,9 @@ private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, (string.IsNullOrEmpty(reRoute.UpstreamHost) || reRoute.UpstreamHost == upstreamHost); } - private DownstreamRoute GetPlaceholderNamesAndValues(string path, ReRoute reRoute) + private DownstreamRoute GetPlaceholderNamesAndValues(string path, string query, ReRoute reRoute) { - var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value); + var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, reRoute.UpstreamPathTemplate.Value); return new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute); } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs index df14065d7..0a84f8f45 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs @@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteProvider { - Response Get(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); + Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 5c40228dc..b6c4bcf6a 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -33,13 +33,15 @@ public async Task Invoke(DownstreamContext context) { var upstreamUrlPath = context.HttpContext.Request.Path.ToString(); + var upstreamQueryString = context.HttpContext.Request.QueryString.ToString(); + var upstreamHost = context.HttpContext.Request.Headers["Host"]; Logger.LogDebug($"Upstream url path is {upstreamUrlPath}"); var provider = _factory.Get(context.Configuration); - var downstreamRoute = provider.Get(upstreamUrlPath, context.HttpContext.Request.Method, context.Configuration, upstreamHost); + var downstreamRoute = provider.Get(upstreamUrlPath, upstreamQueryString, context.HttpContext.Request.Method, context.Configuration, upstreamHost); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs index 678b10810..986d7b844 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs @@ -5,6 +5,6 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public interface IPlaceholderNameAndValueFinder { - Response> Find(string path, string pathTemplate); + Response> Find(string path, string query, string pathTemplate); } } diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs index 1a9f147af..bac96d537 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs @@ -4,6 +4,6 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public interface IUrlPathToUrlTemplateMatcher { - Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate); + Response Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString); } -} \ No newline at end of file +} diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs index 793d33cf8..d1706118a 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs @@ -5,11 +5,18 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher { - public Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate) + public Response Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString) { var regex = new Regex(upstreamUrlPathTemplate); - return regex.IsMatch(upstreamUrlPath) + if (!containsQueryString) + { + return regex.IsMatch(upstreamUrlPath) + ? new OkResponse(new UrlMatch(true)) + : new OkResponse(new UrlMatch(false)); + } + + return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}") ? new OkResponse(new UrlMatch(true)) : new OkResponse(new UrlMatch(false)); } diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 4471618f2..6f4fec00d 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -5,27 +5,46 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder { - public Response> Find(string path, string pathTemplate) + public Response> Find(string path, string query, string pathTemplate) { var placeHolderNameAndValues = new List(); + path = $"{path}{query}"; + int counterForPath = 0; - + + var delimiter = '/'; + var nextDelimiter = '/'; + for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) { if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) { if (IsPlaceholder(pathTemplate[counterForTemplate])) { + //should_find_multiple_query_string make test pass + if (PassedQueryString(pathTemplate, counterForTemplate)) + { + delimiter = '&'; + nextDelimiter = '&'; + } + + //should_find_multiple_query_string_and_path makes test pass + if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate)) + { + delimiter = '?'; + nextDelimiter = '?'; + } + var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); - var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath); + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter); placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - counterForPath = GetNextCounterPosition(path, counterForPath, '/'); + counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter); continue; } @@ -44,7 +63,7 @@ public Response> Find(string path, string pathTemp } else { - var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath + 1); + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '/'); placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); } @@ -57,6 +76,21 @@ public Response> Find(string path, string pathTemp return new OkResponse>(placeHolderNameAndValues); } + private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate) + { + return !pathTemplate.Substring(counterForTemplate).Contains("/"); + } + + private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate) + { + return !pathTemplate.Substring(0, counterForTemplate).Contains("?"); + } + + private static bool PassedQueryString(string pathTemplate, int counterForTemplate) + { + return pathTemplate.Substring(0, counterForTemplate).Contains("?"); + } + private bool IsCatchAll(string path, int counterForPath, string pathTemplate) { return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 @@ -69,11 +103,11 @@ private bool NothingAfterFirstForwardSlash(string path) return path.Length == 1 || path.Length == 0; } - private string GetPlaceholderValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl) + private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter) { - var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl); + var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl); - if (positionOfNextSlash == -1 || urlPathTemplate.Trim('/').EndsWith(variableName)) + if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query))) { positionOfNextSlash = urlPath.Length; } diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 11ee95745..6057e58ab 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,17 +1,15 @@ using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System; -using System.Linq; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Responses; using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.Middleware { + using System.Text.RegularExpressions; + public class DownstreamUrlCreatorMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; @@ -55,12 +53,11 @@ public async Task Invoke(DownstreamContext context) { context.DownstreamRequest.AbsolutePath = GetPath(dsPath); context.DownstreamRequest.Query = GetQueryString(dsPath); - - // todo - do we need to add anything from the request query string onto the query from the - // templae? } - else + else { + RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context); + context.DownstreamRequest.AbsolutePath = dsPath.Value; } } @@ -70,14 +67,37 @@ public async Task Invoke(DownstreamContext context) await _next.Invoke(context); } + private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamContext context) + { + foreach (var nAndV in context.TemplatePlaceholderNameAndValues) + { + var name = nAndV.Name.Replace("{", "").Replace("}", ""); + + if (context.DownstreamRequest.Query.Contains(name) && + context.DownstreamRequest.Query.Contains(nAndV.Value)) + { + var questionMarkOrAmpersand = context.DownstreamRequest.Query.IndexOf(name, StringComparison.Ordinal); + context.DownstreamRequest.Query = context.DownstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1); + + var rgx = new Regex($@"\b{name}={nAndV.Value}\b"); + context.DownstreamRequest.Query = rgx.Replace(context.DownstreamRequest.Query, ""); + + if (!string.IsNullOrEmpty(context.DownstreamRequest.Query)) + { + context.DownstreamRequest.Query = '?' + context.DownstreamRequest.Query.Substring(1); + } + } + } + } + private string GetPath(DownstreamPath dsPath) { - return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?")); + return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal)); } private string GetQueryString(DownstreamPath dsPath) { - return dsPath.Value.Substring(dsPath.Value.IndexOf("?")); + return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal)); } private bool ContainsQueryString(DownstreamPath dsPath) diff --git a/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs b/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs index e4bdb1fc4..352e70f3b 100644 --- a/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs +++ b/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs @@ -2,13 +2,12 @@ namespace Ocelot.Request.Creator { using System.Net.Http; using Ocelot.Request.Middleware; - using System.Runtime.InteropServices; using Ocelot.Infrastructure; public class DownstreamRequestCreator : IDownstreamRequestCreator { private readonly IFrameworkDescription _framework; - private const string dotNetFramework = ".NET Framework"; + private const string DotNetFramework = ".NET Framework"; public DownstreamRequestCreator(IFrameworkDescription framework) { @@ -24,8 +23,7 @@ public DownstreamRequest Create(HttpRequestMessage request) * And MS HttpClient in Full Framework actually rejects it. * see #366 issue **/ - - if(_framework.Get().Contains(dotNetFramework)) + if(_framework.Get().Contains(DotNetFramework)) { if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Head || diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index ce226d98c..4f3151d97 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -28,6 +28,7 @@ public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next, public async Task Invoke(DownstreamContext context) { var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request); + if (downstreamRequest.IsError) { SetPipelineError(context, downstreamRequest.Errors); diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs index 4b33a230f..b42c887eb 100644 --- a/src/Ocelot/Values/UpstreamPathTemplate.cs +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -2,14 +2,16 @@ namespace Ocelot.Values { public class UpstreamPathTemplate { - public UpstreamPathTemplate(string template, int priority) + public UpstreamPathTemplate(string template, int priority, bool containsQueryString) { Template = template; Priority = priority; + ContainsQueryString = containsQueryString; } public string Template { get; } - public int Priority { get; } + public int Priority { get; } + public bool ContainsQueryString { get; } } } diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs index bc0bac237..f97340af3 100644 --- a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs @@ -60,6 +60,152 @@ public void should_return_response_200_with_query_string_template() .BDDfy(); } + [Fact] + public void should_return_response_200_with_query_string_upstream_template() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_404_with_query_string_upstream_template_no_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_404_with_query_string_upstream_template_different_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?test=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_query_string_upstream_template_multiple_params() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "?productId=1", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, int statusCode, string responseBody) { _builder = new WebHostBuilder() @@ -72,10 +218,9 @@ private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, stri app.UsePathBase(basePath); app.Run(async context => { - if(context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) { - context.Response.StatusCode = 404; + context.Response.StatusCode = 500; await context.Response.WriteAsync("downstream path didnt match base path"); } else diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index 678c19347..6ff703bd8 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -14,6 +14,7 @@ public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig private RegExUrlMatcher _urlPathMatcher; private string _downstreamUrlPath; private string _downstreamUrlPathTemplate; + private string _upstreamQuery; public UrlPathToUrlPathTemplateMatcherBenchmarks() { @@ -33,7 +34,7 @@ public void SetUp() [Benchmark(Baseline = true)] public void Baseline() { - _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); + _urlPathMatcher.Match(_downstreamUrlPath, _upstreamQuery, _downstreamUrlPathTemplate, false); } // * Summary * diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index 79111e2e5..af7dfed7a 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -599,7 +599,7 @@ public void should_call_template_pattern_creator_correctly() .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List {"Get"}) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false)) .WithLoadBalancerKey("/api/products/{productId}|Get|") .Build(); @@ -628,7 +628,7 @@ public void should_call_template_pattern_creator_correctly() .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false)) .Build() })) .BDDfy(); @@ -922,7 +922,7 @@ private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern) { _upstreamTemplatePatternCreator .Setup(x => x.Create(It.IsAny())) - .Returns(new UpstreamPathTemplate(pattern, 1)); + .Returns(new UpstreamPathTemplate(pattern, 1, false)); } private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index 369869322..d924586d3 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -10,7 +10,7 @@ namespace Ocelot.UnitTests.Configuration public class UpstreamTemplatePatternCreatorTests { private FileReRoute _fileReRoute; - private UpstreamTemplatePatternCreator _creator; + private readonly UpstreamTemplatePatternCreator _creator; private UpstreamPathTemplate _result; public UpstreamTemplatePatternCreatorTests() @@ -191,6 +191,36 @@ public void should_create_template_pattern_that_starts_with_placeholder_then_has .BDDfy(); } + [Fact] + public void should_create_template_pattern_that_matches_query_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/.+/updates\\?unitId=.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_query_string_with_multiple_params() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId={productId}" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/.+/updates\\?unitId=.+&productId=.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) { _fileReRoute = fileReRoute; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index b4f827035..b29d2d29c 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -28,6 +28,7 @@ public class DownstreamRouteCreatorTests private IInternalConfiguration _configuration; private Mock _qosOptionsCreator; private Response _resultTwo; + private string _upstreamQuery; public DownstreamRouteCreatorTests() { @@ -247,12 +248,12 @@ private void ThenTheHandlerOptionsAreSet() private void WhenICreate() { - _result = _creator.Get(_upstreamUrlPath, _upstreamHttpMethod, _configuration, _upstreamHost); + _result = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); } private void WhenICreateAgain() { - _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamHttpMethod, _configuration, _upstreamHost); + _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); } private void ThenTheDownstreamRoutesAreTheSameReference() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 19b50e240..298492af2 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -86,7 +86,7 @@ private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRout { _downstreamRoute = new OkResponse(downstreamRoute); _finder - .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_downstreamRoute); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index b99bc865d..360d58f4b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -25,6 +25,7 @@ public class DownstreamRouteFinderTests private Response _match; private string _upstreamHttpMethod; private string _upstreamHost; + private string _upstreamQuery; public DownstreamRouteFinderTests() { @@ -48,22 +49,22 @@ public void should_return_highest_priority_when_first() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -73,13 +74,13 @@ public void should_return_highest_priority_when_first() new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build() ))) .BDDfy(); @@ -100,22 +101,22 @@ public void should_return_highest_priority_when_lowest() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -125,10 +126,10 @@ public void should_return_highest_priority_when_lowest() new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) .Build() ))) @@ -151,11 +152,11 @@ public void should_return_route() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -169,10 +170,10 @@ public void should_return_route() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -195,11 +196,11 @@ public void should_not_append_slash_to_upstream_url_path() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -213,10 +214,10 @@ public void should_not_append_slash_to_upstream_url_path() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) @@ -240,11 +241,11 @@ public void should_return_route_if_upstream_path_and_upstream_template_are_the_s .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -257,10 +258,10 @@ public void should_return_route_if_upstream_path_and_upstream_template_are_the_s .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .BDDfy(); @@ -283,22 +284,22 @@ public void should_return_correct_route_for_http_verb() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -311,10 +312,10 @@ public void should_return_correct_route_for_http_verb() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() ))) .BDDfy(); @@ -333,11 +334,11 @@ public void should_not_return_route() .WithDownstreamPathTemplate("somPath") .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false)) .Build()) .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false)) .Build(), }, string.Empty, serviceProviderConfig )) @@ -367,11 +368,11 @@ public void should_return_correct_route_for_http_verb_setting_multiple_upstream_ .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -384,10 +385,10 @@ public void should_return_correct_route_for_http_verb_setting_multiple_upstream_ .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() ))) .BDDfy(); @@ -410,11 +411,11 @@ public void should_return_correct_route_for_http_verb_setting_all_upstream_http_ .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -427,10 +428,10 @@ public void should_return_correct_route_for_http_verb_setting_all_upstream_http_ .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() ))) .BDDfy(); @@ -453,11 +454,11 @@ public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_m .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -486,12 +487,12 @@ public void should_return_route_when_host_matches() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -506,10 +507,10 @@ public void should_return_route_when_host_matches() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -533,11 +534,11 @@ public void should_return_route_when_upstreamhost_is_null() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -551,10 +552,10 @@ public void should_return_route_when_upstreamhost_is_null() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -576,12 +577,12 @@ public void should_not_return_route_when_host_doesnt_match() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build(), new ReRouteBuilder() @@ -589,12 +590,12 @@ public void should_not_return_route_when_host_doesnt_match() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -622,12 +623,12 @@ public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_h .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -655,12 +656,12 @@ public void should_return_route_when_host_does_match_with_empty_upstream_http_me .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -689,23 +690,23 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir .WithDownstreamPathTemplate("THENULLPATH") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -720,10 +721,10 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2)) @@ -738,7 +739,7 @@ private void GivenTheUpstreamHostIs(string upstreamHost) private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) { _finder - .Setup(x => x.Find(It.IsAny(), It.IsAny())) + .Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(response); } @@ -755,32 +756,32 @@ private void ThenAnErrorResponseIsReturned() private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); } private void ThenTheUrlMatcherIsCalledCorrectly(int times) { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Exactly(times)); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Exactly(times)); } private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) { _mockMatcher - .Verify(x => x.Match(expectedUpstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); } private void ThenTheUrlMatcherIsNotCalled() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Never); } private void GivenTheUrlMatcherReturns(Response match) { _match = match; _mockMatcher - .Setup(x => x.Match(It.IsAny(), It.IsAny())) + .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_match); } @@ -797,7 +798,7 @@ private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamHttpMethod, _config, _upstreamHost); + _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _config, _upstreamHost); } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index 0f3fea3b9..10fe106e7 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -9,22 +9,65 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public class RegExUrlMatcherTests { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private string _downstreamUrlPath; + private string _path; private string _downstreamPathTemplate; private Response _result; + private string _queryString; + private bool _containsQueryString; public RegExUrlMatcherTests() { _urlMatcher = new RegExUrlMatcher(); } + [Fact] + public void should_match_path_with_no_query_string() + { + const string regExForwardSlashAndOnePlaceHolder = "^(?i)/newThing$"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/newThing")) + .And(_ => GivenIHaveAQueryString("?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_query_string() + { + const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/.+/updates\\?unitId=.+$"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates")) + .And(_ => GivenIHaveAQueryString("?unitId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .And(_ => GivenThereIsAQueryInTemplate()) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_query_string_with_multiple_params() + { + const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/.+/updates\\?unitId=.+&productId=.+$"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates?unitId=2")) + .And(_ => GivenIHaveAQueryString("?unitId=2&productId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .And(_ => GivenThereIsAQueryInTemplate()) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + [Fact] public void should_not_match_slash_becaue_we_need_to_match_something_after_it() { - const string RegExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].*"; + const string regExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].+"; this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(RegExForwardSlashAndOnePlaceHolder)) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsFalse()) .BDDfy(); @@ -44,7 +87,7 @@ public void should_not_match_forward_slash_only_regex() public void should_not_match_issue_134() { this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.*/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.+/$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsFalse()) .BDDfy(); @@ -64,7 +107,7 @@ public void should_match_forward_slash_only_regex() public void should_find_match_when_template_smaller_than_valid_path() { this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.+$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -124,7 +167,7 @@ public void can_match_down_stream_url_with_downstream_template() public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -134,7 +177,7 @@ public void can_match_down_stream_url_with_downstream_template_with_one_place_ho public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/.*$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -144,7 +187,7 @@ public void can_match_down_stream_url_with_downstream_template_with_two_place_ho public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -154,7 +197,7 @@ public void can_match_down_stream_url_with_downstream_template_with_two_place_ho public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -164,7 +207,7 @@ public void can_match_down_stream_url_with_downstream_template_with_three_place_ public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -174,7 +217,7 @@ public void can_match_down_stream_url_with_downstream_template_with_three_place_ public void should_ignore_case_sensitivity() { this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.*/categories/.*/variant/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.+/categories/.+/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -184,15 +227,20 @@ public void should_ignore_case_sensitivity() public void should_respect_case_sensitivity() { this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsFalse()) .BDDfy(); } - private void GivenIHaveAUpstreamPath(string downstreamPath) + private void GivenIHaveAUpstreamPath(string path) + { + _path = path; + } + + private void GivenIHaveAQueryString(string queryString) { - _downstreamUrlPath = downstreamPath; + _queryString = queryString; } private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) @@ -202,7 +250,7 @@ private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate private void WhenIMatchThePaths() { - _result = _urlMatcher.Match(_downstreamUrlPath, _downstreamPathTemplate); + _result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString); } private void ThenTheResultIsTrue() @@ -214,5 +262,10 @@ private void ThenTheResultIsFalse() { _result.Data.Match.ShouldBeFalse(); } + + private void GivenThereIsAQueryInTemplate() + { + _containsQueryString = true; + } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index 29bc8fcd8..7ce2bb1d0 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -14,6 +14,7 @@ public class UrlPathPlaceholderNameAndValueFinderTests private string _downstreamUrlPath; private string _downstreamPathTemplate; private Response> _result; + private string _query; public UrlPathPlaceholderNameAndValueFinderTests() { @@ -114,6 +115,91 @@ public void should_not_find_anything() .BDDfy(); } + [Fact] + public void should_find_query_string() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_query_string_dont_include_hardcoded() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string_and_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{account}", "3") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products/3")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string_and_path_that_ends_with_slash() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{account}", "3") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products/3/")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}/?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + [Fact] public void can_match_down_stream_url_with_no_slash() { @@ -260,7 +346,12 @@ private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) private void WhenIFindTheUrlVariableNamesAndValues() { - _result = _finder.Find(_downstreamUrlPath, _downstreamPathTemplate); + _result = _finder.Find(_downstreamUrlPath, _query, _downstreamPathTemplate); + } + + private void GivenIHaveAQuery(string query) + { + _query = query; } } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 98fc7c11b..ac59b7362 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -72,6 +72,103 @@ public void should_replace_scheme_and_path() .BDDfy(); } + [Fact] + public void should_replace_query_string() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates")) + .And(x => ThenTheQueryStringIs("")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string_but_leave_non_placeholder_queries() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2&productId=2")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates?productId=2")) + .And(x => ThenTheQueryStringIs("?productId=2")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string_exact_match() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates/{unitIdIty}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2"), + new PlaceholderNameAndValue("{unitIdIty}", "3") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2?unitIdIty=3")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates/3")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates/3")) + .And(x => ThenTheQueryStringIs("")) + .BDDfy(); + } + [Fact] public void should_not_create_service_fabric_url() {