mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 20:10:50 +08:00 
			
		
		
		
	Checkin for caching the template matching for significant route finder performance improvements (#728)
This commit is contained in:
		
				
					committed by
					
						
						Marcelo Castagna
					
				
			
			
				
	
			
			
			
						parent
						
							ac211886f1
						
					
				
				
					commit
					9bbb6364f2
				
			@@ -28,7 +28,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
 | 
			
		||||
 | 
			
		||||
            foreach (var reRoute in applicableReRoutes)
 | 
			
		||||
            {
 | 
			
		||||
                var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern.Template, reRoute.UpstreamTemplatePattern.ContainsQueryString);
 | 
			
		||||
                var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern);
 | 
			
		||||
 | 
			
		||||
                if (urlMatch.Data.Match)
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
 | 
			
		||||
{
 | 
			
		||||
    public interface IUrlPathToUrlTemplateMatcher
 | 
			
		||||
     {
 | 
			
		||||
        Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString);
 | 
			
		||||
     }
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using Ocelot.Values;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
 | 
			
		||||
{
 | 
			
		||||
    public interface IUrlPathToUrlTemplateMatcher
 | 
			
		||||
     {
 | 
			
		||||
        Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, UpstreamPathTemplate pathTemplate);
 | 
			
		||||
     }
 | 
			
		||||
} 
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,23 @@
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
 | 
			
		||||
{
 | 
			
		||||
    public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
 | 
			
		||||
    {
 | 
			
		||||
        public Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString)
 | 
			
		||||
        {
 | 
			
		||||
            var regex = new Regex(upstreamUrlPathTemplate);
 | 
			
		||||
 | 
			
		||||
            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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using Ocelot.Values;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
 | 
			
		||||
{
 | 
			
		||||
    public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
 | 
			
		||||
    {
 | 
			
		||||
        public Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, UpstreamPathTemplate pathTemplate)
 | 
			
		||||
        {
 | 
			
		||||
            if (!pathTemplate.ContainsQueryString)
 | 
			
		||||
            {
 | 
			
		||||
                return pathTemplate.Pattern.IsMatch(upstreamUrlPath)
 | 
			
		||||
                    ? new OkResponse<UrlMatch>(new UrlMatch(true))
 | 
			
		||||
                    : new OkResponse<UrlMatch>(new UrlMatch(false));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return pathTemplate.Pattern.IsMatch($"{upstreamUrlPath}{upstreamQueryString}") 
 | 
			
		||||
                ? new OkResponse<UrlMatch>(new UrlMatch(true)) 
 | 
			
		||||
                : new OkResponse<UrlMatch>(new UrlMatch(false));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,28 @@
 | 
			
		||||
namespace Ocelot.Values
 | 
			
		||||
{
 | 
			
		||||
    public class UpstreamPathTemplate
 | 
			
		||||
    {
 | 
			
		||||
        public UpstreamPathTemplate(string template, int priority, bool containsQueryString, string originalValue)
 | 
			
		||||
        {
 | 
			
		||||
            Template = template;
 | 
			
		||||
            Priority = priority;
 | 
			
		||||
            ContainsQueryString = containsQueryString;
 | 
			
		||||
            OriginalValue = originalValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Template { get; }
 | 
			
		||||
 | 
			
		||||
        public int Priority { get; }
 | 
			
		||||
 | 
			
		||||
        public bool ContainsQueryString { get; }
 | 
			
		||||
 | 
			
		||||
        public string OriginalValue { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Values
 | 
			
		||||
{
 | 
			
		||||
    public class UpstreamPathTemplate
 | 
			
		||||
    {
 | 
			
		||||
        public UpstreamPathTemplate(string template, int priority, bool containsQueryString, string originalValue)
 | 
			
		||||
        {
 | 
			
		||||
            Template = template;
 | 
			
		||||
            Priority = priority;
 | 
			
		||||
            ContainsQueryString = containsQueryString;
 | 
			
		||||
            OriginalValue = originalValue;
 | 
			
		||||
            Pattern = template == null ? 
 | 
			
		||||
                new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : 
 | 
			
		||||
                new Regex(template, RegexOptions.Compiled | RegexOptions.Singleline);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Template { get; }
 | 
			
		||||
 | 
			
		||||
        public int Priority { get; }
 | 
			
		||||
 | 
			
		||||
        public bool ContainsQueryString { get; }
 | 
			
		||||
 | 
			
		||||
        public string OriginalValue { get; }
 | 
			
		||||
        
 | 
			
		||||
        public Regex Pattern { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,51 +1,52 @@
 | 
			
		||||
using System;
 | 
			
		||||
using BenchmarkDotNet.Attributes;
 | 
			
		||||
using BenchmarkDotNet.Columns;
 | 
			
		||||
using BenchmarkDotNet.Configs;
 | 
			
		||||
using BenchmarkDotNet.Diagnosers;
 | 
			
		||||
using BenchmarkDotNet.Validators;
 | 
			
		||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Benchmarks
 | 
			
		||||
{
 | 
			
		||||
    [Config(typeof(UrlPathToUrlPathTemplateMatcherBenchmarks))]
 | 
			
		||||
    public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig
 | 
			
		||||
    {
 | 
			
		||||
        private RegExUrlMatcher _urlPathMatcher;
 | 
			
		||||
        private string _downstreamUrlPath;
 | 
			
		||||
        private string _downstreamUrlPathTemplate;
 | 
			
		||||
        private string _upstreamQuery;
 | 
			
		||||
 | 
			
		||||
        public UrlPathToUrlPathTemplateMatcherBenchmarks()
 | 
			
		||||
        {
 | 
			
		||||
            Add(StatisticColumn.AllStatistics);
 | 
			
		||||
            Add(MemoryDiagnoser.Default);
 | 
			
		||||
            Add(BaselineValidator.FailOnError);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [GlobalSetup]
 | 
			
		||||
        public void SetUp()
 | 
			
		||||
        {
 | 
			
		||||
            _urlPathMatcher = new RegExUrlMatcher();
 | 
			
		||||
            _downstreamUrlPath = "api/product/products/1/variants/?soldout=false";
 | 
			
		||||
            _downstreamUrlPathTemplate = "api/product/products/{productId}/variants/";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Benchmark(Baseline = true)]
 | 
			
		||||
        public void Baseline()
 | 
			
		||||
        {
 | 
			
		||||
            _urlPathMatcher.Match(_downstreamUrlPath, _upstreamQuery, _downstreamUrlPathTemplate, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // * Summary *
 | 
			
		||||
 | 
			
		||||
        // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0]
 | 
			
		||||
        // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores
 | 
			
		||||
        // .NET Core SDK=2.1.4
 | 
			
		||||
        //   [Host]     : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
 | 
			
		||||
        //   DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
 | 
			
		||||
        //      Method |     Mean |     Error |    StdDev |    StdErr |      Min |       Q1 |   Median |       Q3 |      Max |      Op/s |
 | 
			
		||||
        // ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:|
 | 
			
		||||
        //  Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 |
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
using System;
 | 
			
		||||
using BenchmarkDotNet.Attributes;
 | 
			
		||||
using BenchmarkDotNet.Columns;
 | 
			
		||||
using BenchmarkDotNet.Configs;
 | 
			
		||||
using BenchmarkDotNet.Diagnosers;
 | 
			
		||||
using BenchmarkDotNet.Validators;
 | 
			
		||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
 | 
			
		||||
using Ocelot.Values;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Benchmarks
 | 
			
		||||
{
 | 
			
		||||
    [Config(typeof(UrlPathToUrlPathTemplateMatcherBenchmarks))]
 | 
			
		||||
    public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig
 | 
			
		||||
    {
 | 
			
		||||
        private RegExUrlMatcher _urlPathMatcher;
 | 
			
		||||
        private UpstreamPathTemplate _pathTemplate;
 | 
			
		||||
        private string _downstreamUrlPath;
 | 
			
		||||
        private string _upstreamQuery;
 | 
			
		||||
 | 
			
		||||
        public UrlPathToUrlPathTemplateMatcherBenchmarks()
 | 
			
		||||
        {
 | 
			
		||||
            Add(StatisticColumn.AllStatistics);
 | 
			
		||||
            Add(MemoryDiagnoser.Default);
 | 
			
		||||
            Add(BaselineValidator.FailOnError);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [GlobalSetup]
 | 
			
		||||
        public void SetUp()
 | 
			
		||||
        {
 | 
			
		||||
            _urlPathMatcher = new RegExUrlMatcher();
 | 
			
		||||
            _pathTemplate = new UpstreamPathTemplate("api/product/products/{productId}/variants/", 0, false, null);
 | 
			
		||||
            _downstreamUrlPath = "api/product/products/1/variants/?soldout=false";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Benchmark(Baseline = true)]
 | 
			
		||||
        public void Baseline()
 | 
			
		||||
        {
 | 
			
		||||
            _urlPathMatcher.Match(_downstreamUrlPath, _upstreamQuery, _pathTemplate);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // * Summary *
 | 
			
		||||
 | 
			
		||||
        // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0]
 | 
			
		||||
        // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores
 | 
			
		||||
        // .NET Core SDK=2.1.4
 | 
			
		||||
        //   [Host]     : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
 | 
			
		||||
        //   DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
 | 
			
		||||
        //      Method |     Mean |     Error |    StdDev |    StdErr |      Min |       Q1 |   Median |       Q3 |      Max |      Op/s |
 | 
			
		||||
        // ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:|
 | 
			
		||||
        //  Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 |
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -624,7 +624,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
 | 
			
		||||
                .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
 | 
			
		||||
                .And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
 | 
			
		||||
                .When(x => x.WhenICallTheFinder())
 | 
			
		||||
                .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1))
 | 
			
		||||
                .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -677,7 +677,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
 | 
			
		||||
                            .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test"))
 | 
			
		||||
                            .Build()
 | 
			
		||||
                    )))
 | 
			
		||||
                .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2))
 | 
			
		||||
                .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0))
 | 
			
		||||
                .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 1))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -706,32 +707,32 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
 | 
			
		||||
        private void ThenTheUrlMatcherIsCalledCorrectly()
 | 
			
		||||
        {
 | 
			
		||||
            _mockMatcher
 | 
			
		||||
                .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.Template, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once);
 | 
			
		||||
                .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern), Times.Once);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheUrlMatcherIsCalledCorrectly(int times)
 | 
			
		||||
        private void ThenTheUrlMatcherIsCalledCorrectly(int times, int index = 0)
 | 
			
		||||
        {
 | 
			
		||||
            _mockMatcher
 | 
			
		||||
                .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Exactly(times));
 | 
			
		||||
                .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[index].UpstreamTemplatePattern), Times.Exactly(times));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath)
 | 
			
		||||
        {
 | 
			
		||||
            _mockMatcher
 | 
			
		||||
                .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once);
 | 
			
		||||
                .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern), Times.Once);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheUrlMatcherIsNotCalled()
 | 
			
		||||
        {
 | 
			
		||||
            _mockMatcher
 | 
			
		||||
                .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Never);
 | 
			
		||||
                .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern), Times.Never);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheUrlMatcherReturns(Response<UrlMatch> match)
 | 
			
		||||
        {
 | 
			
		||||
            _match = match;
 | 
			
		||||
            _mockMatcher
 | 
			
		||||
                .Setup(x => x.Match(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
 | 
			
		||||
                .Setup(x => x.Match(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<UpstreamPathTemplate>()))
 | 
			
		||||
                .Returns(_match);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using Ocelot.Values;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
@@ -270,7 +271,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher
 | 
			
		||||
 | 
			
		||||
        private void WhenIMatchThePaths()
 | 
			
		||||
        {
 | 
			
		||||
            _result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString);
 | 
			
		||||
            _result = _urlMatcher.Match(_path, _queryString, new UpstreamPathTemplate(_downstreamPathTemplate, 0, _containsQueryString, _downstreamPathTemplate));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheResultIsTrue()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user