From d78d2972e72b4813c12ad7fb5af6b40a030799bd Mon Sep 17 00:00:00 2001 From: Lilith River Date: Fri, 26 Jan 2024 11:14:05 -0700 Subject: [PATCH] WIP --- .../MiddlewareOptionsServerBuilder.cs | 19 +- .../ImageflowMiddlewareOptions.cs | 9 +- .../LegacyOptions/PathMapping.cs | 8 + .../Internal/ExtensionlessPath.cs | 10 - .../Engine/ExtensionlessPath.cs | 9 + src/Imazen.Routing/Engine/RoutingBuilder.cs | 73 ++++++-- src/Imazen.Routing/Engine/RoutingEngine.cs | 12 +- src/Imazen.Routing/Helpers/PathHelpers.cs | 7 +- src/Imazen.Routing/Layers/Conditions.cs | 172 +++++++++++------- src/Imazen.Routing/Layers/LocalFilesLayer.cs | 19 +- .../Routing/Serving/ImageServerTests.cs | 4 +- 11 files changed, 221 insertions(+), 121 deletions(-) delete mode 100644 src/Imazen.Routing/Compatibility/ImageflowServer/Internal/ExtensionlessPath.cs create mode 100644 src/Imazen.Routing/Engine/ExtensionlessPath.cs diff --git a/src/Imageflow.Server/Internal/MiddlewareOptionsServerBuilder.cs b/src/Imageflow.Server/Internal/MiddlewareOptionsServerBuilder.cs index 86248408..13f2bf36 100644 --- a/src/Imageflow.Server/Internal/MiddlewareOptionsServerBuilder.cs +++ b/src/Imageflow.Server/Internal/MiddlewareOptionsServerBuilder.cs @@ -99,9 +99,10 @@ public void PopulateServices() return list; }); serverContainer.Register(watermarkingLogicOptions); - - - var router = CreateRoutes(new RoutingBuilder(),mappedPaths); + + var routingBuilder = new RoutingBuilder(); + routingBuilder.SetGlobalPreconditionsToRequireImageExtensionOrExtensionlessPathPrefixes(options.ExtensionlessPaths); + var router = CreateRoutes(routingBuilder,mappedPaths); var routingEngine = router.Build(logger); @@ -138,7 +139,7 @@ public void PopulateServices() return result; }); } - private RoutingBuilder CreateRoutes(RoutingBuilder builder, List mappedPaths) + private RoutingBuilder CreateRoutes(RoutingBuilder builder, IReadOnlyCollection mappedPaths) { // signature layer @@ -185,6 +186,7 @@ private RoutingBuilder CreateRoutes(RoutingBuilder builder, List m } // Apply command defaults + // TODO: only to already processing images? if (options.CommandDefaults.Count > 0) { builder.AddLayer(new CommandDefaultsLayer(new CommandDefaultsLayerOptions() @@ -220,11 +222,10 @@ private RoutingBuilder CreateRoutes(RoutingBuilder builder, List m builder.AddLayer(new BlobProvidersLayer(blobProviders, blobWrapperProviders)); } - builder.AddLayer(diagnosticsPage); + builder.AddGlobalLayer(diagnosticsPage); - builder.SetDefaultPreconditionsToRequireImageExtension() - .AddEndpoint(Conditions.HasPathSuffix("/imageflow.ready"), + builder.AddGlobalEndpoint(Conditions.HasPathSuffix("/imageflow.ready"), (_) => { options.Licensing?.FireHeartbeat(); @@ -234,14 +235,14 @@ private RoutingBuilder CreateRoutes(RoutingBuilder builder, List m } }); - builder.AddEndpoint(Conditions.HasPathSuffix("/imageflow.health"), + builder.AddGlobalEndpoint(Conditions.HasPathSuffix("/imageflow.health"), (_) => { options.Licensing?.FireHeartbeat(); return SmallHttpResponse.NoStore(200, "Imageflow.Server is healthy."); }); - builder.AddEndpoint(Conditions.HasPathSuffix("/imageflow.license"), + builder.AddGlobalEndpoint(Conditions.HasPathSuffix("/imageflow.license"), (req) => { options.Licensing?.FireHeartbeat(); diff --git a/src/Imageflow.Server/LegacyOptions/ImageflowMiddlewareOptions.cs b/src/Imageflow.Server/LegacyOptions/ImageflowMiddlewareOptions.cs index 4044627a..56af504c 100644 --- a/src/Imageflow.Server/LegacyOptions/ImageflowMiddlewareOptions.cs +++ b/src/Imageflow.Server/LegacyOptions/ImageflowMiddlewareOptions.cs @@ -6,12 +6,9 @@ namespace Imageflow.Server { public class ImageflowMiddlewareOptions: IInfoProvider { - internal struct ExtensionlessPath - { - internal string Prefix { get; set; } + internal record struct ExtensionlessPath(string StringToCompare, StringComparison StringComparison) + : IStringAndComparison; - internal StringComparison PrefixComparison { get; set; } - } internal string? LicenseKey { get; set; } internal EnforceLicenseWith EnforcementMethod { get; set; } = EnforceLicenseWith.Http422Error; @@ -90,7 +87,7 @@ public ImageflowMiddlewareOptions AddPreset(PresetOptions preset) public ImageflowMiddlewareOptions HandleExtensionlessRequestsUnder(string prefix, StringComparison prefixComparison = StringComparison.Ordinal) { - ExtensionlessPaths.Add(new ExtensionlessPath() { Prefix = prefix, PrefixComparison = prefixComparison}); + ExtensionlessPaths.Add(new ExtensionlessPath() { StringToCompare = prefix, StringComparison = prefixComparison}); return this; } diff --git a/src/Imageflow.Server/LegacyOptions/PathMapping.cs b/src/Imageflow.Server/LegacyOptions/PathMapping.cs index 4d187b7c..25a505b8 100644 --- a/src/Imageflow.Server/LegacyOptions/PathMapping.cs +++ b/src/Imageflow.Server/LegacyOptions/PathMapping.cs @@ -20,5 +20,13 @@ public PathMapping(string virtualPath, string physicalPath, bool ignorePrefixCas public string VirtualPath { get; } public string PhysicalPath { get; } public bool IgnorePrefixCase { get; } + /// + /// Duplicate of VirtualPath for IStringAndComparison + /// + public string StringToCompare => VirtualPath; + /// + /// If IgnorePrefixCase is true, returns OrdinalIgnoreCase, otherwise Ordinal + /// + public StringComparison StringComparison => IgnorePrefixCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; } } \ No newline at end of file diff --git a/src/Imazen.Routing/Compatibility/ImageflowServer/Internal/ExtensionlessPath.cs b/src/Imazen.Routing/Compatibility/ImageflowServer/Internal/ExtensionlessPath.cs deleted file mode 100644 index f13719c3..00000000 --- a/src/Imazen.Routing/Compatibility/ImageflowServer/Internal/ExtensionlessPath.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Imazen.Routing.Compatibility.ImageflowServer.Internal -{ - - internal struct ExtensionlessPath - { - internal string Prefix { get; set; } - - internal StringComparison PrefixComparison { get; set; } - } -} \ No newline at end of file diff --git a/src/Imazen.Routing/Engine/ExtensionlessPath.cs b/src/Imazen.Routing/Engine/ExtensionlessPath.cs new file mode 100644 index 00000000..f269a1f0 --- /dev/null +++ b/src/Imazen.Routing/Engine/ExtensionlessPath.cs @@ -0,0 +1,9 @@ +using Imazen.Routing.Layers; + +namespace Imazen.Routing.Engine; + +public readonly record struct ExtensionlessPath(string Prefix, StringComparison StringComparison = StringComparison.OrdinalIgnoreCase) + : IStringAndComparison +{ + public string StringToCompare => Prefix; +} \ No newline at end of file diff --git a/src/Imazen.Routing/Engine/RoutingBuilder.cs b/src/Imazen.Routing/Engine/RoutingBuilder.cs index 20b7f97a..7021329b 100644 --- a/src/Imazen.Routing/Engine/RoutingBuilder.cs +++ b/src/Imazen.Routing/Engine/RoutingBuilder.cs @@ -8,16 +8,28 @@ namespace Imazen.Routing.Engine; public class RoutingBuilder { - - protected IFastCond? DefaultPreconditions { get; set; } + protected IFastCond? GlobalPreconditions { get; set; } protected List Layers { get; } = new List(); // set default conditions (IFastCond) // add page endpoints (IRoutingEndpoint) // Add endpoints based on suffixes or exact matches. At build time, we roll these up into a single FastCond that can be evaluated quickly. + /// + /// Adds an endpoint that also extends GlobalPreconditions + /// + /// + /// + /// + public RoutingBuilder AddGlobalEndpoint(IFastCond fastMatcher, IRoutingEndpoint endpoint) + => AddEndpoint(true, fastMatcher, endpoint); + public RoutingBuilder AddEndpoint(IFastCond fastMatcher, IRoutingEndpoint endpoint) + => AddEndpoint(false, fastMatcher, endpoint); + + private RoutingBuilder AddEndpoint(bool global, IFastCond fastMatcher, IRoutingEndpoint endpoint) { + if (global) AddAlternateGlobalPrecondition(fastMatcher); Layers.Add(new SimpleLayer(fastMatcher.ToString() ?? "(error describing route)", (request) => fastMatcher.Matches(request.MutablePath, request.ReadOnlyQueryWrapper) ? CodeResult.Ok(endpoint) : null, fastMatcher)); @@ -28,21 +40,41 @@ public RoutingBuilder AddLayer(IRoutingLayer layer) Layers.Add(layer); return this; } - + public RoutingBuilder AddGlobalLayer(IRoutingLayer layer) + { + AddAlternateGlobalPrecondition(layer.FastPreconditions ?? Conditions.True); + Layers.Add(layer); + return this; + } //Add predefined reusable responses - public RoutingBuilder AddEndpoint(IFastCond fastMatcher, IAdaptableReusableHttpResponse response) + /// + /// Adds an endpoint that also extends GlobalPreconditions + /// + /// + /// + /// + public RoutingBuilder AddGlobalEndpoint(IFastCond fastMatcher, IAdaptableReusableHttpResponse response) { var endpoint = new PredefinedResponseEndpoint(response); - return AddEndpoint(fastMatcher, endpoint); + return AddGlobalEndpoint(fastMatcher, endpoint); } - - public RoutingBuilder AddEndpoint(IFastCond fastMatcher, SmallHttpResponse response) + public RoutingBuilder AddEndpoint(IFastCond fastMatcher, IAdaptableReusableHttpResponse response) { var endpoint = new PredefinedResponseEndpoint(response); return AddEndpoint(fastMatcher, endpoint); } - // now with a delegate + /// + /// Adds an endpoint that also extends GlobalPreconditions + /// + /// + /// + /// + public RoutingBuilder AddGlobalEndpoint(IFastCond fastMatcher, Func handler) + { + var endpoint = new SyncEndpointFunc(handler); + return AddGlobalEndpoint(fastMatcher, endpoint); + } public RoutingBuilder AddEndpoint(IFastCond fastMatcher, Func handler) { @@ -53,21 +85,34 @@ public RoutingBuilder AddEndpoint(IFastCond fastMatcher, Func(IReadOnlyCollection? extensionlessPaths = null) + where T : IStringAndComparison { - DefaultPreconditions = Conditions.HasSupportedImageExtension; + if (extensionlessPaths is { Count: > 0 }) + { + GlobalPreconditions = Conditions.HasSupportedImageExtension.Or( + Conditions.PathHasNoExtension.And(Conditions.HasPathPrefix(extensionlessPaths))); + return this; + } + GlobalPreconditions = Conditions.HasSupportedImageExtension; + return this; + } + public RoutingBuilder AddAlternateGlobalPrecondition(IFastCond? fastMatcher) + { + if (fastMatcher == null) return this; + if (fastMatcher is Conditions.FastCondFalse) return this; + GlobalPreconditions = GlobalPreconditions == null ? fastMatcher : GlobalPreconditions.Or(fastMatcher); return this; } - public RoutingEngine Build(IReLogger logger) { - return new RoutingEngine(Layers.ToArray(), DefaultPreconditions ?? Conditions.True, logger); + return new RoutingEngine(Layers.ToArray(), GlobalPreconditions ?? Conditions.True, logger); } diff --git a/src/Imazen.Routing/Engine/RoutingEngine.cs b/src/Imazen.Routing/Engine/RoutingEngine.cs index 2ca4e66f..35a28b85 100644 --- a/src/Imazen.Routing/Engine/RoutingEngine.cs +++ b/src/Imazen.Routing/Engine/RoutingEngine.cs @@ -15,22 +15,22 @@ public class RoutingEngine : IBlobRequestRouter, IHasDiagnosticPageSection private readonly IRoutingLayer[] layers; private readonly IReLogger logger; - private readonly Conditions.FastCondAnyOptimized mightHandleConditions; - internal RoutingEngine(IRoutingLayer[] layers, IFastCond fastCondLogic, IReLogger logger) + private readonly IFastCond mightHandleConditions; + internal RoutingEngine(IRoutingLayer[] layers, IFastCond globalPrecondition, IReLogger logger) { this.layers = layers; this.logger = logger; // Build a list of unique fast exits we must consult. We can skip duplicates. - var uniqueFastExits = new List(4) { fastCondLogic }; + var preconditions = new List(layers.Length) { }; foreach (var layer in layers) { - if (layer.FastPreconditions != null && !uniqueFastExits.Contains(layer.FastPreconditions)) + if (layer.FastPreconditions != null && !preconditions.Contains(layer.FastPreconditions)) { - uniqueFastExits.Add(layer.FastPreconditions); + preconditions.Add(layer.FastPreconditions); } } - mightHandleConditions = new Conditions.FastCondAnyOptimized(uniqueFastExits); + mightHandleConditions = globalPrecondition.And(preconditions.AnyPrecondition()).Optimize(); } public bool MightHandleRequest(string path, TQ query) where TQ : IReadOnlyQueryWrapper diff --git a/src/Imazen.Routing/Helpers/PathHelpers.cs b/src/Imazen.Routing/Helpers/PathHelpers.cs index 9b52b3b8..4845b960 100644 --- a/src/Imazen.Routing/Helpers/PathHelpers.cs +++ b/src/Imazen.Routing/Helpers/PathHelpers.cs @@ -41,7 +41,12 @@ public static class PathHelpers internal static bool IsImagePath(string path) { - return Suffixes.Any(suffix => path.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var suffix in Suffixes) + { + if (path.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) return true; + } + return false; } public static string? SanitizeImageExtension(string extension) diff --git a/src/Imazen.Routing/Layers/Conditions.cs b/src/Imazen.Routing/Layers/Conditions.cs index 6a24490f..8c1cdf64 100644 --- a/src/Imazen.Routing/Layers/Conditions.cs +++ b/src/Imazen.Routing/Layers/Conditions.cs @@ -19,6 +19,12 @@ internal interface IFastCondCanMerge : IFastCond IFastCond? TryMerge(IFastCond other); } +public interface IStringAndComparison +{ + string StringToCompare { get; } + StringComparison StringComparison { get; } +} + public static class Conditions { public static IFastCond Optimize(this IFastCond condition) @@ -57,14 +63,24 @@ public static FastCondHasQueryStringKey HasQueryStringKey(params string[] keys) public static readonly FastCondPathHasNoExtension PathHasNoExtension = new FastCondPathHasNoExtension(); public static readonly FastCondHasSupportedImageExtension HasSupportedImageExtension = new FastCondHasSupportedImageExtension(); - + + public static IFastCond HasPathPrefix(IEnumerable prefixes) where T : IStringAndComparison + { + return prefixes.GroupBy(p => p.StringComparison) + .Select(g => (IFastCond)(new FastCondHasPathPrefixes(g.Select(p => p.StringToCompare).ToArray(), g.Key))) + .AnyPrecondition().Optimize(); + } public static FastCondHasPathPrefixes HasPathPrefixOrdinalIgnoreCase(params string[] prefixes) => new FastCondHasPathPrefixes(prefixes, StringComparison.OrdinalIgnoreCase); public static FastCondHasPathPrefixes HasPathPrefixInvariantIgnoreCase(params string[] prefixes) => new FastCondHasPathPrefixes(prefixes, StringComparison.InvariantCultureIgnoreCase); - + public static FastCondAny AnyPrecondition(this IEnumerable preconditions) where T:IFastCond + => new(preconditions.Select(c => (IFastCond)c).ToList()); + public static FastCondAny AnyPrecondition(IReadOnlyCollection preconditions) => new (preconditions); + + public static FastCondHasPathPrefixes HasPathPrefix(params string[] prefixes) => new FastCondHasPathPrefixes(prefixes, StringComparison.Ordinal); @@ -187,8 +203,10 @@ internal static string[] CombineUnique(string[] a, string[] b) } return combined.ToArray(); } - private static void FlattenRecursiveAndOptimize(List flattened, IEnumerable alternateConditions) + private static void FlattenRecursiveAndOptimize(List flattened, IEnumerable alternateConditions) where T:IFastCond { + // We flatten the Ors, and optimize everything else individually. + // Our caller is responsible for optimizing the final list of OR conditions foreach (var condition in alternateConditions) { // Covers FastCondAnyNaive, FastCondAnyOptimized, FastCondOr @@ -217,14 +235,14 @@ private static void FlattenRecursiveAndOptimize(List flattened, IEnu } - internal static List FlattenRecursiveOptimizeMergeAndOptimize(IReadOnlyCollection alternateConditions) + internal static List FlattenRecursiveOptimizeMergeAndOptimize(IEnumerable alternateConditions) where T:IFastCond { - var flattened = new List(alternateConditions.Count + 2); + var flattened = new List((alternateConditions as IReadOnlyCollection)?.Count ?? 2 + 2); FlattenRecursiveAndOptimize(flattened, alternateConditions); - //if any are Always, all others can be removed + //if any are True, all others can be removed if (flattened.Any(f => f is FastCondTrue)) { - return new List {new FastCondTrue()}; + return [new FastCondTrue()]; } // false can be removed flattened.RemoveAll(f => f is FastCondFalse); @@ -265,29 +283,79 @@ internal static List FlattenRecursiveOptimizeMergeAndOptimize(IReadOn final.Add(condition); } } + // Now order most likely first + TryOptimizeOrder(ref final, true); return final; } - + internal static int GetSortRankFor(IFastCond? c, bool moreLikelyIsHigher) + { + // Anything to do with path extensions at the top + // Then anything to do with path prefixes + // Then anything to do with querystring keys + // then anything to do with path suffixes. + // we recurse into any nested ORs, using (moreLikelyIsBetter = true) and ANDs (moreLikelyIsBetter = false) + // And NOTs invert the previous + + var rank = c switch + { + null => 0, + FastCondPathHasNoExtension => 100, + FastCondHasSupportedImageExtension => 90, + FastCondHasPathPrefixes => 80, + FastCondHasPathSuffixes => 60, + FastCondHasQueryStringKey => 70, + FastCondPathEquals => 5, + FastCondPathEqualsAny => 10, + IFastCondAnd a => GetSortRankFor(a.RequiredConditions.First(),false), + IFastCondOr o => GetSortRankFor(o.AlternateConditions.First(),true), + IFastCondNot n => GetSortRankFor(n.NotThis, !moreLikelyIsHigher), + _ => 0 + }; + + return moreLikelyIsHigher ? rank : -rank; + } + internal static void TryOptimizeOrder(ref List conditions, bool MostLikelyOnTop) + { + // We will only have OptimizedAnys, nots, and various matchers. + // Primarily we want the most exclusive matchers first, so we can short-circuit. + // Anything to do with path extensions at the top + // Then anything to do with path prefixes + // Then anything to do with querystring keys + // then anything to do with path suffixes. + // we recurse into any nested ORs + if (MostLikelyOnTop) + conditions.Sort((a, b) => + GetSortRankFor(a, false).CompareTo(GetSortRankFor(b, false))); + else + { + conditions.Sort((a, b) => + GetSortRankFor(a, true).CompareTo(GetSortRankFor(b, true))); + } + } internal static bool TryOptimize(IFastCond logicalOp, out IFastCond replacement) { if (logicalOp is IFastCondAnd andOp) { var newAnd = new List(andOp.RequiredConditions); - bool changed = false; for (var i = 0; i < newAnd.Count; i++) { - if (TryOptimize(newAnd[i], out var optimized)) + newAnd[i] = newAnd[i].Optimize(); + if (newAnd[i] is FastCondTrue) { - newAnd[i] = optimized; - changed = true; + newAnd.RemoveAt(i); } - - if (newAnd[i] is FastCondTrue) + if (newAnd[i] is FastCondFalse) + { + replacement = Conditions.False; + return true; + } + // flatten nested ANDs + if (newAnd[i] is IFastCondAnd and) { newAnd.RemoveAt(i); - changed = true; + newAnd.AddRange(and.RequiredConditions); } } @@ -297,76 +365,54 @@ internal static bool TryOptimize(IFastCond logicalOp, out IFastCond replacement) return true; } - if (changed) - { - replacement = new FastCondAll(newAnd); - return true; - } + TryOptimizeOrder(ref newAnd, false); + replacement = new FastCondAll(newAnd); + return true; + } else if (logicalOp is IFastCondOr orOp) { - var optimizedAny = - orOp as FastCondAnyOptimized ?? new FastCondAnyOptimized(orOp.AlternateConditions); - if (optimizedAny.AlternateConditions.Count == 1) - { - replacement = optimizedAny.AlternateConditions.First(); - return true; - } - replacement = optimizedAny; - return optimizedAny != logicalOp; + var optimizedOr = new FastCondAny( + FastCondOptimizer.FlattenRecursiveOptimizeMergeAndOptimize(orOp.AlternateConditions)); + replacement = optimizedOr.AlternateConditions.Count == 1 ? optimizedOr.AlternateConditions.First() + : optimizedOr; + return true; } else if (logicalOp is IFastCondNot notOp) { - if (TryOptimize(notOp.NotThis, out var optimized)) + + if (notOp.NotThis is FastCondTrue) { - replacement = new FastCondNot(optimized); + replacement = new FastCondFalse(); return true; } - } - replacement = logicalOp; - return false; - } - } - - public class FastCondAnyOptimized : IFastCondOr - { - private readonly List optimized; - - public IReadOnlyCollection AlternateConditions => optimized; - - public FastCondAnyOptimized(IReadOnlyCollection conditions) - { - optimized = FastCondOptimizer.FlattenRecursiveOptimizeMergeAndOptimize(conditions); - } - public bool Matches(string path, IReadOnlyQueryWrapper query) - { - if (optimized.Count == 0) return true; - foreach (var condition in optimized) - { - if (condition.Matches(path, query)) + if (notOp.NotThis is FastCondFalse) { + replacement = new FastCondTrue(); return true; } + if (TryOptimize(notOp.NotThis, out var optimized)) + { + replacement = new FastCondNot(optimized); + return true; + } + // we could invert and -> not(each(or()) or or to and(each(not())) if some + // sub-conditions are faster that way, but I don't see any at this point. + } + replacement = logicalOp; return false; } - - public override string ToString() - { - return $"optimized.any({string.Join(", ", optimized)})"; - } - } - public readonly record struct FastCondAny(IReadOnlyCollection Conditions) : IFastCondOr, IFastCondCanMerge { public IReadOnlyCollection AlternateConditions => Conditions; public bool Matches(string path, IReadOnlyQueryWrapper query) { - if (Conditions.Count == 0) return true; + if (Conditions.Count == 0) return false; foreach (var condition in Conditions) { if (condition.Matches(path, query)) @@ -668,5 +714,7 @@ public override string ToString() } } + + } - \ No newline at end of file + diff --git a/src/Imazen.Routing/Layers/LocalFilesLayer.cs b/src/Imazen.Routing/Layers/LocalFilesLayer.cs index 1e576eda..eb4d1e31 100644 --- a/src/Imazen.Routing/Layers/LocalFilesLayer.cs +++ b/src/Imazen.Routing/Layers/LocalFilesLayer.cs @@ -7,7 +7,7 @@ using Imazen.Routing.Requests; namespace Imazen.Routing.Layers; -public interface IPathMapping +public interface IPathMapping : IStringAndComparison { string VirtualPath { get; } string PhysicalPath { get; } @@ -17,7 +17,12 @@ public interface IPathMapping // prevent all file requests from being routed to it. } -public record PathMapping(string VirtualPath, string PhysicalPath, bool IgnorePrefixCase = false) : IPathMapping; + +public record PathMapping(string VirtualPath, string PhysicalPath, bool IgnorePrefixCase = false) : IPathMapping +{ + public string StringToCompare => VirtualPath; + public StringComparison StringComparison => IgnorePrefixCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; +} public class LocalFilesLayer : IRoutingLayer { public LocalFilesLayer(List pathMappings) @@ -31,15 +36,7 @@ public LocalFilesLayer(List pathMappings) { throw new ArgumentException("Path mappings must have both a virtual and physical path"); } - - var ignoreCase = this.PathMappings.Where(m => m.IgnorePrefixCase).Select(m => m.VirtualPath).ToArray(); - var ordinalCase = this.PathMappings.Where(m => !m.IgnorePrefixCase).Select(m => m.VirtualPath).ToArray(); - - FastPreconditions = new Conditions.FastCondAnyOptimized(new List - { - ordinalCase.Length == 0 ? Conditions.False : Conditions.HasPathPrefix(ordinalCase), - ignoreCase.Length == 0 ? Conditions.False : Conditions.HasPathPrefixOrdinalIgnoreCase(ignoreCase) - }); + FastPreconditions = Conditions.HasPathPrefix(PathMappings); } protected readonly List PathMappings; diff --git a/tests/ImazenShared.Tests/Routing/Serving/ImageServerTests.cs b/tests/ImazenShared.Tests/Routing/Serving/ImageServerTests.cs index 8f49a483..fa7057a7 100644 --- a/tests/ImazenShared.Tests/Routing/Serving/ImageServerTests.cs +++ b/tests/ImazenShared.Tests/Routing/Serving/ImageServerTests.cs @@ -53,7 +53,7 @@ ImageServerOptions imageServerOptions public void TestMightHandleRequest() { var b = new RoutingBuilder(); - b.AddEndpoint(Conditions.PathEquals("/hi"), + b.AddGlobalEndpoint(Conditions.PathEquals("/hi"), SmallHttpResponse.Text(200, "HI")); var imageServer = CreateImageServer( @@ -69,7 +69,7 @@ public void TestMightHandleRequest() public async void TestTryHandleRequestAsync() { var b = new RoutingBuilder(); - b.AddEndpoint(Conditions.PathEquals("/hi"), + b.AddGlobalEndpoint(Conditions.PathEquals("/hi"), SmallHttpResponse.Text(200, "HI")); var imageServer = CreateImageServer(