Added url replacer

This commit is contained in:
Tom Gardham-Pallister 2016-07-21 21:14:27 +01:00
parent dbff2b9530
commit d3b9fddcfd
8 changed files with 250 additions and 38 deletions

View File

@ -2,6 +2,6 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher
{ {
public interface IUrlPathToUrlPathTemplateMatcher public interface IUrlPathToUrlPathTemplateMatcher
{ {
UrlPathMatch Match(string urlPath, string urlPathTemplate); UrlPathMatch Match(string downstreamUrlPath, string downStreamUrlPathTemplate);
} }
} }

View File

@ -4,15 +4,14 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher
{ {
public class UrlPathMatch public class UrlPathMatch
{ {
public UrlPathMatch(bool match, List<TemplateVariableNameAndValue> templateVariableNameAndValues, string urlPathTemplate) public UrlPathMatch(bool match, List<TemplateVariableNameAndValue> templateVariableNameAndValues, string downstreamUrlPathTemplate)
{ {
Match = match; Match = match;
TemplateVariableNameAndValues = templateVariableNameAndValues; TemplateVariableNameAndValues = templateVariableNameAndValues;
UrlPathTemplate = urlPathTemplate; DownstreamUrlPathTemplate = downstreamUrlPathTemplate;
} }
public bool Match {get;private set;} public bool Match {get;private set;}
public List<TemplateVariableNameAndValue> TemplateVariableNameAndValues {get;private set;} public List<TemplateVariableNameAndValue> TemplateVariableNameAndValues {get;private set;}
public string DownstreamUrlPathTemplate {get;private set;}
public string UrlPathTemplate {get;private set;}
} }
} }

View File

