mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-10-31 10:19:26 +08:00 
			
		
		
		
	Added url replacer
This commit is contained in:
		| @@ -2,6 +2,6 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher | ||||
| { | ||||
|      public interface IUrlPathToUrlPathTemplateMatcher | ||||
|      { | ||||
|         UrlPathMatch Match(string urlPath, string urlPathTemplate); | ||||
|         UrlPathMatch Match(string downstreamUrlPath, string downStreamUrlPathTemplate); | ||||
|      } | ||||
| }  | ||||
| @@ -4,15 +4,14 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher | ||||
| { | ||||
|     public class UrlPathMatch | ||||
|     { | ||||
|         public UrlPathMatch(bool match, List<TemplateVariableNameAndValue> templateVariableNameAndValues, string urlPathTemplate) | ||||
|         public UrlPathMatch(bool match, List<TemplateVariableNameAndValue> templateVariableNameAndValues, string downstreamUrlPathTemplate) | ||||
|         { | ||||
|             Match = match;  | ||||
|             TemplateVariableNameAndValues = templateVariableNameAndValues; | ||||
|             UrlPathTemplate = urlPathTemplate; | ||||
|             DownstreamUrlPathTemplate = downstreamUrlPathTemplate; | ||||
|         } | ||||
|         public bool Match {get;private set;} | ||||
|         public List<TemplateVariableNameAndValue> TemplateVariableNameAndValues {get;private set;} | ||||
|  | ||||
|         public string UrlPathTemplate {get;private set;} | ||||
|         public string DownstreamUrlPathTemplate {get;private set;} | ||||
|     } | ||||
| } | ||||
| @@ -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<TemplateVariableNameAndValue>(); | ||||
|  | ||||
|             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; | ||||
|                     }  | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| using Ocelot.Library.Infrastructure.UrlPathMatcher; | ||||
|  | ||||
| namespace Ocelot.Library.Infrastructure.UrlPathReplacer | ||||
| { | ||||
|     public interface IUpstreamUrlPathTemplateVariableReplacer | ||||
|     { | ||||
|         string ReplaceTemplateVariable(string upstreamPathTemplate, UrlPathMatch urlPathMatch); | ||||
|          | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|         } | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -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<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] | ||||
|         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>  | ||||
|             { | ||||
|                 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<TemplateVariableNameAndValue>  | ||||
|             { | ||||
|                 new TemplateVariableNameAndValue("{productid}", "1") | ||||
|                 new TemplateVariableNameAndValue("{productId}", "1") | ||||
|             }; | ||||
|  | ||||
|             GivenIHaveADownstreamPath("api/product/products/1"); | ||||
| @@ -67,8 +111,8 @@ namespace Ocelot.UnitTests | ||||
|         { | ||||
|             var expectedTemplates = new List<TemplateVariableNameAndValue>  | ||||
|             { | ||||
|                 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<TemplateVariableNameAndValue>  | ||||
|             { | ||||
|                 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<TemplateVariableNameAndValue>  | ||||
|             { | ||||
|                 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<TemplateVariableNameAndValue>  | ||||
|             { | ||||
|                 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) | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tom Gardham-Pallister
					Tom Gardham-Pallister