mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 10:38:15 +08:00
Added support for query string parameters in upstream path template (#467)
This commit is contained in:
@ -14,7 +14,8 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
public UpstreamPathTemplate Create(IReRoute reRoute)
|
||||
{
|
||||
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
||||
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
||||
|
||||
|
||||
var placeholders = new List<string>();
|
||||
|
||||
@ -30,9 +31,17 @@ namespace Ocelot.Configuration.Creator
|
||||
//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 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
if (upstreamTemplate == "/")
|
||||
{
|
||||
return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority);
|
||||
return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString);
|
||||
}
|
||||
|
||||
if(upstreamTemplate.EndsWith("/"))
|
||||
@ -54,7 +63,7 @@ namespace Ocelot.Configuration.Creator
|
||||
? $"^{upstreamTemplate}{RegExMatchEndString}"
|
||||
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
||||
|
||||
return new UpstreamPathTemplate(route, reRoute.Priority);
|
||||
return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString);
|
||||
}
|
||||
|
||||
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket)
|
||||
|
@ -21,7 +21,7 @@
|
||||
_cache = new ConcurrentDictionary<string, OkResponse<DownstreamRoute>>();
|
||||
}
|
||||
|
||||
public Response<DownstreamRoute> Get(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost)
|
||||
public Response<DownstreamRoute> Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost)
|
||||
{
|
||||
var serviceName = GetServiceName(upstreamUrlPath);
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
_placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
||||
}
|
||||
|
||||
public Response<DownstreamRoute> Get(string path, string httpMethod, IInternalConfiguration configuration, string upstreamHost)
|
||||
public Response<DownstreamRoute> Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, IInternalConfiguration configuration, string upstreamHost)
|
||||
{
|
||||
var downstreamRoutes = new List<DownstreamRoute>();
|
||||
|
||||
@ -28,11 +28,11 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
|
||||
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 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
return notNullOption != null ? new OkResponse<DownstreamRoute>(notNullOption) : new OkResponse<DownstreamRoute>(nullOption);
|
||||
}
|
||||
|
||||
return new ErrorResponse<DownstreamRoute>(new UnableToFindDownstreamRouteError(path, httpMethod));
|
||||
return new ErrorResponse<DownstreamRoute>(new UnableToFindDownstreamRouteError(upstreamUrlPath, httpMethod));
|
||||
}
|
||||
|
||||
private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost)
|
||||
@ -53,9 +53,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
(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);
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
{
|
||||
public interface IDownstreamRouteProvider
|
||||
{
|
||||
Response<DownstreamRoute> Get(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost);
|
||||
Response<DownstreamRoute> Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost);
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +33,15 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -5,6 +5,6 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
{
|
||||
public interface IPlaceholderNameAndValueFinder
|
||||
{
|
||||
Response<List<PlaceholderNameAndValue>> Find(string path, string pathTemplate);
|
||||
Response<List<PlaceholderNameAndValue>> Find(string path, string query, string pathTemplate);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
{
|
||||
public interface IUrlPathToUrlTemplateMatcher
|
||||
{
|
||||
Response<UrlMatch> Match(string upstreamUrlPath, string upstreamUrlPathTemplate);
|
||||
Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,18 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
{
|
||||
public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
|
||||
{
|
||||
public Response<UrlMatch> Match(string upstreamUrlPath, string upstreamUrlPathTemplate)
|
||||
public Response<UrlMatch> 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<UrlMatch>(new UrlMatch(true))
|
||||
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
||||
}
|
||||
|
||||
return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}")
|
||||
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
||||
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
||||
}
|
||||
|
@ -5,27 +5,46 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
{
|
||||
public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder
|
||||
{
|
||||
public Response<List<PlaceholderNameAndValue>> Find(string path, string pathTemplate)
|
||||
public Response<List<PlaceholderNameAndValue>> Find(string path, string query, string pathTemplate)
|
||||
{
|
||||
var placeHolderNameAndValues = new List<PlaceholderNameAndValue>();
|
||||
|
||||
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 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
}
|
||||
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 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
return new OkResponse<List<PlaceholderNameAndValue>>(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 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||
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;
|
||||
}
|
||||
|
@ -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 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
{
|
||||
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 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
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)
|
||||
|
@ -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 @@ namespace Ocelot.Request.Creator
|
||||
* 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 ||
|
||||
|
@ -28,6 +28,7 @@ namespace Ocelot.Request.Middleware
|
||||
public async Task Invoke(DownstreamContext context)
|
||||
{
|
||||
var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request);
|
||||
|
||||
if (downstreamRequest.IsError)
|
||||
{
|
||||
SetPipelineError(context, downstreamRequest.Errors);
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user