diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs index a5a9e25a..5ed4d637 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs @@ -2,6 +2,6 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { public interface IUrlPathToUrlPathTemplateMatcher { - UrlPathMatch Match(string urlPath, string urlPathTemplate); + UrlPathMatch Match(string downstreamUrlPath, string downStreamUrlPathTemplate); } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs index e7f21430..7f474e62 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs @@ -4,15 +4,14 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { public class UrlPathMatch { - public UrlPathMatch(bool match, List templateVariableNameAndValues, string urlPathTemplate) + public UrlPathMatch(bool match, List templateVariableNameAndValues, string downstreamUrlPathTemplate) { Match = match; TemplateVariableNameAndValues = templateVariableNameAndValues; - UrlPathTemplate = urlPathTemplate; + DownstreamUrlPathTemplate = downstreamUrlPathTemplate; } public bool Match {get;private set;} public List TemplateVariableNameAndValues {get;private set;} - - public string UrlPathTemplate {get;private set;} + public string DownstreamUrlPathTemplate {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs index 8ca2ded1..3b2152e8 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs @@ -5,35 +5,31 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { 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(); - urlPath = urlPath.ToLower(); - - urlPathTemplate = urlPathTemplate.ToLower(); - 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); templateKeysAndValues.Add(templateVariableNameAndValue); - counterForTemplate = GetNextCounterPosition(urlPathTemplate, counterForTemplate, '}'); + counterForTemplate = GetNextCounterPosition(downstreamUrlPathTemplate, counterForTemplate, '}'); - counterForUrl = GetNextCounterPosition(urlPath, counterForUrl, '/'); + counterForUrl = GetNextCounterPosition(downstreamUrlPath, counterForUrl, '/'); continue; } diff --git a/src/Ocelot.Library/Infrastructure/UrlPathReplacer/IUpstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot.Library/Infrastructure/UrlPathReplacer/IUpstreamUrlPathTemplateVariableReplacer.cs new file mode 100644 index 00000000..cb455185 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/UrlPathReplacer/IUpstreamUrlPathTemplateVariableReplacer.cs @@ -0,0 +1,10 @@ +using Ocelot.Library.Infrastructure.UrlPathMatcher; + +namespace Ocelot.Library.Infrastructure.UrlPathReplacer +{ + public interface IUpstreamUrlPathTemplateVariableReplacer + { + string ReplaceTemplateVariable(string upstreamPathTemplate, UrlPathMatch urlPathMatch); + + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathReplacer/UpstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot.Library/Infrastructure/UrlPathReplacer/UpstreamUrlPathTemplateVariableReplacer.cs new file mode 100644 index 00000000..b52ea69e --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/UrlPathReplacer/UpstreamUrlPathTemplateVariableReplacer.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/ProxyMiddleware.cs b/src/Ocelot.Library/Middleware/ProxyMiddleware.cs index 6c7ad7ef..cb5eb149 100644 --- a/src/Ocelot.Library/Middleware/ProxyMiddleware.cs +++ b/src/Ocelot.Library/Middleware/ProxyMiddleware.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Library.Infrastructure.HostUrlRepository; using Ocelot.Library.Infrastructure.UrlPathMatcher; +using Ocelot.Library.Infrastructure.UrlPathReplacer; using Ocelot.Library.Infrastructure.UrlPathTemplateRepository; namespace Ocelot.Library.Middleware @@ -13,15 +14,18 @@ namespace Ocelot.Library.Middleware private readonly IUrlPathToUrlPathTemplateMatcher _urlMatcher; private readonly IUrlPathTemplateMapRepository _urlPathRepository; private readonly IHostUrlMapRepository _hostUrlRepository; + private readonly IUpstreamUrlPathTemplateVariableReplacer _urlReplacer; public ProxyMiddleware(RequestDelegate next, IUrlPathToUrlPathTemplateMatcher urlMatcher, IUrlPathTemplateMapRepository urlPathRepository, - IHostUrlMapRepository hostUrlRepository) + IHostUrlMapRepository hostUrlRepository, + IUpstreamUrlPathTemplateVariableReplacer urlReplacer) { _next = next; _urlMatcher = urlMatcher; _urlPathRepository = urlPathRepository; _hostUrlRepository = hostUrlRepository; + _urlReplacer = urlReplacer; } public async Task Invoke(HttpContext context) @@ -29,18 +33,18 @@ namespace Ocelot.Library.Middleware var path = context.Request.Path.ToString(); - var templates = _urlPathRepository.All; + var urlPathTemplateMaps = _urlPathRepository.All; 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); if (urlPathMatch.Match) { - upstreamPathUrl = template.UpstreamUrlPathTemplate; + upstreamPathUrlTemplate = template.UpstreamUrlPathTemplate; break; } } @@ -50,11 +54,11 @@ namespace Ocelot.Library.Middleware 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); } diff --git a/test/Ocelot.UnitTests/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/UpstreamUrlPathTemplateVariableReplacerTests.cs new file mode 100644 index 00000000..31811406 --- /dev/null +++ b/test/Ocelot.UnitTests/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -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(), "")); + WhenIReplaceTheTemplateVariables(); + ThenTheUpstreamUrlPathIsReturned(""); + } + + [Fact] + public void can_replace_url_no_slash() + { + GivenThereIsAnUpstreamUrlPath("api"); + GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List(), "api")); + WhenIReplaceTheTemplateVariables(); + ThenTheUpstreamUrlPathIsReturned("api"); + } + + [Fact] + public void can_replace_url_one_slash() + { + GivenThereIsAnUpstreamUrlPath("api/"); + GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List(), "api/")); + WhenIReplaceTheTemplateVariables(); + ThenTheUpstreamUrlPathIsReturned("api/"); + } + + [Fact] + public void can_replace_url_multiple_slash() + { + GivenThereIsAnUpstreamUrlPath("api/product/products/"); + GivenThereIsAUrlPathMatch(new UrlPathMatch(true, new List(), "api/product/products/")); + WhenIReplaceTheTemplateVariables(); + ThenTheUpstreamUrlPathIsReturned("api/product/products/"); + } + + [Fact] + public void can_replace_url_one_template_variable() + { + var templateVariables = new List() + { + 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() + { + 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() + { + 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() + { + 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); + } + + } +} diff --git a/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs b/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs index 1219476a..4cf8a87f 100644 --- a/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs +++ b/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs @@ -18,6 +18,50 @@ namespace Ocelot.UnitTests _urlMapper = new UrlPathToUrlPathTemplateMatcher(); } + [Fact] + public void can_match_down_stream_url() + { + GivenIHaveADownstreamPath(""); + GivenIHaveAnDownstreamPathTemplate(""); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(new List()); + ThenTheUrlPathTemplateIs(""); + } + + [Fact] + public void can_match_down_stream_url_with_no_slash() + { + GivenIHaveADownstreamPath("api"); + GivenIHaveAnDownstreamPathTemplate("api"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(new List()); + ThenTheUrlPathTemplateIs("api"); + } + + [Fact] + public void can_match_down_stream_url_with_one_slash() + { + GivenIHaveADownstreamPath("api/"); + GivenIHaveAnDownstreamPathTemplate("api/"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(new List()); + 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()); + ThenTheUrlPathTemplateIs("api/product/products/"); + } + [Fact] 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 { - new TemplateVariableNameAndValue("{productid}", "1") + new TemplateVariableNameAndValue("{productId}", "1") }; GivenIHaveADownstreamPath("api/product/products/1/variants/?soldout=false"); @@ -50,7 +94,7 @@ namespace Ocelot.UnitTests { var expectedTemplates = new List { - new TemplateVariableNameAndValue("{productid}", "1") + new TemplateVariableNameAndValue("{productId}", "1") }; GivenIHaveADownstreamPath("api/product/products/1"); @@ -67,8 +111,8 @@ namespace Ocelot.UnitTests { var expectedTemplates = new List { - new TemplateVariableNameAndValue("{productid}", "1"), - new TemplateVariableNameAndValue("{categoryid}", "2") + new TemplateVariableNameAndValue("{productId}", "1"), + new TemplateVariableNameAndValue("{categoryId}", "2") }; GivenIHaveADownstreamPath("api/product/products/1/2"); @@ -85,8 +129,8 @@ namespace Ocelot.UnitTests { var expectedTemplates = new List { - new TemplateVariableNameAndValue("{productid}", "1"), - new TemplateVariableNameAndValue("{categoryid}", "2") + new TemplateVariableNameAndValue("{productId}", "1"), + new TemplateVariableNameAndValue("{categoryId}", "2") }; GivenIHaveADownstreamPath("api/product/products/1/categories/2"); @@ -103,9 +147,9 @@ namespace Ocelot.UnitTests { var expectedTemplates = new List { - new TemplateVariableNameAndValue("{productid}", "1"), - new TemplateVariableNameAndValue("{categoryid}", "2"), - new TemplateVariableNameAndValue("{variantid}", "123") + new TemplateVariableNameAndValue("{productId}", "1"), + new TemplateVariableNameAndValue("{categoryId}", "2"), + new TemplateVariableNameAndValue("{variantId}", "123") }; GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/123"); @@ -122,8 +166,8 @@ namespace Ocelot.UnitTests { var expectedTemplates = new List { - new TemplateVariableNameAndValue("{productid}", "1"), - new TemplateVariableNameAndValue("{categoryid}", "2") + new TemplateVariableNameAndValue("{productId}", "1"), + new TemplateVariableNameAndValue("{categoryId}", "2") }; GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/"); @@ -147,7 +191,7 @@ namespace Ocelot.UnitTests private void ThenTheUrlPathTemplateIs(string expectedUrlPathTemplate) { - _result.UrlPathTemplate.ShouldBe(expectedUrlPathTemplate); + _result.DownstreamUrlPathTemplate.ShouldBe(expectedUrlPathTemplate); } private void GivenIHaveADownstreamPath(string downstreamPath) {