@ -5,35 +5,31 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher
{ {
public class UrlPathToUrlPathTemplateMatcher : IUrlPathToUrlPathTemplateMatcher public class UrlPathToUrlPathTemplateMatcher : IUrlPathToUrlPathTemplateMatcher
{ {
public UrlPathMatch Match(string urlPath, string urlPathTemplate) public UrlPathMatch Match(string downstreamUrlPath, string downstreamUrlPathTemplate)
{ {
var urlPathTemplateCopy = urlPathTemplate; var urlPathTemplateCopy = downstreamUrlPathTemplate;
var templateKeysAndValues = new List<TemplateVariableNameAndValue>(); var templateKeysAndValues = new List<TemplateVariableNameAndValue>();
urlPath = urlPath.ToLower();
urlPathTemplate = urlPathTemplate.ToLower();
int counterForUrl = 0; int counterForUrl = 0;
for (int counterForTemplate = 0; counterForTemplate < urlPathTemplate.Length; counterForTemplate++) for (int counterForTemplate = 0; counterForTemplate < downstreamUrlPathTemplate.Length; counterForTemplate++)
{ {
if (CharactersDontMatch(urlPathTemplate[counterForTemplate], urlPath[counterForUrl]) && ContinueScanningUrl(counterForUrl,urlPath.Length)) if (CharactersDontMatch(downstreamUrlPathTemplate[counterForTemplate], downstreamUrlPath[counterForUrl]) && ContinueScanningUrl(counterForUrl,downstreamUrlPath.Length))
{ {
if (IsPlaceholder(urlPathTemplate[counterForTemplate])) if (IsPlaceholder(downstreamUrlPathTemplate[counterForTemplate]))
{ {
var variableName = GetPlaceholderVariableName(urlPathTemplate, counterForTemplate); var variableName = GetPlaceholderVariableName(downstreamUrlPathTemplate, counterForTemplate);
var variableValue = GetPlaceholderVariableValue(urlPath, counterForUrl); var variableValue = GetPlaceholderVariableValue(downstreamUrlPath, counterForUrl);
var templateVariableNameAndValue = new TemplateVariableNameAndValue(variableName, variableValue); var templateVariableNameAndValue = new TemplateVariableNameAndValue(variableName, variableValue);
templateKeysAndValues.Add(templateVariableNameAndValue); templateKeysAndValues.Add(templateVariableNameAndValue);
counterForTemplate = GetNextCounterPosition(urlPathTemplate, counterForTemplate, '}'); counterForTemplate = GetNextCounterPosition(downstreamUrlPathTemplate, counterForTemplate, '}');
counterForUrl = GetNextCounterPosition(urlPath, counterForUrl, '/'); counterForUrl = GetNextCounterPosition(downstreamUrlPath, counterForUrl, '/');
continue; continue;
} }

View File

@ -0,0 +1,10 @@
using Ocelot.Library.Infrastructure.UrlPathMatcher;
namespace Ocelot.Library.Infrastructure.UrlPathReplacer
{
public interface IUpstreamUrlPathTemplateVariableReplacer
{
string ReplaceTemplateVariable(string upstreamPathTemplate, UrlPathMatch urlPathMatch);
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Text;
using Ocelot.Library.Infrastructure.UrlPathMatcher;
namespace Ocelot.Library.Infrastructure.UrlPathReplacer
{
public class UpstreamUrlPathTemplateVariableReplacer : IUpstreamUrlPathTemplateVariableReplacer
{
public string ReplaceTemplateVariable(string upstreamPathTemplate, UrlPathMatch urlPathMatch)
{
var upstreamUrl = new StringBuilder();
upstreamUrl.Append(upstreamPathTemplate);
foreach (var templateVarAndValue in urlPathMatch.TemplateVariableNameAndValues)
{
upstreamUrl.Replace(templateVarAndValue.TemplateVariableName, templateVarAndValue.TemplateVariableValue);
}
return upstreamUrl.ToString();
}
}
}

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.HostUrlRepository; using Ocelot.Library.Infrastructure.HostUrlRepository;
using Ocelot.Library.Infrastructure.UrlPathMatcher; using Ocelot.Library.Infrastructure.UrlPathMatcher;
using Ocelot.Library.Infrastructure.UrlPathReplacer;
using Ocelot.Library.Infrastructure.UrlPathTemplateRepository; using Ocelot.Library.Infrastructure.UrlPathTemplateRepository;
namespace Ocelot.Library.Middleware namespace Ocelot.Library.Middleware
@ -13,15 +14,18 @@ namespace Ocelot.Library.Middleware
private readonly IUrlPathToUrlPathTemplateMatcher _urlMatcher; private readonly IUrlPathToUrlPathTemplateMatcher _urlMatcher;
private readonly IUrlPathTemplateMapRepository _urlPathRepository; private readonly IUrlPathTemplateMapRepository _urlPathRepository;
private readonly IHostUrlMapRepository _hostUrlRepository; private readonly IHostUrlMapRepository _hostUrlRepository;
private readonly IUpstreamUrlPathTemplateVariableReplacer _urlReplacer;
public ProxyMiddleware(RequestDelegate next, public ProxyMiddleware(RequestDelegate next,
IUrlPathToUrlPathTemplateMatcher urlMatcher, IUrlPathToUrlPathTemplateMatcher urlMatcher,
IUrlPathTemplateMapRepository urlPathRepository, IUrlPathTemplateMapRepository urlPathRepository,
IHostUrlMapRepository hostUrlRepository) IHostUrlMapRepository hostUrlRepository,
IUpstreamUrlPathTemplateVariableReplacer urlReplacer)
{ {
_next = next; _next = next;
_urlMatcher = urlMatcher; _urlMatcher = urlMatcher;
_urlPathRepository = urlPathRepository; _urlPathRepository = urlPathRepository;
_hostUrlRepository = hostUrlRepository; _hostUrlRepository = hostUrlRepository;
_urlReplacer = urlReplacer;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
@ -29,18 +33,18 @@ namespace Ocelot.Library.Middleware
var path = context.Request.Path.ToString(); var path = context.Request.Path.ToString();
var templates = _urlPathRepository.All; var urlPathTemplateMaps = _urlPathRepository.All;
UrlPathMatch urlPathMatch = null; UrlPathMatch urlPathMatch = null;
string upstreamPathUrl = string.Empty; string upstreamPathUrlTemplate = string.Empty;
foreach (var template in templates.Data) foreach (var template in urlPathTemplateMaps.Data)
{ {
urlPathMatch = _urlMatcher.Match(path, template.DownstreamUrlPathTemplate); urlPathMatch = _urlMatcher.Match(path, template.DownstreamUrlPathTemplate);
if (urlPathMatch.Match) if (urlPathMatch.Match)
{ {
upstreamPathUrl = template.UpstreamUrlPathTemplate; upstreamPathUrlTemplate = template.UpstreamUrlPathTemplate;
break; break;
} }
} }
@ -50,11 +54,11 @@ namespace Ocelot.Library.Middleware
throw new Exception("BOOOM TING! no match"); throw new Exception("BOOOM TING! no match");
} }
var upstreamHostUrl = _hostUrlRepository.GetBaseUrlMap(urlPathMatch.UrlPathTemplate); var upstreamHostUrl = _hostUrlRepository.GetBaseUrlMap(urlPathMatch.DownstreamUrlPathTemplate);
//now map the variables from the url path to the upstream url path
var pathUrl = _urlReplacer.ReplaceTemplateVariable(upstreamPathUrlTemplate, urlPathMatch);
//make a http request to this endpoint...maybe bring in a library
await _next.Invoke(context); await _next.Invoke(context);
} }

View File

@ -0,0 +1,137 @@
using System.Collections.Generic;
using Ocelot.Library.Infrastructure.UrlPathMatcher;
using Ocelot.Library.Infrastructure.UrlPathReplacer;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests
{
public class UpstreamUrlPathTemplateVariableReplacerTests
{
private string _upstreamUrlPath;
private UrlPathMatch _urlPathMatch;
private string _result;
private IUpstreamUrlPathTemplateVariableReplacer _upstreamUrlPathReplacer;
public UpstreamUrlPathTemplateVariableReplacerTests()
{
_upstreamUrlPathReplacer = new UpstreamUrlPathTemplateVariableReplacer();
}
[Fact]
public void can_replace_no_template_variables()
{
GivenThereIsAnUpstreamUrlPath("");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List<TemplateVariableNameAndValue>(), ""));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("");
}
[Fact]
public void can_replace_url_no_slash()
{
GivenThereIsAnUpstreamUrlPath("api");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List<TemplateVariableNameAndValue>(), "api"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("api");
}
[Fact]
public void can_replace_url_one_slash()
{
GivenThereIsAnUpstreamUrlPath("api/");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List<TemplateVariableNameAndValue>(), "api/"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("api/");
}
[Fact]
public void can_replace_url_multiple_slash()
{
GivenThereIsAnUpstreamUrlPath("api/product/products/");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List<TemplateVariableNameAndValue>(), "api/product/products/"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("api/product/products/");
}
[Fact]
public void can_replace_url_one_template_variable()
{
var templateVariables = new List<TemplateVariableNameAndValue>()
{
new TemplateVariableNameAndValue("{productId}", "1")
};
GivenThereIsAnUpstreamUrlPath("productservice/products/{productId}/");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, templateVariables, "api/products/{productId}/"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("productservice/products/1/");
}
[Fact]
public void can_replace_url_one_template_variable_with_path_after()
{
var templateVariables = new List<TemplateVariableNameAndValue>()
{
new TemplateVariableNameAndValue("{productId}", "1")
};
GivenThereIsAnUpstreamUrlPath("productservice/products/{productId}/variants");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, templateVariables, "api/products/{productId}/"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("productservice/products/1/variants");
}
[Fact]
public void can_replace_url_two_template_variable()
{
var templateVariables = new List<TemplateVariableNameAndValue>()
{
new TemplateVariableNameAndValue("{productId}", "1"),
new TemplateVariableNameAndValue("{variantId}", "12")
};
GivenThereIsAnUpstreamUrlPath("productservice/products/{productId}/variants/{variantId}");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, templateVariables, "api/products/{productId}/{variantId}"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("productservice/products/1/variants/12");
}
[Fact]
public void can_replace_url_three_template_variable()
{
var templateVariables = new List<TemplateVariableNameAndValue>()
{
new TemplateVariableNameAndValue("{productId}", "1"),
new TemplateVariableNameAndValue("{variantId}", "12"),
new TemplateVariableNameAndValue("{categoryId}", "34")
};
GivenThereIsAnUpstreamUrlPath("productservice/category/{categoryId}/products/{productId}/variants/{variantId}");
GivenThereIsAUrlPathMatch(new UrlPathMatch(true, templateVariables, "api/products/{categoryId}/{productId}/{variantId}"));
WhenIReplaceTheTemplateVariables();
ThenTheUpstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12");
}
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
{
_upstreamUrlPath = upstreamUrlPath;
}
private void GivenThereIsAUrlPathMatch(UrlPathMatch urlPathMatch)
{
_urlPathMatch = urlPathMatch;
}
private void WhenIReplaceTheTemplateVariables()
{
_result = _upstreamUrlPathReplacer.ReplaceTemplateVariable(_upstreamUrlPath, _urlPathMatch);
}
private void ThenTheUpstreamUrlPathIsReturned(string expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -18,6 +18,50 @@ namespace Ocelot.UnitTests
_urlMapper = new UrlPathToUrlPathTemplateMatcher(); _urlMapper = new UrlPathToUrlPathTemplateMatcher();
} }
[Fact]
public void can_match_down_stream_url()
{
GivenIHaveADownstreamPath("");
GivenIHaveAnDownstreamPathTemplate("");
WhenIMatchThePaths();
ThenTheResultIsTrue();
ThenTheTemplatesDictionaryIs(new List<TemplateVariableNameAndValue>());
ThenTheUrlPathTemplateIs("");
}
[Fact]
public void can_match_down_stream_url_with_no_slash()
{
GivenIHaveADownstreamPath("api");
GivenIHaveAnDownstreamPathTemplate("api");
WhenIMatchThePaths();
ThenTheResultIsTrue();
ThenTheTemplatesDictionaryIs(new List<TemplateVariableNameAndValue>());
ThenTheUrlPathTemplateIs("api");
}
[Fact]
public void can_match_down_stream_url_with_one_slash()
{
GivenIHaveADownstreamPath("api/");
GivenIHaveAnDownstreamPathTemplate("api/");
WhenIMatchThePaths();
ThenTheResultIsTrue();
ThenTheTemplatesDictionaryIs(new List<TemplateVariableNameAndValue>());
ThenTheUrlPathTemplateIs("api/");
}
[Fact]
public void can_match_down_stream_url_with_downstream_template()
{
GivenIHaveADownstreamPath("api/product/products/");
GivenIHaveAnDownstreamPathTemplate("api/product/products/");
WhenIMatchThePaths();
ThenTheResultIsTrue();
ThenTheTemplatesDictionaryIs(new List<TemplateVariableNameAndValue>());
ThenTheUrlPathTemplateIs("api/product/products/");
}
[Fact] [Fact]
public void can_match_down_stream_url_with_downstream_template_with_one_query_string_parameter() public void can_match_down_stream_url_with_downstream_template_with_one_query_string_parameter()
{ {
@ -34,7 +78,7 @@ namespace Ocelot.UnitTests
{ {
var expectedTemplates = new List<TemplateVariableNameAndValue> var expectedTemplates = new List<TemplateVariableNameAndValue>
{ {
new TemplateVariableNameAndValue("{productid}", "1") new TemplateVariableNameAndValue("{productId}", "1")
}; };
GivenIHaveADownstreamPath("api/product/products/1/variants/?soldout=false"); GivenIHaveADownstreamPath("api/product/products/1/variants/?soldout=false");
@ -50,7 +94,7 @@ namespace Ocelot.UnitTests
{ {
var expectedTemplates = new List<TemplateVariableNameAndValue> var expectedTemplates = new List<TemplateVariableNameAndValue>
{ {
new TemplateVariableNameAndValue("{productid}", "1") new TemplateVariableNameAndValue("{productId}", "1")
}; };
GivenIHaveADownstreamPath("api/product/products/1"); GivenIHaveADownstreamPath("api/product/products/1");
@ -67,8 +111,8 @@ namespace Ocelot.UnitTests
{ {
var expectedTemplates = new List<TemplateVariableNameAndValue> var expectedTemplates = new List<TemplateVariableNameAndValue>
{ {
new TemplateVariableNameAndValue("{productid}", "1"), new TemplateVariableNameAndValue("{productId}", "1"),
new TemplateVariableNameAndValue("{categoryid}", "2") new TemplateVariableNameAndValue("{categoryId}", "2")
}; };
GivenIHaveADownstreamPath("api/product/products/1/2"); GivenIHaveADownstreamPath("api/product/products/1/2");
@ -85,8 +129,8 @@ namespace Ocelot.UnitTests
{ {
var expectedTemplates = new List<TemplateVariableNameAndValue> var expectedTemplates = new List<TemplateVariableNameAndValue>
{ {
new TemplateVariableNameAndValue("{productid}", "1"), new TemplateVariableNameAndValue("{productId}", "1"),
new TemplateVariableNameAndValue("{categoryid}", "2") new TemplateVariableNameAndValue("{categoryId}", "2")
}; };
GivenIHaveADownstreamPath("api/product/products/1/categories/2"); GivenIHaveADownstreamPath("api/product/products/1/categories/2");
@ -103,9 +147,9 @@ namespace Ocelot.UnitTests
{ {
var expectedTemplates = new List<TemplateVariableNameAndValue> var expectedTemplates = new List<TemplateVariableNameAndValue>
{ {
new TemplateVariableNameAndValue("{productid}", "1"), new TemplateVariableNameAndValue("{productId}", "1"),
new TemplateVariableNameAndValue("{categoryid}", "2"), new TemplateVariableNameAndValue("{categoryId}", "2"),
new TemplateVariableNameAndValue("{variantid}", "123") new TemplateVariableNameAndValue("{variantId}", "123")
}; };
GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/123"); GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/123");
@ -122,8 +166,8 @@ namespace Ocelot.UnitTests
{ {
var expectedTemplates = new List<TemplateVariableNameAndValue> var expectedTemplates = new List<TemplateVariableNameAndValue>
{ {
new TemplateVariableNameAndValue("{productid}", "1"), new TemplateVariableNameAndValue("{productId}", "1"),
new TemplateVariableNameAndValue("{categoryid}", "2") new TemplateVariableNameAndValue("{categoryId}", "2")
}; };
GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/"); GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/");
@ -147,7 +191,7 @@ namespace Ocelot.UnitTests
private void ThenTheUrlPathTemplateIs(string expectedUrlPathTemplate) private void ThenTheUrlPathTemplateIs(string expectedUrlPathTemplate)
{ {
_result.UrlPathTemplate.ShouldBe(expectedUrlPathTemplate); _result.DownstreamUrlPathTemplate.ShouldBe(expectedUrlPathTemplate);
} }
private void GivenIHaveADownstreamPath(string downstreamPath) private void GivenIHaveADownstreamPath(string downstreamPath)
{ {