Flesh out experimental matchers
This change improves this area a bit by consolidating the matcher implementations between the benchmarks project and the conformance tests. Additionally I split the minimal matcher into a really trivial implementation for the simple tests and a more complex one for the larger tests. This allows us to keep the plaintext/techempower scenario in sight while also having a good baseline for the more sophisticated tests. Also starting to add tests that verify that matchers behave as expected. The matchers now successfully execute all of these benchmarks, which means that they support literals and parameters. Missing features: - complex segments - catchall - default values - optional parameters - constraints - complex segments with file extensions This is a good place to iterate a bit more of perf and try to make a decision about what we want to implement.
This commit is contained in:
parent
d3ddc1709a
commit
00e99dbbb2
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
|
|
@ -11,6 +12,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
private const int SampleCount = 100;
|
||||
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
private Matcher _instruction;
|
||||
private Matcher _route;
|
||||
private Matcher _tree;
|
||||
|
||||
|
|
@ -28,12 +32,58 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
// of the request data.
|
||||
_samples = SampleRequests(EndpointCount, SampleCount);
|
||||
|
||||
_route = SetupMatcher(RouteMatcher.CreateBuilder());
|
||||
_tree = SetupMatcher(TreeRouterMatcher.CreateBuilder());
|
||||
_baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
|
||||
_dfa = SetupMatcher(new DfaMatcherBuilder());
|
||||
_instruction = SetupMatcher(new InstructionMatcherBuilder());
|
||||
_route = SetupMatcher(new RouteMatcherBuilder());
|
||||
_tree = SetupMatcher(new TreeRouterMatcherBuilder());
|
||||
|
||||
_feature = new EndpointFeature();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = SampleCount)]
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < SampleCount; i++)
|
||||
{
|
||||
var sample = _samples[i];
|
||||
var httpContext = _requests[sample];
|
||||
await _baseline._matchers[sample].MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = SampleCount)]
|
||||
public async Task Dfa()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < SampleCount; i++)
|
||||
{
|
||||
var sample = _samples[i];
|
||||
if (sample == 805)
|
||||
{
|
||||
GC.KeepAlive(5);
|
||||
}
|
||||
var httpContext = _requests[sample];
|
||||
await _dfa.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = SampleCount)]
|
||||
public async Task Instruction()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < SampleCount; i++)
|
||||
{
|
||||
var sample = _samples[i];
|
||||
var httpContext = _requests[sample];
|
||||
await _instruction.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = SampleCount)]
|
||||
public async Task LegacyRoute()
|
||||
{
|
||||
|
|
@ -42,6 +92,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
var sample = _samples[i];
|
||||
var httpContext = _requests[sample];
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _route.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
|
|
@ -55,6 +109,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
var sample = _samples[i];
|
||||
var httpContext = _requests[sample];
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _tree.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,353 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class DfaMatcher : Matcher
|
||||
{
|
||||
public static MatcherBuilder CreateBuilder() => new Builder();
|
||||
|
||||
private readonly State[] _states;
|
||||
|
||||
private DfaMatcher(State[] states)
|
||||
{
|
||||
_states = states;
|
||||
}
|
||||
|
||||
public override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var states = _states;
|
||||
var current = 0;
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
|
||||
var start = 1; // PathString always has a leading slash
|
||||
var length = 0;
|
||||
var end = 0;
|
||||
while ((end = path.IndexOf('/', start + 1)) >= 0)
|
||||
{
|
||||
current = states[current].Transitions.GetDestination(path, start, end - start);
|
||||
start = end;
|
||||
}
|
||||
|
||||
// residue
|
||||
length = path.Length - start;
|
||||
if (length > 0)
|
||||
{
|
||||
current = states[current].Transitions.GetDestination(path, start, length);
|
||||
}
|
||||
|
||||
var matches = states[current].Matches;
|
||||
feature.Endpoint = matches.Length == 0 ? null : matches[0];
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private struct State
|
||||
{
|
||||
public bool IsAccepting;
|
||||
public Endpoint[] Matches;
|
||||
public JumpTable Transitions;
|
||||
}
|
||||
|
||||
private abstract class JumpTable
|
||||
{
|
||||
public abstract int GetDestination(string text, int start, int length);
|
||||
}
|
||||
|
||||
private class JumpTableBuilder
|
||||
{
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
public int Depth { get; set; }
|
||||
|
||||
public int Exit { get; set; }
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
return new SimpleJumpTable(Depth, Exit, _entries.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleJumpTable : JumpTable
|
||||
{
|
||||
private readonly (string text, int destination)[] _entries;
|
||||
private readonly int _depth;
|
||||
private readonly int _exit;
|
||||
|
||||
public SimpleJumpTable(int depth, int exit, (string text, int destination)[] entries)
|
||||
{
|
||||
_depth = depth;
|
||||
_exit = exit;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public override int GetDestination(string text, int start, int length)
|
||||
{
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
if (length == _entries[i].text.Length &&
|
||||
string.Compare(
|
||||
text,
|
||||
start,
|
||||
_entries[i].text,
|
||||
0,
|
||||
length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return _entries[i].destination;
|
||||
}
|
||||
}
|
||||
|
||||
return _exit;
|
||||
}
|
||||
}
|
||||
|
||||
private class Entry
|
||||
{
|
||||
public int Order;
|
||||
public decimal Precedence;
|
||||
public RouteTemplate Pattern;
|
||||
public Endpoint Endpoint;
|
||||
}
|
||||
|
||||
private class Node
|
||||
{
|
||||
public int Depth { get; set; }
|
||||
|
||||
public List<Entry> Matches { get; } = new List<Entry>();
|
||||
|
||||
public Dictionary<string, Node> Literals { get; } = new Dictionary<string, Node>();
|
||||
}
|
||||
|
||||
private class Builder : MatcherBuilder
|
||||
{
|
||||
private List<Entry> _entries = new List<Entry>();
|
||||
|
||||
public override void AddEntry(string pattern, MatcherEndpoint endpoint)
|
||||
{
|
||||
var parsed = TemplateParser.Parse(pattern);
|
||||
_entries.Add(new Entry()
|
||||
{
|
||||
Order = 0,
|
||||
Pattern = parsed,
|
||||
Precedence = RoutePrecedence.ComputeInbound(parsed),
|
||||
Endpoint = endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
Sort(_entries);
|
||||
|
||||
var root = new Node() { Depth = -1 };
|
||||
|
||||
// Since we overlay parameters onto the literal entries, we do two passes, first we create
|
||||
// all of the literal nodes, then we 'spread' parameters
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
|
||||
var parent = root;
|
||||
|
||||
for (var depth = 0; depth < entry.Pattern.Segments.Count; depth++)
|
||||
{
|
||||
var segment = entry.Pattern.Segments[depth];
|
||||
|
||||
if (segment.IsSimple && segment.Parts[0].IsLiteral)
|
||||
{
|
||||
if (!parent.Literals.TryGetValue(segment.Parts[0].Text, out var next))
|
||||
{
|
||||
next = new Node() { Depth = depth, };
|
||||
parent.Literals.Add(segment.Parts[0].Text, next);
|
||||
}
|
||||
|
||||
parent = next;
|
||||
}
|
||||
else if (segment.IsSimple && segment.Parts[0].IsParameter)
|
||||
{
|
||||
if (!parent.Literals.TryGetValue("*", out var next))
|
||||
{
|
||||
next = new Node() { Depth = depth, };
|
||||
parent.Literals.Add("*", next);
|
||||
}
|
||||
|
||||
parent = next;
|
||||
}
|
||||
}
|
||||
|
||||
parent.Matches.Add(entry);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
|
||||
var parents = new List<Node>() { root, };
|
||||
|
||||
for (var depth = 0; depth < entry.Pattern.Segments.Count; depth++)
|
||||
{
|
||||
var segment = entry.Pattern.Segments[depth];
|
||||
|
||||
if (segment.IsSimple && segment.Parts[0].IsLiteral)
|
||||
{
|
||||
var next = new List<Node>();
|
||||
for (var j = 0; j < parents.Count; j++)
|
||||
{
|
||||
if (!parents[j].Literals.TryGetValue(segment.Parts[0].Text, out var child))
|
||||
{
|
||||
child = new Node() { Depth = depth, };
|
||||
if (parents[j].Literals.TryGetValue("*", out var parameter))
|
||||
{
|
||||
child.Matches.AddRange(parameter.Matches);
|
||||
foreach (var kvp in parameter.Literals)
|
||||
{
|
||||
child.Literals.Add(kvp.Key, DeepCopy(kvp.Value));
|
||||
}
|
||||
}
|
||||
|
||||
parents[j].Literals.Add(segment.Parts[0].Text, child);
|
||||
}
|
||||
|
||||
next.Add(child);
|
||||
}
|
||||
|
||||
parents = next;
|
||||
}
|
||||
else if (segment.IsSimple && segment.Parts[0].IsParameter)
|
||||
{
|
||||
var next = new List<Node>();
|
||||
for (var j = 0; j < parents.Count; j++)
|
||||
{
|
||||
next.AddRange(parents[j].Literals.Values);
|
||||
}
|
||||
|
||||
parents = next;
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < parents.Count; j++)
|
||||
{
|
||||
if (!parents[j].Matches.Contains(entry))
|
||||
{
|
||||
parents[j].Matches.Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var states = new List<State>();
|
||||
var tables = new List<JumpTableBuilder>();
|
||||
AddNode(root, states, tables);
|
||||
|
||||
var exit = states.Count;
|
||||
states.Add(new State() { IsAccepting = false, Matches = Array.Empty<Endpoint>(), });
|
||||
tables.Add(new JumpTableBuilder() { Exit = exit, });
|
||||
|
||||
for (var i = 0; i < tables.Count; i++)
|
||||
{
|
||||
if (tables[i].Exit == -1)
|
||||
{
|
||||
tables[i].Exit = exit;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < states.Count; i++)
|
||||
{
|
||||
states[i] = new State()
|
||||
{
|
||||
IsAccepting = states[i].IsAccepting,
|
||||
Matches = states[i].Matches,
|
||||
Transitions = tables[i].Build(),
|
||||
};
|
||||
}
|
||||
|
||||
return new DfaMatcher(states.ToArray());
|
||||
}
|
||||
|
||||
private static int AddNode(Node node, List<State> states, List<JumpTableBuilder> tables)
|
||||
{
|
||||
Sort(node.Matches);
|
||||
|
||||
var index = states.Count;
|
||||
states.Add(new State() { Matches = node.Matches.Select(e => e.Endpoint).ToArray(), IsAccepting = node.Matches.Count > 0 });
|
||||
|
||||
var table = new JumpTableBuilder() { Depth = node.Depth, };
|
||||
tables.Add(table);
|
||||
|
||||
foreach (var kvp in node.Literals)
|
||||
{
|
||||
if (kvp.Key == "*")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var transition = AddNode(kvp.Value, states, tables);
|
||||
table.AddEntry(kvp.Key, transition);
|
||||
}
|
||||
|
||||
var exitIndex = -1;
|
||||
if (node.Literals.TryGetValue("*", out var exit))
|
||||
{
|
||||
exitIndex = AddNode(exit, states, tables);
|
||||
}
|
||||
|
||||
table.Exit = exitIndex;
|
||||
return index;
|
||||
}
|
||||
|
||||
private static void Sort(List<Entry> entries)
|
||||
{
|
||||
entries.Sort((x, y) =>
|
||||
{
|
||||
var comparison = x.Order.CompareTo(y.Order);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
comparison = y.Precedence.CompareTo(x.Precedence);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
return x.Pattern.TemplateText.CompareTo(y.Pattern.TemplateText);
|
||||
});
|
||||
}
|
||||
|
||||
private static Node DeepCopy(Node node)
|
||||
{
|
||||
var copy = new Node() { Depth = node.Depth, };
|
||||
copy.Matches.AddRange(node.Matches);
|
||||
|
||||
foreach (var kvp in node.Literals)
|
||||
{
|
||||
copy.Literals.Add(kvp.Key, DeepCopy(kvp.Value));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public partial class GithubMatcherBenchmark : MatcherBenchmarkBase
|
||||
{
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
private Matcher _instruction;
|
||||
private Matcher _route;
|
||||
private Matcher _tree;
|
||||
|
||||
|
|
@ -22,12 +25,51 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
|
||||
SetupRequests();
|
||||
|
||||
_route = SetupMatcher(RouteMatcher.CreateBuilder());
|
||||
_tree = SetupMatcher(TreeRouterMatcher.CreateBuilder());
|
||||
_baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
|
||||
_dfa = SetupMatcher(new DfaMatcherBuilder());
|
||||
_instruction = SetupMatcher(new InstructionMatcherBuilder());
|
||||
_route = SetupMatcher(new RouteMatcherBuilder());
|
||||
_tree = SetupMatcher(new TreeRouterMatcherBuilder());
|
||||
|
||||
_feature = new EndpointFeature();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = EndpointCount)]
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = _requests[i];
|
||||
await _baseline._matchers[i].MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[i], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark( OperationsPerInvoke = EndpointCount)]
|
||||
public async Task Dfa()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = _requests[i];
|
||||
await _dfa.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[i], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = EndpointCount)]
|
||||
public async Task Instruction()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = _requests[i];
|
||||
await _instruction.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[i], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = EndpointCount)]
|
||||
public async Task LegacyRoute()
|
||||
{
|
||||
|
|
@ -35,6 +77,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = _requests[i];
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _route.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[i], feature.Endpoint);
|
||||
}
|
||||
|
|
@ -47,6 +93,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = _requests[i];
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _tree.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, _endpoints[i], feature.Endpoint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -798,161 +798,161 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
builder.AddEntry("/emojis", _endpoints[0]);
|
||||
builder.AddEntry("/events", _endpoints[1]);
|
||||
builder.AddEntry("/feeds", _endpoints[2]);
|
||||
builder.AddEntry("/gists", _endpoints[3]);
|
||||
builder.AddEntry("/issues", _endpoints[4]);
|
||||
builder.AddEntry("/markdown", _endpoints[5]);
|
||||
builder.AddEntry("/meta", _endpoints[6]);
|
||||
builder.AddEntry("/notifications", _endpoints[7]);
|
||||
builder.AddEntry("/rate_limit", _endpoints[8]);
|
||||
builder.AddEntry("/repositories", _endpoints[9]);
|
||||
builder.AddEntry("/user", _endpoints[10]);
|
||||
builder.AddEntry("/users", _endpoints[11]);
|
||||
builder.AddEntry("/gists/public", _endpoints[12]);
|
||||
builder.AddEntry("/gists/starred", _endpoints[13]);
|
||||
builder.AddEntry("/gitignore/templates", _endpoints[14]);
|
||||
builder.AddEntry("/markdown/raw", _endpoints[15]);
|
||||
builder.AddEntry("/search/code", _endpoints[16]);
|
||||
builder.AddEntry("/search/issues", _endpoints[17]);
|
||||
builder.AddEntry("/search/repositories", _endpoints[18]);
|
||||
builder.AddEntry("/search/users", _endpoints[19]);
|
||||
builder.AddEntry("/user/emails", _endpoints[20]);
|
||||
builder.AddEntry("/user/followers", _endpoints[21]);
|
||||
builder.AddEntry("/user/following", _endpoints[22]);
|
||||
builder.AddEntry("/user/issues", _endpoints[23]);
|
||||
builder.AddEntry("/user/keys", _endpoints[24]);
|
||||
builder.AddEntry("/user/orgs", _endpoints[25]);
|
||||
builder.AddEntry("/user/repos", _endpoints[26]);
|
||||
builder.AddEntry("/user/starred", _endpoints[27]);
|
||||
builder.AddEntry("/user/subscriptions", _endpoints[28]);
|
||||
builder.AddEntry("/user/teams", _endpoints[29]);
|
||||
builder.AddEntry("/legacy/repos/search/{keyword}", _endpoints[30]);
|
||||
builder.AddEntry("/legacy/user/email/{email}", _endpoints[31]);
|
||||
builder.AddEntry("/legacy/user/search/{keyword}", _endpoints[32]);
|
||||
builder.AddEntry("/legacy/issues/search/{owner}/{repository}/{state}/{keyword}", _endpoints[33]);
|
||||
builder.AddEntry("/gitignore/templates/{language}", _endpoints[34]);
|
||||
builder.AddEntry("/notifications/threads/{id}", _endpoints[35]);
|
||||
builder.AddEntry("/user/following/{username}", _endpoints[36]);
|
||||
builder.AddEntry("/user/keys/{keyId}", _endpoints[37]);
|
||||
builder.AddEntry("/notifications/threads/{id}/subscription", _endpoints[38]);
|
||||
builder.AddEntry("/user/starred/{owner}/{repo}", _endpoints[39]);
|
||||
builder.AddEntry("/user/subscriptions/{owner}/{repo}", _endpoints[40]);
|
||||
builder.AddEntry("/gists/{id}", _endpoints[41]);
|
||||
builder.AddEntry("/orgs/{org}", _endpoints[42]);
|
||||
builder.AddEntry("/teams/{teamId}", _endpoints[43]);
|
||||
builder.AddEntry("/users/{username}", _endpoints[44]);
|
||||
builder.AddEntry("/gists/{id}/comments", _endpoints[45]);
|
||||
builder.AddEntry("/gists/{id}/forks", _endpoints[46]);
|
||||
builder.AddEntry("/gists/{id}/star", _endpoints[47]);
|
||||
builder.AddEntry("/orgs/{org}/events", _endpoints[48]);
|
||||
builder.AddEntry("/orgs/{org}/issues", _endpoints[49]);
|
||||
builder.AddEntry("/orgs/{org}/members", _endpoints[50]);
|
||||
builder.AddEntry("/orgs/{org}/public_members", _endpoints[51]);
|
||||
builder.AddEntry("/orgs/{org}/repos", _endpoints[52]);
|
||||
builder.AddEntry("/orgs/{org}/teams", _endpoints[53]);
|
||||
builder.AddEntry("/teams/{teamId}/members", _endpoints[54]);
|
||||
builder.AddEntry("/teams/{teamId}/repos", _endpoints[55]);
|
||||
builder.AddEntry("/users/{username}/events", _endpoints[56]);
|
||||
builder.AddEntry("/users/{username}/followers", _endpoints[57]);
|
||||
builder.AddEntry("/users/{username}/gists", _endpoints[58]);
|
||||
builder.AddEntry("/users/{username}/keys", _endpoints[59]);
|
||||
builder.AddEntry("/users/{username}/orgs", _endpoints[60]);
|
||||
builder.AddEntry("/users/{username}/received_events", _endpoints[61]);
|
||||
builder.AddEntry("/users/{username}/repos", _endpoints[62]);
|
||||
builder.AddEntry("/users/{username}/starred", _endpoints[63]);
|
||||
builder.AddEntry("/users/{username}/subscriptions", _endpoints[64]);
|
||||
builder.AddEntry("/users/{username}/received_events/public", _endpoints[65]);
|
||||
builder.AddEntry("/users/{username}/events/orgs/{org}", _endpoints[66]);
|
||||
builder.AddEntry("/gists/{id}/comments/{commentId}", _endpoints[67]);
|
||||
builder.AddEntry("/orgs/{org}/members/{username}", _endpoints[68]);
|
||||
builder.AddEntry("/orgs/{org}/public_members/{username}", _endpoints[69]);
|
||||
builder.AddEntry("/teams/{teamId}/members/{username}", _endpoints[70]);
|
||||
builder.AddEntry("/teams/{teamId}/memberships/{username}", _endpoints[71]);
|
||||
builder.AddEntry("/users/{username}/following/{targetUser}", _endpoints[72]);
|
||||
builder.AddEntry("/teams/{teamId}/repos/{owner}/{repo}", _endpoints[73]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}", _endpoints[74]);
|
||||
builder.AddEntry("/networks/{owner}/{repo}/events", _endpoints[75]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/assignees", _endpoints[76]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/branches", _endpoints[77]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/collaborators", _endpoints[78]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/comments", _endpoints[79]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/commits", _endpoints[80]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/contributors", _endpoints[81]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/deployments", _endpoints[82]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/downloads", _endpoints[83]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/events", _endpoints[84]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/forks", _endpoints[85]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/hooks", _endpoints[86]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues", _endpoints[87]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/keys", _endpoints[88]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/labels", _endpoints[89]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/languages", _endpoints[90]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/merges", _endpoints[91]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/milestones", _endpoints[92]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/notifications", _endpoints[93]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls", _endpoints[94]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/readme", _endpoints[95]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/releases", _endpoints[96]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/stargazers", _endpoints[97]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/subscribers", _endpoints[98]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/subscription", _endpoints[99]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/tags", _endpoints[100]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/teams", _endpoints[101]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/watchers", _endpoints[102]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/blobs", _endpoints[103]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/commits", _endpoints[104]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/refs", _endpoints[105]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/tags", _endpoints[106]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/trees", _endpoints[107]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/comments", _endpoints[108]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/events", _endpoints[109]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/comments", _endpoints[110]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/stats/code_frequency", _endpoints[111]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/stats/commit_activity", _endpoints[112]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/stats/contributors", _endpoints[113]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/stats/participation", _endpoints[114]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/stats/punch_card", _endpoints[115]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/blobs/{shaCode}", _endpoints[116]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/commits/{shaCode}", _endpoints[117]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/refs/{ref}", _endpoints[118]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/tags/{shaCode}", _endpoints[119]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/git/trees/{shaCode}", _endpoints[120]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/comments/{commentId}", _endpoints[121]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/events/{eventId}", _endpoints[122]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/comments/{commentId}", _endpoints[123]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/releases/assets/{id}", _endpoints[124]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/assignees/{assignee}", _endpoints[125]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/branches/{branch}", _endpoints[126]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/collaborators/{user}", _endpoints[127]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/comments/{commentId}", _endpoints[128]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/commits/{shaCode}", _endpoints[129]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/contents/{path}", _endpoints[130]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/downloads/{downloadId}", _endpoints[131]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/hooks/{hookId}", _endpoints[132]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/{number}", _endpoints[133]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/keys/{keyId}", _endpoints[134]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/labels/{name}", _endpoints[135]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/milestones/{number}", _endpoints[136]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/{number}", _endpoints[137]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/releases/{id}", _endpoints[138]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/statuses/{ref}", _endpoints[139]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/commits/{ref}/status", _endpoints[140]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/commits/{shaCode}/comments", _endpoints[141]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/deployments/{id}/statuses", _endpoints[142]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/hooks/{hookId}/tests", _endpoints[143]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/{number}/comments", _endpoints[144]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/{number}/events", _endpoints[145]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/{number}/labels", _endpoints[146]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/milestones/{number}/labels", _endpoints[147]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/{number}/comments", _endpoints[148]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/{number}/commits", _endpoints[149]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/{number}/files", _endpoints[150]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/pulls/{number}/merge", _endpoints[151]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/releases/{id}/assets", _endpoints[152]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/issues/{number}/labels/{name}", _endpoints[153]);
|
||||
builder.AddEntry("/repos/{owner}/{repo}/{archive_format}/{path}", _endpoints[154]);
|
||||
builder.AddEndpoint(_endpoints[0]);
|
||||
builder.AddEndpoint(_endpoints[1]);
|
||||
builder.AddEndpoint(_endpoints[2]);
|
||||
builder.AddEndpoint(_endpoints[3]);
|
||||
builder.AddEndpoint(_endpoints[4]);
|
||||
builder.AddEndpoint(_endpoints[5]);
|
||||
builder.AddEndpoint(_endpoints[6]);
|
||||
builder.AddEndpoint(_endpoints[7]);
|
||||
builder.AddEndpoint(_endpoints[8]);
|
||||
builder.AddEndpoint(_endpoints[9]);
|
||||
builder.AddEndpoint(_endpoints[10]);
|
||||
builder.AddEndpoint(_endpoints[11]);
|
||||
builder.AddEndpoint(_endpoints[12]);
|
||||
builder.AddEndpoint(_endpoints[13]);
|
||||
builder.AddEndpoint(_endpoints[14]);
|
||||
builder.AddEndpoint(_endpoints[15]);
|
||||
builder.AddEndpoint(_endpoints[16]);
|
||||
builder.AddEndpoint(_endpoints[17]);
|
||||
builder.AddEndpoint(_endpoints[18]);
|
||||
builder.AddEndpoint(_endpoints[19]);
|
||||
builder.AddEndpoint(_endpoints[20]);
|
||||
builder.AddEndpoint(_endpoints[21]);
|
||||
builder.AddEndpoint(_endpoints[22]);
|
||||
builder.AddEndpoint(_endpoints[23]);
|
||||
builder.AddEndpoint(_endpoints[24]);
|
||||
builder.AddEndpoint(_endpoints[25]);
|
||||
builder.AddEndpoint(_endpoints[26]);
|
||||
builder.AddEndpoint(_endpoints[27]);
|
||||
builder.AddEndpoint(_endpoints[28]);
|
||||
builder.AddEndpoint(_endpoints[29]);
|
||||
builder.AddEndpoint(_endpoints[30]);
|
||||
builder.AddEndpoint(_endpoints[31]);
|
||||
builder.AddEndpoint(_endpoints[32]);
|
||||
builder.AddEndpoint(_endpoints[33]);
|
||||
builder.AddEndpoint(_endpoints[34]);
|
||||
builder.AddEndpoint(_endpoints[35]);
|
||||
builder.AddEndpoint(_endpoints[36]);
|
||||
builder.AddEndpoint(_endpoints[37]);
|
||||
builder.AddEndpoint(_endpoints[38]);
|
||||
builder.AddEndpoint(_endpoints[39]);
|
||||
builder.AddEndpoint(_endpoints[40]);
|
||||
builder.AddEndpoint(_endpoints[41]);
|
||||
builder.AddEndpoint(_endpoints[42]);
|
||||
builder.AddEndpoint(_endpoints[43]);
|
||||
builder.AddEndpoint(_endpoints[44]);
|
||||
builder.AddEndpoint(_endpoints[45]);
|
||||
builder.AddEndpoint(_endpoints[46]);
|
||||
builder.AddEndpoint(_endpoints[47]);
|
||||
builder.AddEndpoint(_endpoints[48]);
|
||||
builder.AddEndpoint(_endpoints[49]);
|
||||
builder.AddEndpoint(_endpoints[50]);
|
||||
builder.AddEndpoint(_endpoints[51]);
|
||||
builder.AddEndpoint(_endpoints[52]);
|
||||
builder.AddEndpoint(_endpoints[53]);
|
||||
builder.AddEndpoint(_endpoints[54]);
|
||||
builder.AddEndpoint(_endpoints[55]);
|
||||
builder.AddEndpoint(_endpoints[56]);
|
||||
builder.AddEndpoint(_endpoints[57]);
|
||||
builder.AddEndpoint(_endpoints[58]);
|
||||
builder.AddEndpoint(_endpoints[59]);
|
||||
builder.AddEndpoint(_endpoints[60]);
|
||||
builder.AddEndpoint(_endpoints[61]);
|
||||
builder.AddEndpoint(_endpoints[62]);
|
||||
builder.AddEndpoint(_endpoints[63]);
|
||||
builder.AddEndpoint(_endpoints[64]);
|
||||
builder.AddEndpoint(_endpoints[65]);
|
||||
builder.AddEndpoint(_endpoints[66]);
|
||||
builder.AddEndpoint(_endpoints[67]);
|
||||
builder.AddEndpoint(_endpoints[68]);
|
||||
builder.AddEndpoint(_endpoints[69]);
|
||||
builder.AddEndpoint(_endpoints[70]);
|
||||
builder.AddEndpoint(_endpoints[71]);
|
||||
builder.AddEndpoint(_endpoints[72]);
|
||||
builder.AddEndpoint(_endpoints[73]);
|
||||
builder.AddEndpoint(_endpoints[74]);
|
||||
builder.AddEndpoint(_endpoints[75]);
|
||||
builder.AddEndpoint(_endpoints[76]);
|
||||
builder.AddEndpoint(_endpoints[77]);
|
||||
builder.AddEndpoint(_endpoints[78]);
|
||||
builder.AddEndpoint(_endpoints[79]);
|
||||
builder.AddEndpoint(_endpoints[80]);
|
||||
builder.AddEndpoint(_endpoints[81]);
|
||||
builder.AddEndpoint(_endpoints[82]);
|
||||
builder.AddEndpoint(_endpoints[83]);
|
||||
builder.AddEndpoint(_endpoints[84]);
|
||||
builder.AddEndpoint(_endpoints[85]);
|
||||
builder.AddEndpoint(_endpoints[86]);
|
||||
builder.AddEndpoint(_endpoints[87]);
|
||||
builder.AddEndpoint(_endpoints[88]);
|
||||
builder.AddEndpoint(_endpoints[89]);
|
||||
builder.AddEndpoint(_endpoints[90]);
|
||||
builder.AddEndpoint(_endpoints[91]);
|
||||
builder.AddEndpoint(_endpoints[92]);
|
||||
builder.AddEndpoint(_endpoints[93]);
|
||||
builder.AddEndpoint(_endpoints[94]);
|
||||
builder.AddEndpoint(_endpoints[95]);
|
||||
builder.AddEndpoint(_endpoints[96]);
|
||||
builder.AddEndpoint(_endpoints[97]);
|
||||
builder.AddEndpoint(_endpoints[98]);
|
||||
builder.AddEndpoint(_endpoints[99]);
|
||||
builder.AddEndpoint(_endpoints[100]);
|
||||
builder.AddEndpoint(_endpoints[101]);
|
||||
builder.AddEndpoint(_endpoints[102]);
|
||||
builder.AddEndpoint(_endpoints[103]);
|
||||
builder.AddEndpoint(_endpoints[104]);
|
||||
builder.AddEndpoint(_endpoints[105]);
|
||||
builder.AddEndpoint(_endpoints[106]);
|
||||
builder.AddEndpoint(_endpoints[107]);
|
||||
builder.AddEndpoint(_endpoints[108]);
|
||||
builder.AddEndpoint(_endpoints[109]);
|
||||
builder.AddEndpoint(_endpoints[110]);
|
||||
builder.AddEndpoint(_endpoints[111]);
|
||||
builder.AddEndpoint(_endpoints[112]);
|
||||
builder.AddEndpoint(_endpoints[113]);
|
||||
builder.AddEndpoint(_endpoints[114]);
|
||||
builder.AddEndpoint(_endpoints[115]);
|
||||
builder.AddEndpoint(_endpoints[116]);
|
||||
builder.AddEndpoint(_endpoints[117]);
|
||||
builder.AddEndpoint(_endpoints[118]);
|
||||
builder.AddEndpoint(_endpoints[119]);
|
||||
builder.AddEndpoint(_endpoints[120]);
|
||||
builder.AddEndpoint(_endpoints[121]);
|
||||
builder.AddEndpoint(_endpoints[122]);
|
||||
builder.AddEndpoint(_endpoints[123]);
|
||||
builder.AddEndpoint(_endpoints[124]);
|
||||
builder.AddEndpoint(_endpoints[125]);
|
||||
builder.AddEndpoint(_endpoints[126]);
|
||||
builder.AddEndpoint(_endpoints[127]);
|
||||
builder.AddEndpoint(_endpoints[128]);
|
||||
builder.AddEndpoint(_endpoints[129]);
|
||||
builder.AddEndpoint(_endpoints[130]);
|
||||
builder.AddEndpoint(_endpoints[131]);
|
||||
builder.AddEndpoint(_endpoints[132]);
|
||||
builder.AddEndpoint(_endpoints[133]);
|
||||
builder.AddEndpoint(_endpoints[134]);
|
||||
builder.AddEndpoint(_endpoints[135]);
|
||||
builder.AddEndpoint(_endpoints[136]);
|
||||
builder.AddEndpoint(_endpoints[137]);
|
||||
builder.AddEndpoint(_endpoints[138]);
|
||||
builder.AddEndpoint(_endpoints[139]);
|
||||
builder.AddEndpoint(_endpoints[140]);
|
||||
builder.AddEndpoint(_endpoints[141]);
|
||||
builder.AddEndpoint(_endpoints[142]);
|
||||
builder.AddEndpoint(_endpoints[143]);
|
||||
builder.AddEndpoint(_endpoints[144]);
|
||||
builder.AddEndpoint(_endpoints[145]);
|
||||
builder.AddEndpoint(_endpoints[146]);
|
||||
builder.AddEndpoint(_endpoints[147]);
|
||||
builder.AddEndpoint(_endpoints[148]);
|
||||
builder.AddEndpoint(_endpoints[149]);
|
||||
builder.AddEndpoint(_endpoints[150]);
|
||||
builder.AddEndpoint(_endpoints[151]);
|
||||
builder.AddEndpoint(_endpoints[152]);
|
||||
builder.AddEndpoint(_endpoints[153]);
|
||||
builder.AddEndpoint(_endpoints[154]);
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,479 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class InstructionMatcher : Matcher
|
||||
{
|
||||
public static MatcherBuilder CreateBuilder() => new Builder();
|
||||
|
||||
private State _state;
|
||||
|
||||
private InstructionMatcher(Instruction[] instructions, Endpoint[] endpoints, JumpTable[] tables)
|
||||
{
|
||||
_state = new State()
|
||||
{
|
||||
Instructions = instructions,
|
||||
Endpoints = endpoints,
|
||||
Tables = tables,
|
||||
};
|
||||
}
|
||||
|
||||
public unsafe override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var state = _state;
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
var buffer = stackalloc int[32];
|
||||
|
||||
var segment = 0;
|
||||
var index = -1;
|
||||
while ((index = path.IndexOf('/', index + 1)) >= 0)
|
||||
{
|
||||
buffer[segment++] = index + 1;
|
||||
}
|
||||
|
||||
buffer[segment] = path.Length;
|
||||
|
||||
var i = 0;
|
||||
Endpoint result = null;
|
||||
while (i < state.Instructions.Length)
|
||||
{
|
||||
var instruction = state.Instructions[i];
|
||||
switch (instruction.Code)
|
||||
{
|
||||
case InstructionCode.Accept:
|
||||
{
|
||||
result = state.Endpoints[instruction.Payload];
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case InstructionCode.Branch:
|
||||
{
|
||||
var table = state.Tables[instruction.Payload];
|
||||
i = table.GetDestination(buffer, path);
|
||||
break;
|
||||
}
|
||||
case InstructionCode.Jump:
|
||||
{
|
||||
i = instruction.Payload;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feature.Endpoint = result;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private class State
|
||||
{
|
||||
public Endpoint[] Endpoints;
|
||||
public Instruction[] Instructions;
|
||||
public JumpTable[] Tables;
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToDebugString(),nq}")]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct Instruction
|
||||
{
|
||||
[FieldOffset(3)]
|
||||
public InstructionCode Code;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public int Payload;
|
||||
|
||||
private string ToDebugString()
|
||||
{
|
||||
return $"{Code}: {Payload}";
|
||||
}
|
||||
}
|
||||
|
||||
private enum InstructionCode : byte
|
||||
{
|
||||
Accept,
|
||||
Branch,
|
||||
Jump,
|
||||
Pop, // Only used during the instruction builder phase
|
||||
}
|
||||
|
||||
private abstract class JumpTable
|
||||
{
|
||||
public unsafe abstract int GetDestination(int* segments, string text);
|
||||
}
|
||||
|
||||
private class JumpTableBuilder
|
||||
{
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
public int Depth { get; set; }
|
||||
|
||||
public int Exit { get; set; }
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
return new SimpleJumpTable(Depth, Exit, _entries.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleJumpTable : JumpTable
|
||||
{
|
||||
private readonly (string text, int destination)[] _entries;
|
||||
private readonly int _depth;
|
||||
private readonly int _exit;
|
||||
|
||||
public SimpleJumpTable(int depth, int exit, (string text, int destination)[] entries)
|
||||
{
|
||||
_depth = depth;
|
||||
_exit = exit;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public unsafe override int GetDestination(int* segments, string text)
|
||||
{
|
||||
var start = segments[_depth];
|
||||
var length = segments[_depth + 1] - start;
|
||||
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
if (length == _entries[i].text.Length &&
|
||||
string.Compare(
|
||||
text,
|
||||
start,
|
||||
_entries[i].text,
|
||||
0,
|
||||
length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return _entries[i].destination;
|
||||
}
|
||||
}
|
||||
|
||||
return _exit;
|
||||
}
|
||||
}
|
||||
|
||||
private class Entry
|
||||
{
|
||||
public int Order;
|
||||
public decimal Precedence;
|
||||
public RouteTemplate Pattern;
|
||||
public Endpoint Endpoint;
|
||||
}
|
||||
|
||||
private class InstructionBuilder
|
||||
{
|
||||
private readonly List<Instruction> _instructions = new List<Instruction>();
|
||||
private readonly List<Endpoint> _endpoints = new List<Endpoint>();
|
||||
private readonly List<JumpTableBuilder> _tables = new List<JumpTableBuilder>();
|
||||
|
||||
private readonly List<int> _blocks = new List<int>();
|
||||
|
||||
public int Next => _instructions.Count;
|
||||
|
||||
public void BeginBlock()
|
||||
{
|
||||
_blocks.Add(Next);
|
||||
}
|
||||
|
||||
public void EndBlock()
|
||||
{
|
||||
var start = _blocks[_blocks.Count - 1];
|
||||
var end = Next;
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
if (_instructions[i].Code == InstructionCode.Pop)
|
||||
{
|
||||
_instructions[i] = new Instruction() { Code = InstructionCode.Jump, Payload = end };
|
||||
}
|
||||
}
|
||||
|
||||
_blocks.RemoveAt(_blocks.Count - 1);
|
||||
}
|
||||
|
||||
public int AddInstruction(Instruction instruction)
|
||||
{
|
||||
_instructions.Add(instruction);
|
||||
return _instructions.Count - 1;
|
||||
}
|
||||
|
||||
public int AddEndpoint(Endpoint endpoint)
|
||||
{
|
||||
_endpoints.Add(endpoint);
|
||||
return _endpoints.Count - 1;
|
||||
}
|
||||
|
||||
public int AddJumpTable(JumpTableBuilder table)
|
||||
{
|
||||
_tables.Add(table);
|
||||
return _tables.Count - 1;
|
||||
}
|
||||
|
||||
public void Deconstruct(out Instruction[] instructions, out Endpoint[] endpoints, out JumpTable[] tables)
|
||||
{
|
||||
instructions = _instructions.ToArray();
|
||||
endpoints = _endpoints.ToArray();
|
||||
|
||||
tables = new JumpTable[_tables.Count];
|
||||
for (var i = 0; i < _tables.Count; i++)
|
||||
{
|
||||
tables[i] = _tables[i].Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class Node
|
||||
{
|
||||
public int Depth { get; protected set; }
|
||||
public List<Node> Children { get; } = new List<Node>();
|
||||
|
||||
public abstract void Lower(InstructionBuilder builder);
|
||||
|
||||
public TNode GetNode<TNode>() where TNode : Node
|
||||
{
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is TNode match)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TNode AddNode<TNode>(TNode node) where TNode : Node
|
||||
{
|
||||
// We already ordered the routes into precedence order
|
||||
Children.Add(node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private class SequenceNode : Node
|
||||
{
|
||||
public SequenceNode(int depth)
|
||||
{
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
Children[i].Lower(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OrderNode : SequenceNode
|
||||
{
|
||||
public OrderNode(int order)
|
||||
: base(0)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public int Order { get; }
|
||||
}
|
||||
|
||||
private class BranchNode : Node
|
||||
{
|
||||
public BranchNode(int depth)
|
||||
{
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public List<string> Literals { get; } = new List<string>();
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
var table = new JumpTableBuilder() { Depth = Depth, };
|
||||
var index = builder.AddJumpTable(table);
|
||||
builder.AddInstruction(new Instruction() { Code = InstructionCode.Branch, Payload = index });
|
||||
|
||||
builder.BeginBlock();
|
||||
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
table.AddEntry(Literals[i], builder.Next);
|
||||
Children[i].Lower(builder);
|
||||
builder.AddInstruction(new Instruction() { Code = InstructionCode.Pop, });
|
||||
}
|
||||
|
||||
builder.EndBlock();
|
||||
table.Exit = builder.Next;
|
||||
}
|
||||
}
|
||||
|
||||
private class ParameterNode : Node
|
||||
{
|
||||
public ParameterNode(int depth)
|
||||
{
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
Children[i].Lower(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AcceptNode : Node
|
||||
{
|
||||
public AcceptNode(Endpoint endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
|
||||
public Endpoint Endpoint { get; }
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
builder.AddInstruction(new Instruction()
|
||||
{
|
||||
Code = InstructionCode.Accept,
|
||||
Payload = builder.AddEndpoint(Endpoint),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class Builder : MatcherBuilder
|
||||
{
|
||||
private List<Entry> _entries = new List<Entry>();
|
||||
|
||||
public override void AddEntry(string pattern, MatcherEndpoint endpoint)
|
||||
{
|
||||
var parsed = TemplateParser.Parse(pattern);
|
||||
_entries.Add(new Entry()
|
||||
{
|
||||
Order = 0,
|
||||
Pattern = parsed,
|
||||
Precedence = RoutePrecedence.ComputeInbound(parsed),
|
||||
Endpoint = endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
_entries.Sort((x, y) =>
|
||||
{
|
||||
var comparison = x.Order.CompareTo(y.Order);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
comparison = y.Precedence.CompareTo(x.Precedence);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
return x.Pattern.TemplateText.CompareTo(y.Pattern.TemplateText);
|
||||
});
|
||||
|
||||
var roots = new List<OrderNode>();
|
||||
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
|
||||
var parent = (SequenceNode)GetOrCreateRootNode(roots, entry.Order);
|
||||
|
||||
for (var depth = 0; depth < entry.Pattern.Segments.Count; depth++)
|
||||
{
|
||||
var segment = entry.Pattern.Segments[depth];
|
||||
if (segment.IsSimple && segment.Parts[0].IsLiteral)
|
||||
{
|
||||
var branch = parent.GetNode<BranchNode>() ?? parent.AddNode(new BranchNode(depth));
|
||||
|
||||
var index = branch.Literals.IndexOf(segment.Parts[0].Text);
|
||||
if (index == -1)
|
||||
{
|
||||
branch.Literals.Add(segment.Parts[0].Text);
|
||||
branch.AddNode(new SequenceNode(depth + 1));
|
||||
index = branch.Children.Count - 1;
|
||||
}
|
||||
|
||||
parent = (SequenceNode)branch.Children[index];
|
||||
}
|
||||
else if (segment.IsSimple && segment.Parts[0].IsParameter)
|
||||
{
|
||||
var parameter = parent.GetNode<ParameterNode>() ?? parent.AddNode(new ParameterNode(depth));
|
||||
if (parameter.Children.Count == 0)
|
||||
{
|
||||
parameter.AddNode(new SequenceNode(depth + 1));
|
||||
}
|
||||
|
||||
parent = (SequenceNode)parameter.Children[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
parent.AddNode(new AcceptNode(entry.Endpoint));
|
||||
}
|
||||
|
||||
var builder = new InstructionBuilder();
|
||||
for (var i = 0; i < roots.Count; i++)
|
||||
{
|
||||
roots[i].Lower(builder);
|
||||
}
|
||||
|
||||
var (instructions, endpoints, tables) = builder;
|
||||
return new InstructionMatcher(instructions, endpoints, tables);
|
||||
}
|
||||
|
||||
private OrderNode GetOrCreateRootNode(List<OrderNode> roots, int order)
|
||||
{
|
||||
OrderNode root = null;
|
||||
for (var j = 0; j < roots.Count; j++)
|
||||
{
|
||||
if (roots[j].Order == order)
|
||||
{
|
||||
root = roots[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
// Nodes are guaranteed to be in order because the entries are in order.
|
||||
root = new OrderNode(order);
|
||||
roots.Add(root);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class MinimalMatcher : Matcher
|
||||
{
|
||||
public static MatcherBuilder CreateBuilder() => new Builder();
|
||||
|
||||
private readonly (string pattern, Endpoint endpoint)[] _entries;
|
||||
|
||||
private MinimalMatcher((string pattern, Endpoint endpoint)[] entries)
|
||||
{
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
if (string.Equals(_entries[i].pattern, path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
feature.Endpoint = _entries[i].endpoint;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private class Builder : MatcherBuilder
|
||||
{
|
||||
private List<(string pattern, Endpoint endpoint)> _entries = new List<(string pattern, Endpoint endpoint)>();
|
||||
|
||||
public override void AddEntry(string pattern, MatcherEndpoint endpoint)
|
||||
{
|
||||
_entries.Add((pattern, endpoint));
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new MinimalMatcher(_entries.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class RouteMatcher : Matcher
|
||||
{
|
||||
public static MatcherBuilder CreateBuilder() => new Builder();
|
||||
|
||||
private IRouter _inner;
|
||||
|
||||
private RouteMatcher(IRouter inner)
|
||||
{
|
||||
_inner = inner;
|
||||
}
|
||||
|
||||
public async override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var context = new RouteContext(httpContext);
|
||||
await _inner.RouteAsync(context);
|
||||
|
||||
if (context.Handler != null)
|
||||
{
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
await context.Handler(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
private class Builder : MatcherBuilder
|
||||
{
|
||||
private readonly RouteCollection _routes = new RouteCollection();
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
|
||||
public Builder()
|
||||
{
|
||||
_constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()));
|
||||
}
|
||||
|
||||
public override void AddEntry(string pattern, MatcherEndpoint endpoint)
|
||||
{
|
||||
var handler = new RouteHandler(c =>
|
||||
{
|
||||
c.Features.Get<IEndpointFeature>().Endpoint = endpoint;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_routes.Add(new Route(handler, pattern, _constraintResolver));
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new RouteMatcher(_routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
public class SingleEntryMatcherBenchmark : MatcherBenchmarkBase
|
||||
{
|
||||
private Matcher _minimal;
|
||||
private Matcher _baseline;
|
||||
private Matcher _dfa;
|
||||
private Matcher _instruction;
|
||||
private Matcher _route;
|
||||
|
|
@ -28,26 +28,26 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
_requests[0].RequestServices = CreateServices();
|
||||
_requests[0].Request.Path = "/plaintext";
|
||||
|
||||
_minimal = SetupMatcher(MinimalMatcher.CreateBuilder());
|
||||
_dfa = SetupMatcher(DfaMatcher.CreateBuilder());
|
||||
_instruction = SetupMatcher(InstructionMatcher.CreateBuilder());
|
||||
_route = SetupMatcher(RouteMatcher.CreateBuilder());
|
||||
_tree = SetupMatcher(TreeRouterMatcher.CreateBuilder());
|
||||
_baseline = SetupMatcher(new TrivialMatcherBuilder());
|
||||
_dfa = SetupMatcher(new DfaMatcherBuilder());
|
||||
_instruction = SetupMatcher(new InstructionMatcherBuilder());
|
||||
_route = SetupMatcher(new RouteMatcherBuilder());
|
||||
_tree = SetupMatcher(new TreeRouterMatcherBuilder());
|
||||
|
||||
_feature = new EndpointFeature();
|
||||
}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
builder.AddEntry("/plaintext", _endpoints[0]);
|
||||
builder.AddEndpoint(_endpoints[0]);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Minimal()
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
await _minimal.MatchAsync(_requests[0], feature);
|
||||
await _baseline.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +71,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
public async Task LegacyRoute()
|
||||
{
|
||||
var feature = _feature;
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
_requests[0].Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _route.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
}
|
||||
|
|
@ -79,6 +83,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
public async Task LegacyTreeRouter()
|
||||
{
|
||||
var feature = _feature;
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
_requests[0].Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _tree.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
public class SmallEntryCountLiteralMatcherBenchark : MatcherBenchmarkBase
|
||||
{
|
||||
private Matcher _minimal;
|
||||
private Matcher _baseline;
|
||||
private Matcher _dfa;
|
||||
private Matcher _instruction;
|
||||
private Matcher _route;
|
||||
|
|
@ -20,46 +20,65 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_endpoints = new MatcherEndpoint[1];
|
||||
_endpoints[0] = CreateEndpoint("/plaintext");
|
||||
SetupEndpoints();
|
||||
|
||||
SetupRequests();
|
||||
|
||||
_baseline = SetupMatcher(new TrivialMatcherBuilder());
|
||||
_dfa = SetupMatcher(new DfaMatcherBuilder());
|
||||
_instruction = SetupMatcher(new InstructionMatcherBuilder());
|
||||
_route = SetupMatcher(new RouteMatcherBuilder());
|
||||
_tree = SetupMatcher(new TreeRouterMatcherBuilder());
|
||||
|
||||
_feature = new EndpointFeature();
|
||||
}
|
||||
|
||||
private void SetupEndpoints()
|
||||
{
|
||||
_endpoints = new MatcherEndpoint[10];
|
||||
_endpoints[0] = CreateEndpoint("/another-really-cool-entry");
|
||||
_endpoints[1] = CreateEndpoint("/Some-Entry");
|
||||
_endpoints[2] = CreateEndpoint("/a/path/with/more/segments");
|
||||
_endpoints[3] = CreateEndpoint("/random/name");
|
||||
_endpoints[4] = CreateEndpoint("/random/name2");
|
||||
_endpoints[5] = CreateEndpoint("/random/name3");
|
||||
_endpoints[6] = CreateEndpoint("/random/name4");
|
||||
_endpoints[7] = CreateEndpoint("/plaintext1");
|
||||
_endpoints[8] = CreateEndpoint("/plaintext2");
|
||||
_endpoints[9] = CreateEndpoint("/plaintext");
|
||||
}
|
||||
|
||||
private void SetupRequests()
|
||||
{
|
||||
_requests = new HttpContext[1];
|
||||
_requests[0] = new DefaultHttpContext();
|
||||
_requests[0].RequestServices = CreateServices();
|
||||
_requests[0].Request.Path = "/plaintext";
|
||||
|
||||
_minimal = SetupMatcher(MinimalMatcher.CreateBuilder());
|
||||
_dfa = SetupMatcher(DfaMatcher.CreateBuilder());
|
||||
_instruction = SetupMatcher(InstructionMatcher.CreateBuilder());
|
||||
_route = SetupMatcher(RouteMatcher.CreateBuilder());
|
||||
_tree = SetupMatcher(TreeRouterMatcher.CreateBuilder());
|
||||
|
||||
_feature = new EndpointFeature();
|
||||
}
|
||||
|
||||
// For this case we're specifically targeting the last entry to hit 'worst case'
|
||||
// performance for the matchers that scale linearly.
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
builder.AddEntry("/another-really-cool-entry", null);
|
||||
builder.AddEntry("/Some-Entry", null);
|
||||
builder.AddEntry("/a/path/with/more/segments", null);
|
||||
builder.AddEntry("/random/name", null);
|
||||
builder.AddEntry("/random/name2", null);
|
||||
builder.AddEntry("/random/name3", null);
|
||||
builder.AddEntry("/random/name4", null);
|
||||
builder.AddEntry("/plaintext1", null);
|
||||
builder.AddEntry("/plaintext2", null);
|
||||
builder.AddEntry("/plaintext", _endpoints[0]);
|
||||
builder.AddEndpoint(_endpoints[0]);
|
||||
builder.AddEndpoint(_endpoints[1]);
|
||||
builder.AddEndpoint(_endpoints[2]);
|
||||
builder.AddEndpoint(_endpoints[3]);
|
||||
builder.AddEndpoint(_endpoints[4]);
|
||||
builder.AddEndpoint(_endpoints[5]);
|
||||
builder.AddEndpoint(_endpoints[6]);
|
||||
builder.AddEndpoint(_endpoints[7]);
|
||||
builder.AddEndpoint(_endpoints[8]);
|
||||
builder.AddEndpoint(_endpoints[9]);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Minimal()
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
await _minimal.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
await _baseline.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[9], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
|
@ -67,7 +86,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
var feature = _feature;
|
||||
await _dfa.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
Validate(_requests[0], _endpoints[9], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
|
@ -75,23 +94,31 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
var feature = _feature;
|
||||
await _instruction.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
Validate(_requests[0], _endpoints[9], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task LegacyRoute()
|
||||
{
|
||||
var feature = _feature;
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
_requests[0].Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _route.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
Validate(_requests[0], _endpoints[9], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task LegacyTreeRouter()
|
||||
{
|
||||
var feature = _feature;
|
||||
|
||||
// This is required to make the legacy router implementation work with dispatcher.
|
||||
_requests[0].Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _tree.MatchAsync(_requests[0], feature);
|
||||
Validate(_requests[0], _endpoints[0], feature.Endpoint);
|
||||
Validate(_requests[0], _endpoints[9], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class TreeRouterMatcher : Matcher
|
||||
{
|
||||
public static MatcherBuilder CreateBuilder() => new Builder();
|
||||
|
||||
private TreeRouter _inner;
|
||||
|
||||
private TreeRouterMatcher(TreeRouter inner)
|
||||
{
|
||||
_inner = inner;
|
||||
}
|
||||
|
||||
public async override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var context = new RouteContext(httpContext);
|
||||
await _inner.RouteAsync(context);
|
||||
|
||||
if (context.Handler != null)
|
||||
{
|
||||
await context.Handler(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
private class Builder : MatcherBuilder
|
||||
{
|
||||
private readonly TreeRouteBuilder _inner;
|
||||
|
||||
public Builder()
|
||||
{
|
||||
_inner = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
}
|
||||
|
||||
public override void AddEntry(string template, MatcherEndpoint endpoint)
|
||||
{
|
||||
var handler = new RouteHandler(c =>
|
||||
{
|
||||
c.Features.Get<IEndpointFeature>().Endpoint = endpoint;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_inner.MapInbound(handler, TemplateParser.Parse(template), "default", 0);
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new TreeRouterMatcher(_inner.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
// A test-only matcher implementation - used as a baseline for simpler
|
||||
// perf tests. The idea with this matcher is that we can cheat on the requirements
|
||||
// to establish a lower bound for perf comparisons.
|
||||
internal class TrivialMatcher : Matcher
|
||||
{
|
||||
private readonly MatcherEndpoint _endpoint;
|
||||
|
||||
public TrivialMatcher(MatcherEndpoint endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
public override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
if (string.Equals(_endpoint.Template, path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
feature.Endpoint = _endpoint;
|
||||
feature.Values = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class TrivialMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private readonly List<MatcherEndpoint> _endpoints = new List<MatcherEndpoint>();
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new TrivialMatcher(_endpoints.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,46 @@
|
|||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Some sources are shared with the unit test so we can benchmark some 'test only' implementations
|
||||
for perf comparisons.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\MatcherBuilder.cs">
|
||||
<Link>Matchers\MatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\BarebonesMatcher.cs">
|
||||
<Link>Matchers\BarebonesMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\BarebonesMatcherBuilder.cs">
|
||||
<Link>Matchers\BarebonesMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\DfaMatcher.cs">
|
||||
<Link>Matchers\DfaMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\DfaMatcherBuilder.cs">
|
||||
<Link>Matchers\DfaMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\InstructionMatcher.cs">
|
||||
<Link>Matchers\InstructionMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\InstructionMatcherBuilder.cs">
|
||||
<Link>Matchers\InstructionMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\RouteMatcher.cs">
|
||||
<Link>Matchers\RouteMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\RouteMatcherBuilder.cs">
|
||||
<Link>Matchers\RouteMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\TreeRouterMatcher.cs">
|
||||
<Link>Matchers\TreeRouterMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matchers\TreeRouterMatcherBuilder.cs">
|
||||
<Link>Matchers\TreeRouterMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Swaggatherer
|
|||
var setupMatcherLines = new List<string>();
|
||||
for (var i = 0; i < entries.Count; i++)
|
||||
{
|
||||
setupMatcherLines.Add($" builder.AddEntry(\"{entries[i].Template.TemplateText}\", _endpoints[{i}]);");
|
||||
setupMatcherLines.Add($" builder.AddEndpoint(_endpoints[{i}]);");
|
||||
}
|
||||
|
||||
return string.Format(@"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
// A test-only matcher implementation - used as a baseline for more compilated
|
||||
// perf tests. The idea with this matcher is that we can cheat on the requirements
|
||||
// to establish a lower bound for perf comparisons.
|
||||
internal class BarebonesMatcher : Matcher
|
||||
{
|
||||
public readonly InnerMatcher[] _matchers;
|
||||
|
||||
public BarebonesMatcher(InnerMatcher[] matchers)
|
||||
{
|
||||
_matchers = matchers;
|
||||
}
|
||||
|
||||
public override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _matchers.Length; i++)
|
||||
{
|
||||
if (_matchers[i].TryMatch(httpContext, feature))
|
||||
{
|
||||
feature.Endpoint = _matchers[i]._endpoint;
|
||||
feature.Values = new RouteValueDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public sealed class InnerMatcher : Matcher
|
||||
{
|
||||
private readonly string[] _segments;
|
||||
public readonly MatcherEndpoint _endpoint;
|
||||
|
||||
public InnerMatcher(string[] segments, MatcherEndpoint endpoint)
|
||||
{
|
||||
_segments = segments;
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
public bool TryMatch(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
var segment = 0;
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
|
||||
var start = 1; // PathString always has a leading slash
|
||||
var end = 0;
|
||||
while ((end = path.IndexOf('/', start)) >= 0)
|
||||
{
|
||||
var comparand = _segments.Length > segment ? _segments[segment] : null;
|
||||
if ((comparand == null && end - start == 0) ||
|
||||
(comparand != null &&
|
||||
(comparand.Length != end - start ||
|
||||
string.Compare(
|
||||
path,
|
||||
start,
|
||||
comparand,
|
||||
0,
|
||||
comparand.Length,
|
||||
StringComparison.OrdinalIgnoreCase) != 0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
segment++;
|
||||
}
|
||||
|
||||
// residue
|
||||
var length = path.Length - start;
|
||||
if (length > 0)
|
||||
{
|
||||
var comparand = _segments.Length > segment ? _segments[segment] : null;
|
||||
if (comparand != null &&
|
||||
(comparand.Length != length ||
|
||||
string.Compare(
|
||||
path,
|
||||
start,
|
||||
comparand,
|
||||
0,
|
||||
comparand.Length,
|
||||
StringComparison.OrdinalIgnoreCase) != 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
segment++;
|
||||
}
|
||||
|
||||
return segment == _segments.Length;
|
||||
}
|
||||
|
||||
public override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (TryMatch(httpContext, feature))
|
||||
{
|
||||
feature.Endpoint = _endpoint;
|
||||
feature.Values = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using static Microsoft.AspNetCore.Routing.Matchers.BarebonesMatcher;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class BarebonesMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private List<MatcherEndpoint> _endpoints = new List<MatcherEndpoint>();
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
var matchers = new InnerMatcher[_endpoints.Count];
|
||||
for (var i = 0; i < _endpoints.Count; i++)
|
||||
{
|
||||
var parsed = TemplateParser.Parse(_endpoints[i].Template);
|
||||
var segments = parsed.Segments
|
||||
.Select(s => s.IsSimple && s.Parts[0].IsLiteral ? s.Parts[0].Text : null)
|
||||
.ToArray();
|
||||
matchers[i] = new InnerMatcher(segments, _endpoints[i]);
|
||||
}
|
||||
|
||||
return new BarebonesMatcher(matchers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class BarebonesMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
// Route values not supported
|
||||
[Fact]
|
||||
public override Task Match_SingleParameter()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Route values not supported
|
||||
[Fact]
|
||||
public override Task Match_SingleParameter_TrailingSlash()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Route values not supported
|
||||
[Theory]
|
||||
[InlineData(null, null, null, null)]
|
||||
public override Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
GC.KeepAlive(new object [] { template, path, keys, values });
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
{
|
||||
var builder = new BarebonesMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class DfaMatcher : Matcher
|
||||
{
|
||||
private readonly State[] _states;
|
||||
|
||||
public DfaMatcher(State[] states)
|
||||
{
|
||||
_states = states;
|
||||
}
|
||||
|
||||
public unsafe override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var states = _states;
|
||||
var current = 0;
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
|
||||
// This section tokenizes the path by marking the sequence of slashes, and their
|
||||
// position in the string. The consuming code uses the sequence and the count of
|
||||
// slashes to deduce the length of each segment.
|
||||
//
|
||||
// If there is residue (text after last slash) then the length of the segment will
|
||||
// computed based on the string length.
|
||||
var buffer = stackalloc Segment[32];
|
||||
var segment = 0;
|
||||
var start = 1; // PathString guarantees a leading /
|
||||
var end = 0;
|
||||
var length = 0;
|
||||
while ((end = path.IndexOf('/', start)) >= 0 && segment < 32)
|
||||
{
|
||||
length = end - start;
|
||||
buffer[segment++] = new Segment() { Start = start, Length = length, };
|
||||
current = states[current].Transitions.GetDestination(path, start, length);
|
||||
|
||||
start = end + 1; // resume search after the current character
|
||||
}
|
||||
|
||||
// Residue
|
||||
length = path.Length - start;
|
||||
if (length > 0)
|
||||
{
|
||||
buffer[segment++] = new Segment() { Start = start, Length = length, };
|
||||
current = states[current].Transitions.GetDestination(path, start, length);
|
||||
}
|
||||
|
||||
var matches = new List<(Endpoint, RouteValueDictionary)>();
|
||||
|
||||
var candidates = states[current].Matches;
|
||||
for (var i = 0; i < candidates.Length; i++)
|
||||
{
|
||||
var values = new RouteValueDictionary();
|
||||
var parameters = candidates[i].Parameters;
|
||||
if (parameters != null)
|
||||
{
|
||||
for (var j = 0; j < parameters.Length; j++)
|
||||
{
|
||||
var parameter = parameters[j];
|
||||
if (parameter != null && buffer[j].Length == 0)
|
||||
{
|
||||
goto notmatch;
|
||||
}
|
||||
else if (parameter != null)
|
||||
{
|
||||
var value = path.Substring(buffer[j].Start, buffer[j].Length);
|
||||
values.Add(parameter, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches.Add((candidates[i].Endpoint, values));
|
||||
|
||||
notmatch: ;
|
||||
}
|
||||
|
||||
feature.Endpoint = matches.Count == 0 ? null : matches[0].Item1;
|
||||
feature.Values = matches.Count == 0 ? null : matches[0].Item2;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public struct Segment
|
||||
{
|
||||
public int Start;
|
||||
public int Length;
|
||||
}
|
||||
|
||||
public struct State
|
||||
{
|
||||
public bool IsAccepting;
|
||||
public Candidate[] Matches;
|
||||
public JumpTable Transitions;
|
||||
}
|
||||
|
||||
public struct Candidate
|
||||
{
|
||||
public Endpoint Endpoint;
|
||||
public string[] Parameters;
|
||||
}
|
||||
|
||||
public abstract class JumpTable
|
||||
{
|
||||
public abstract int GetDestination(string text, int start, int length);
|
||||
}
|
||||
|
||||
public class JumpTableBuilder
|
||||
{
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
public int Depth { get; set; }
|
||||
|
||||
public int Exit { get; set; }
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
return new SimpleJumpTable(Depth, Exit, _entries.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleJumpTable : JumpTable
|
||||
{
|
||||
private readonly (string text, int destination)[] _entries;
|
||||
private readonly int _depth;
|
||||
private readonly int _exit;
|
||||
|
||||
public SimpleJumpTable(int depth, int exit, (string text, int destination)[] entries)
|
||||
{
|
||||
_depth = depth;
|
||||
_exit = exit;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public override int GetDestination(string text, int start, int length)
|
||||
{
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
if (length == _entries[i].text.Length &&
|
||||
string.Compare(
|
||||
text,
|
||||
start,
|
||||
_entries[i].text,
|
||||
0,
|
||||
length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return _entries[i].destination;
|
||||
}
|
||||
}
|
||||
|
||||
return _exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using static Microsoft.AspNetCore.Routing.Matchers.DfaMatcher;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class DfaMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private List<Entry> _entries = new List<Entry>();
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
var parsed = TemplateParser.Parse(endpoint.Template);
|
||||
_entries.Add(new Entry()
|
||||
{
|
||||
Order = 0,
|
||||
Pattern = parsed,
|
||||
Precedence = RoutePrecedence.ComputeInbound(parsed),
|
||||
Endpoint = endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
Sort(_entries);
|
||||
|
||||
var root = new Node() { Depth = -1 };
|
||||
|
||||
// We build the tree by doing a BFS over the list of entries. This is important
|
||||
// because a 'parameter' node can also traverse the same paths that literal nodes traverse.
|
||||
var maxDepth = 0;
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
maxDepth = Math.Max(maxDepth, _entries[i].Pattern.Segments.Count);
|
||||
}
|
||||
|
||||
for (var depth = 0; depth <= maxDepth; depth++)
|
||||
{
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Pattern.Segments.Count < depth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the parents of this edge at the current depth
|
||||
var parents = new List<Node>() { root };
|
||||
for (var j = 0; j < depth; j++)
|
||||
{
|
||||
var next = new List<Node>();
|
||||
for (var k = 0; k < parents.Count; k++)
|
||||
{
|
||||
next.Add(Traverse(parents[k], entry.Pattern.Segments[j]));
|
||||
}
|
||||
|
||||
parents = next;
|
||||
}
|
||||
|
||||
if (entry.Pattern.Segments.Count == depth)
|
||||
{
|
||||
for (var j = 0; j < parents.Count; j++)
|
||||
{
|
||||
var parent = parents[j];
|
||||
parent.Matches.Add(entry);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var segment = entry.Pattern.Segments[depth];
|
||||
for (var j = 0; j < parents.Count; j++)
|
||||
{
|
||||
var parent = parents[j];
|
||||
if (segment.IsSimple && segment.Parts[0].IsLiteral)
|
||||
{
|
||||
if (!parent.Literals.TryGetValue(segment.Parts[0].Text, out var next))
|
||||
{
|
||||
next = new Node() { Depth = depth, };
|
||||
parent.Literals.Add(segment.Parts[0].Text, next);
|
||||
}
|
||||
}
|
||||
else if (segment.IsSimple && segment.Parts[0].IsParameter)
|
||||
{
|
||||
if (!parent.Literals.TryGetValue("*", out var next))
|
||||
{
|
||||
next = new Node() { Depth = depth, };
|
||||
parent.Literals.Add("*", next);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("We only support simple segments.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var states = new List<State>();
|
||||
var tables = new List<JumpTableBuilder>();
|
||||
AddNode(root, states, tables);
|
||||
|
||||
var exit = states.Count;
|
||||
states.Add(new State() { IsAccepting = false, Matches = Array.Empty<Candidate>(), });
|
||||
tables.Add(new JumpTableBuilder() { Exit = exit, });
|
||||
|
||||
for (var i = 0; i < tables.Count; i++)
|
||||
{
|
||||
if (tables[i].Exit == -1)
|
||||
{
|
||||
tables[i].Exit = exit;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < states.Count; i++)
|
||||
{
|
||||
states[i] = new State()
|
||||
{
|
||||
IsAccepting = states[i].IsAccepting,
|
||||
Matches = states[i].Matches,
|
||||
Transitions = tables[i].Build(),
|
||||
};
|
||||
}
|
||||
|
||||
return new DfaMatcher(states.ToArray());
|
||||
}
|
||||
|
||||
private Node Traverse(Node node, TemplateSegment segment)
|
||||
{
|
||||
if (!segment.IsSimple)
|
||||
{
|
||||
throw new InvalidOperationException("We only support simple segments.");
|
||||
}
|
||||
|
||||
if (segment.Parts[0].IsLiteral)
|
||||
{
|
||||
return node.Literals[segment.Parts[0].Text];
|
||||
}
|
||||
|
||||
return node.Literals["*"];
|
||||
}
|
||||
|
||||
private static int AddNode(Node node, List<State> states, List<JumpTableBuilder> tables)
|
||||
{
|
||||
Sort(node.Matches);
|
||||
|
||||
var index = states.Count;
|
||||
states.Add(new State()
|
||||
{
|
||||
Matches = node.Matches.Select(CreateCandidate).ToArray(),
|
||||
IsAccepting = node.Matches.Count > 0,
|
||||
});
|
||||
|
||||
var table = new JumpTableBuilder() { Depth = node.Depth, };
|
||||
tables.Add(table);
|
||||
|
||||
foreach (var kvp in node.Literals)
|
||||
{
|
||||
if (kvp.Key == "*")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var transition = AddNode(kvp.Value, states, tables);
|
||||
table.AddEntry(kvp.Key, transition);
|
||||
}
|
||||
|
||||
var exitIndex = -1;
|
||||
if (node.Literals.TryGetValue("*", out var exit))
|
||||
{
|
||||
exitIndex = AddNode(exit, states, tables);
|
||||
}
|
||||
|
||||
table.Exit = exitIndex;
|
||||
return index;
|
||||
}
|
||||
|
||||
private static Candidate CreateCandidate(Entry entry)
|
||||
{
|
||||
return new Candidate()
|
||||
{
|
||||
Endpoint = entry.Endpoint,
|
||||
Parameters = entry.Pattern.Segments.Select(s => s.IsSimple && s.Parts[0].IsParameter ? s.Parts[0].Name : null).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
private static void Sort(List<Entry> entries)
|
||||
{
|
||||
entries.Sort((x, y) =>
|
||||
{
|
||||
var comparison = x.Order.CompareTo(y.Order);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
comparison = x.Precedence.CompareTo(y.Precedence);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
return x.Pattern.TemplateText.CompareTo(y.Pattern.TemplateText);
|
||||
});
|
||||
}
|
||||
|
||||
private static Node DeepCopy(Node node)
|
||||
{
|
||||
var copy = new Node() { Depth = node.Depth, };
|
||||
copy.Matches.AddRange(node.Matches);
|
||||
|
||||
foreach (var kvp in node.Literals)
|
||||
{
|
||||
copy.Literals.Add(kvp.Key, DeepCopy(kvp.Value));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private class Entry
|
||||
{
|
||||
public int Order;
|
||||
public decimal Precedence;
|
||||
public RouteTemplate Pattern;
|
||||
public Endpoint Endpoint;
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private class Node
|
||||
{
|
||||
public int Depth { get; set; }
|
||||
|
||||
public List<Entry> Matches { get; } = new List<Entry>();
|
||||
|
||||
public Dictionary<string, Node> Literals { get; } = new Dictionary<string, Node>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("d:");
|
||||
builder.Append(Depth);
|
||||
builder.Append(" m:");
|
||||
builder.Append(Matches.Count);
|
||||
builder.Append(" c: ");
|
||||
builder.Append(string.Join(", ", Literals.Select(kvp => $"{kvp.Key}->({kvp.Value.DebuggerToString()})")));
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class DfaMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
// Route values not supported
|
||||
[Fact]
|
||||
public override Task Match_SingleParameter_TrailingSlash()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Route values not supported
|
||||
[Theory]
|
||||
[InlineData(null, null, null, null)]
|
||||
public override Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
GC.KeepAlive(new object[] { template, path, keys, values });
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
{
|
||||
var builder = new DfaMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit.Sdk;
|
||||
|
||||
|
|
@ -13,6 +14,17 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
AssertMatch(feature, expected, new RouteValueDictionary());
|
||||
}
|
||||
|
||||
public static void AssertMatch(IEndpointFeature feature, Endpoint expected, string[] keys, string[] values)
|
||||
{
|
||||
if (keys.Length != values.Length)
|
||||
{
|
||||
throw new XunitException($"Keys and Values must be the same length.");
|
||||
}
|
||||
|
||||
var zipped = keys.Zip(values, (k, v) => new KeyValuePair<string, object>(k, v));
|
||||
AssertMatch(feature, expected, new RouteValueDictionary(zipped));
|
||||
}
|
||||
|
||||
public static void AssertMatch(IEndpointFeature feature, Endpoint expected, RouteValueDictionary values)
|
||||
{
|
||||
if (feature.Endpoint == null)
|
||||
|
|
@ -20,6 +32,11 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
throw new XunitException($"Was expected to match '{expected.DisplayName}' but did not match.");
|
||||
}
|
||||
|
||||
if (feature.Values == null)
|
||||
{
|
||||
throw new XunitException("Values is null.");
|
||||
}
|
||||
|
||||
if (!object.ReferenceEquals(expected, feature.Endpoint))
|
||||
{
|
||||
throw new XunitException(
|
||||
|
|
@ -27,6 +44,8 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
$"'{feature.Endpoint.DisplayName}' with values: {FormatRouteValues(feature.Values)}.");
|
||||
}
|
||||
|
||||
// Note: this comparison is intended for unit testing, and is stricter than necessary to make tests
|
||||
// more precise. Route value comparisons in product code are more flexible than a simple .Equals.
|
||||
if (values.Count != feature.Values.Count ||
|
||||
!values.OrderBy(kvp => kvp.Key).SequenceEqual(feature.Values.OrderBy(kvp => kvp.Key)))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class InstructionMatcher : Matcher
|
||||
{
|
||||
private State _state;
|
||||
|
||||
public InstructionMatcher(Instruction[] instructions, Candidate[] candidates, JumpTable[] tables)
|
||||
{
|
||||
_state = new State()
|
||||
{
|
||||
Instructions = instructions,
|
||||
Candidates = candidates,
|
||||
Tables = tables,
|
||||
};
|
||||
}
|
||||
|
||||
public unsafe override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var state = _state;
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
|
||||
// This section tokenizes the path by marking the sequence of slashes, and their
|
||||
// position in the string. The consuming code uses the sequence and the count of
|
||||
// slashes to deduce the length of each segment.
|
||||
//
|
||||
// If there is residue (text after last slash) then the length of the segment will
|
||||
// computed based on the string length.
|
||||
var buffer = stackalloc Segment[32];
|
||||
var count = 0;
|
||||
var start = 1; // PathString guarantees a leading /
|
||||
var end = 0;
|
||||
while ((end = path.IndexOf('/', start)) >= 0 && count < 32)
|
||||
{
|
||||
buffer[count++] = new Segment() { Start = start, Length = end - start, };
|
||||
start = end + 1; // resume search after the current character
|
||||
}
|
||||
|
||||
// Residue
|
||||
if (start < path.Length)
|
||||
{
|
||||
buffer[count++] = new Segment() { Start = start, Length = path.Length - start, };
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
Candidate match = default(Candidate);
|
||||
while (i < state.Instructions.Length)
|
||||
{
|
||||
var instruction = state.Instructions[i];
|
||||
switch (instruction.Code)
|
||||
{
|
||||
case InstructionCode.Accept:
|
||||
{
|
||||
if (count == instruction.Depth)
|
||||
{
|
||||
match = state.Candidates[instruction.Payload];
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case InstructionCode.Branch:
|
||||
{
|
||||
var table = state.Tables[instruction.Payload];
|
||||
i = table.GetDestination(buffer, count, path);
|
||||
break;
|
||||
}
|
||||
case InstructionCode.Jump:
|
||||
{
|
||||
i = instruction.Payload;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match.Endpoint != null)
|
||||
{
|
||||
var values = new RouteValueDictionary();
|
||||
var parameters = match.Parameters;
|
||||
if (parameters != null)
|
||||
{
|
||||
for (var j = 0; j < parameters.Length; j++)
|
||||
{
|
||||
var parameter = parameters[j];
|
||||
if (parameter != null && buffer[j].Length == 0)
|
||||
{
|
||||
goto notmatch;
|
||||
}
|
||||
else if (parameter != null)
|
||||
{
|
||||
var value = path.Substring(buffer[j].Start, buffer[j].Length);
|
||||
values.Add(parameter, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feature.Endpoint = match.Endpoint;
|
||||
feature.Values = values;
|
||||
|
||||
notmatch:;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public struct Segment
|
||||
{
|
||||
public int Start;
|
||||
public int Length;
|
||||
}
|
||||
|
||||
public struct Candidate
|
||||
{
|
||||
public Endpoint Endpoint;
|
||||
public string[] Parameters;
|
||||
}
|
||||
|
||||
public class State
|
||||
{
|
||||
public Candidate[] Candidates;
|
||||
public Instruction[] Instructions;
|
||||
public JumpTable[] Tables;
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToDebugString(),nq}")]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Instruction
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public byte Depth;
|
||||
|
||||
[FieldOffset(3)]
|
||||
public InstructionCode Code;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public int Payload;
|
||||
|
||||
private string ToDebugString()
|
||||
{
|
||||
return $"{Code}: {Payload}";
|
||||
}
|
||||
}
|
||||
|
||||
public enum InstructionCode : byte
|
||||
{
|
||||
Accept,
|
||||
Branch,
|
||||
Jump,
|
||||
Pop, // Only used during the instruction builder phase
|
||||
}
|
||||
|
||||
public abstract class JumpTable
|
||||
{
|
||||
public unsafe abstract int GetDestination(Segment* segments, int count, string path);
|
||||
}
|
||||
|
||||
public class JumpTableBuilder
|
||||
{
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
public int Depth { get; set; }
|
||||
|
||||
public int Exit { get; set; }
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
return new SimpleJumpTable(Depth, Exit, _entries.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleJumpTable : JumpTable
|
||||
{
|
||||
private readonly (string text, int destination)[] _entries;
|
||||
private readonly int _depth;
|
||||
private readonly int _exit;
|
||||
|
||||
public SimpleJumpTable(int depth, int exit, (string text, int destination)[] entries)
|
||||
{
|
||||
_depth = depth;
|
||||
_exit = exit;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public unsafe override int GetDestination(Segment* segments, int count, string path)
|
||||
{
|
||||
if (_depth == count)
|
||||
{
|
||||
return _exit;
|
||||
}
|
||||
|
||||
var start = segments[_depth].Start;
|
||||
var length = segments[_depth].Length;
|
||||
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
if (length == _entries[i].text.Length &&
|
||||
string.Compare(
|
||||
path,
|
||||
start,
|
||||
_entries[i].text,
|
||||
0,
|
||||
length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return _entries[i].destination;
|
||||
}
|
||||
}
|
||||
|
||||
return _exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using static Microsoft.AspNetCore.Routing.Matchers.InstructionMatcher;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class InstructionMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private List<Entry> _entries = new List<Entry>();
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
var parsed = TemplateParser.Parse(endpoint.Template);
|
||||
_entries.Add(new Entry()
|
||||
{
|
||||
Order = 0,
|
||||
Pattern = parsed,
|
||||
Precedence = RoutePrecedence.ComputeInbound(parsed),
|
||||
Endpoint = endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
_entries.Sort((x, y) =>
|
||||
{
|
||||
var comparison = x.Order.CompareTo(y.Order);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
comparison = y.Precedence.CompareTo(x.Precedence);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
return x.Pattern.TemplateText.CompareTo(y.Pattern.TemplateText);
|
||||
});
|
||||
|
||||
var roots = new List<OrderNode>();
|
||||
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
|
||||
var parent = (SequenceNode)GetOrCreateRootNode(roots, entry.Order);
|
||||
|
||||
var depth = 0;
|
||||
for (; depth < entry.Pattern.Segments.Count; depth++)
|
||||
{
|
||||
var segment = entry.Pattern.Segments[depth];
|
||||
if (segment.IsSimple && segment.Parts[0].IsLiteral)
|
||||
{
|
||||
var branch = parent.GetNode<BranchNode>() ?? parent.AddNode(new BranchNode(depth));
|
||||
|
||||
var index = -1;
|
||||
for (var j = 0; j < branch.Literals.Count; j++)
|
||||
{
|
||||
if (string.Equals(segment.Parts[0].Text, branch.Literals[j], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
branch.Literals.Add(segment.Parts[0].Text);
|
||||
branch.AddNode(new SequenceNode(depth + 1));
|
||||
index = branch.Children.Count - 1;
|
||||
}
|
||||
|
||||
parent = (SequenceNode)branch.Children[index];
|
||||
}
|
||||
else if (segment.IsSimple && segment.Parts[0].IsParameter)
|
||||
{
|
||||
var parameter = parent.GetNode<ParameterNode>() ?? parent.AddNode(new ParameterNode(depth));
|
||||
if (parameter.Children.Count == 0)
|
||||
{
|
||||
parameter.AddNode(new SequenceNode(depth + 1));
|
||||
}
|
||||
|
||||
parent = (SequenceNode)parameter.Children[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
parent.AddNode(new AcceptNode(depth, entry.Endpoint));
|
||||
}
|
||||
|
||||
var builder = new InstructionBuilder();
|
||||
for (var i = 0; i < roots.Count; i++)
|
||||
{
|
||||
roots[i].Lower(builder);
|
||||
}
|
||||
|
||||
var (instructions, endpoints, tables) = builder;
|
||||
var candidates = new Candidate[endpoints.Length];
|
||||
for (var i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
candidates[i] = CreateCandidate(endpoints[i]);
|
||||
}
|
||||
|
||||
return new InstructionMatcher(instructions, candidates, tables);
|
||||
}
|
||||
|
||||
private OrderNode GetOrCreateRootNode(List<OrderNode> roots, int order)
|
||||
{
|
||||
OrderNode root = null;
|
||||
for (var j = 0; j < roots.Count; j++)
|
||||
{
|
||||
if (roots[j].Order == order)
|
||||
{
|
||||
root = roots[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
// Nodes are guaranteed to be in order because the entries are in order.
|
||||
root = new OrderNode(order);
|
||||
roots.Add(root);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static Candidate CreateCandidate(MatcherEndpoint endpoint)
|
||||
{
|
||||
var parsed = TemplateParser.Parse(endpoint.Template);
|
||||
return new Candidate()
|
||||
{
|
||||
Endpoint = endpoint,
|
||||
Parameters = parsed.Segments.Select(s => s.IsSimple && s.Parts[0].IsParameter ? s.Parts[0].Name : null).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
private class Entry
|
||||
{
|
||||
public int Order;
|
||||
public decimal Precedence;
|
||||
public RouteTemplate Pattern;
|
||||
public MatcherEndpoint Endpoint;
|
||||
}
|
||||
|
||||
private class InstructionBuilder
|
||||
{
|
||||
private readonly List<Instruction> _instructions = new List<Instruction>();
|
||||
private readonly List<MatcherEndpoint> _endpoints = new List<MatcherEndpoint>();
|
||||
private readonly List<JumpTableBuilder> _tables = new List<JumpTableBuilder>();
|
||||
|
||||
private readonly List<int> _blocks = new List<int>();
|
||||
|
||||
public int Next => _instructions.Count;
|
||||
|
||||
public void BeginBlock()
|
||||
{
|
||||
_blocks.Add(Next);
|
||||
}
|
||||
|
||||
public void EndBlock()
|
||||
{
|
||||
var start = _blocks[_blocks.Count - 1];
|
||||
var end = Next;
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
if (_instructions[i].Code == InstructionCode.Pop)
|
||||
{
|
||||
_instructions[i] = new Instruction()
|
||||
{
|
||||
Code = InstructionCode.Jump,
|
||||
Depth = _instructions[i].Depth,
|
||||
Payload = end,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_blocks.RemoveAt(_blocks.Count - 1);
|
||||
}
|
||||
|
||||
public int AddInstruction(Instruction instruction)
|
||||
{
|
||||
_instructions.Add(instruction);
|
||||
return _instructions.Count - 1;
|
||||
}
|
||||
|
||||
public int AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
_endpoints.Add(endpoint);
|
||||
return _endpoints.Count - 1;
|
||||
}
|
||||
|
||||
public int AddJumpTable(JumpTableBuilder table)
|
||||
{
|
||||
_tables.Add(table);
|
||||
return _tables.Count - 1;
|
||||
}
|
||||
|
||||
public void Deconstruct(
|
||||
out Instruction[] instructions,
|
||||
out MatcherEndpoint[] endpoints,
|
||||
out JumpTable[] tables)
|
||||
{
|
||||
instructions = _instructions.ToArray();
|
||||
endpoints = _endpoints.ToArray();
|
||||
|
||||
tables = new JumpTable[_tables.Count];
|
||||
for (var i = 0; i < _tables.Count; i++)
|
||||
{
|
||||
tables[i] = _tables[i].Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class Node
|
||||
{
|
||||
public int Depth { get; protected set; }
|
||||
public List<Node> Children { get; } = new List<Node>();
|
||||
|
||||
public abstract void Lower(InstructionBuilder builder);
|
||||
|
||||
public TNode GetNode<TNode>() where TNode : Node
|
||||
{
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is TNode match)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TNode AddNode<TNode>(TNode node) where TNode : Node
|
||||
{
|
||||
// We already ordered the routes into precedence order
|
||||
Children.Add(node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private class SequenceNode : Node
|
||||
{
|
||||
public SequenceNode(int depth)
|
||||
{
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
Children[i].Lower(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OrderNode : SequenceNode
|
||||
{
|
||||
public OrderNode(int order)
|
||||
: base(0)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public int Order { get; }
|
||||
}
|
||||
|
||||
private class BranchNode : Node
|
||||
{
|
||||
public BranchNode(int depth)
|
||||
{
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public List<string> Literals { get; } = new List<string>();
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
var table = new JumpTableBuilder() { Depth = Depth, };
|
||||
var index = builder.AddJumpTable(table);
|
||||
builder.AddInstruction(new Instruction()
|
||||
{
|
||||
Code = InstructionCode.Branch,
|
||||
Depth = (byte)Depth,
|
||||
Payload = index
|
||||
});
|
||||
|
||||
builder.BeginBlock();
|
||||
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
table.AddEntry(Literals[i], builder.Next);
|
||||
Children[i].Lower(builder);
|
||||
builder.AddInstruction(new Instruction()
|
||||
{
|
||||
Code = InstructionCode.Pop,
|
||||
Depth = (byte)Depth,
|
||||
});
|
||||
}
|
||||
|
||||
builder.EndBlock();
|
||||
table.Exit = builder.Next;
|
||||
}
|
||||
}
|
||||
|
||||
private class ParameterNode : Node
|
||||
{
|
||||
public ParameterNode(int depth)
|
||||
{
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
{
|
||||
Children[i].Lower(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AcceptNode : Node
|
||||
{
|
||||
public AcceptNode(int depth, MatcherEndpoint endpoint)
|
||||
{
|
||||
Depth = depth;
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
|
||||
public MatcherEndpoint Endpoint { get; }
|
||||
|
||||
public override void Lower(InstructionBuilder builder)
|
||||
{
|
||||
builder.AddInstruction(new Instruction()
|
||||
{
|
||||
Code = InstructionCode.Accept,
|
||||
Depth = (byte)Depth,
|
||||
Payload = builder.AddEndpoint(Endpoint),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class InstructionMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
{
|
||||
var builder = new InstructionMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
{
|
||||
internal abstract class MatcherBuilder
|
||||
{
|
||||
public abstract void AddEntry(string template, MatcherEndpoint endpoint);
|
||||
public abstract void AddEndpoint(MatcherEndpoint endpoint);
|
||||
|
||||
public abstract Matcher Build();
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -14,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
internal abstract Matcher CreateMatcher(MatcherEndpoint endpoint);
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleLiteralSegment_Success()
|
||||
public virtual async Task Match_SingleLiteralSegment()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
|
|
@ -27,11 +29,98 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext("/simple/");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/simple")]
|
||||
[InlineData("/sImpLe")]
|
||||
[InlineData("/SIMPLE")]
|
||||
public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/Simple");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Some matchers will optimize for the ASCII case
|
||||
[Theory]
|
||||
[InlineData("/SÏmple", "/SÏmple")]
|
||||
[InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
|
||||
public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers should operate on the decoded representation - a matcher that calls
|
||||
// `httpContext.Request.Path.ToString()` will break this test.
|
||||
[Theory]
|
||||
[InlineData("/S%mple", "/S%mple")]
|
||||
[InlineData("/S\\imple", "/S\\imple")] // surrogate pair
|
||||
public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/imple")]
|
||||
[InlineData("/siple")]
|
||||
[InlineData("/simple1")]
|
||||
[InlineData("/simple/not-simple")]
|
||||
[InlineData("/simple/a/b/c")]
|
||||
public virtual async Task NotMatch_SingleLiteralSegment(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("simple")]
|
||||
[InlineData("/simple")]
|
||||
[InlineData("~/simple")]
|
||||
public virtual async Task Match_Sanitizies_TemplatePrefix(string template)
|
||||
public virtual async Task Match_Sanitizies_Template(string template)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
|
|
@ -44,6 +133,159 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers do their own 'splitting' of the path into segments, so including
|
||||
// some extra variation here
|
||||
[Theory]
|
||||
[InlineData("/a/b", "/a/b")]
|
||||
[InlineData("/a/b", "/A/B")]
|
||||
[InlineData("/a/b", "/a/b/")]
|
||||
[InlineData("/a/b/c", "/a/b/c")]
|
||||
[InlineData("/a/b/c", "/a/b/c/")]
|
||||
[InlineData("/a/b/c/d", "/a/b/c/d")]
|
||||
[InlineData("/a/b/c/d", "/a/b/c/d/")]
|
||||
public virtual async Task Match_MultipleLiteralSegments(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers do their own 'splitting' of the path into segments, so including
|
||||
// some extra variation here
|
||||
[Theory]
|
||||
[InlineData("/a/b", "/")]
|
||||
[InlineData("/a/b", "/a")]
|
||||
[InlineData("/a/b", "/a/")]
|
||||
[InlineData("/a/b", "/a//")]
|
||||
[InlineData("/a/b", "/aa/")]
|
||||
[InlineData("/a/b", "/a/bb")]
|
||||
[InlineData("/a/b", "/a/bb/")]
|
||||
[InlineData("/a/b/c", "/aa/b/c")]
|
||||
[InlineData("/a/b/c", "/a/bb/c/")]
|
||||
[InlineData("/a/b/c", "/a/b/cab")]
|
||||
[InlineData("/a/b/c", "/d/b/c/")]
|
||||
[InlineData("/a/b/c", "//b/c")]
|
||||
[InlineData("/a/b/c", "/a/b//")]
|
||||
[InlineData("/a/b/c", "/a/b/c/d")]
|
||||
[InlineData("/a/b/c", "/a/b/c/d/e")]
|
||||
public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext("/14");
|
||||
var values = new RouteValueDictionary(new { p = "14", });
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter_TrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext("/14/");
|
||||
var values = new RouteValueDictionary(new { p = "14", });
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/a/b")]
|
||||
[InlineData("/a/b/c")]
|
||||
[InlineData("//")]
|
||||
public virtual async Task NotMatch_SingleParameter(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/subscriptions/{subscriptionId}/providers/Microsoft.Insights/metricAlerts", "/subscriptions/foo/providers/Microsoft.Insights/metricAlerts", new string[] { "subscriptionId", }, new string[] { "foo", })]
|
||||
[InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] {"54", })]
|
||||
[InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
|
||||
[InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
|
||||
[InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
|
||||
public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a}/b", "/54/bb")]
|
||||
[InlineData("/{a}/b", "/54/b/17")]
|
||||
[InlineData("/{a}/b", "/54/b//")]
|
||||
[InlineData("/{a}/{b}", "//73")]
|
||||
[InlineData("/{a}/{b}", "/54//")]
|
||||
[InlineData("/{a}/{b}", "/54/73/18")]
|
||||
[InlineData("/a/{b}/c", "/aa/b/c")]
|
||||
[InlineData("/a/{b}/c", "/a/b/cc")]
|
||||
[InlineData("/a/{b}/c", "/a/b/c/d")]
|
||||
[InlineData("/{a}/b/{c}", "/54/bb/c")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c//")]
|
||||
[InlineData("/{a}/{b}/{c}", "//b/c/")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54//c/")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b//")]
|
||||
public virtual async Task NotMatch_MultipleParameters(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
internal static (HttpContext httpContext, IEndpointFeature feature) CreateContext(string path)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
// This is an adapter to use Route in the conformance tests
|
||||
internal class RouteMatcher : Matcher
|
||||
{
|
||||
private readonly RouteCollection _inner;
|
||||
|
||||
internal RouteMatcher(RouteCollection inner)
|
||||
{
|
||||
_inner = inner;
|
||||
}
|
||||
|
||||
public async override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var context = new RouteContext(httpContext);
|
||||
await _inner.RouteAsync(context);
|
||||
|
||||
if (context.Handler != null)
|
||||
{
|
||||
feature.Values = context.RouteData.Values;
|
||||
await context.Handler(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class RouteMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private readonly RouteCollection _routes = new RouteCollection();
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
|
||||
public RouteMatcherBuilder()
|
||||
{
|
||||
_constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()));
|
||||
}
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
var handler = new RouteHandler(c =>
|
||||
{
|
||||
c.Features.Get<IEndpointFeature>().Endpoint = endpoint;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_routes.Add(new Route(handler, endpoint.Template, _constraintResolver));
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new RouteMatcher(_routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class RouteMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
{
|
||||
var builder = new RouteMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
|
||||
if (context.Handler != null)
|
||||
{
|
||||
httpContext.Features.Get<IEndpointFeature>().Values = context.RouteData.Values;
|
||||
feature.Values = context.RouteData.Values;
|
||||
await context.Handler(httpContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class TreeRouterMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private readonly TreeRouteBuilder _inner;
|
||||
|
||||
public TreeRouterMatcherBuilder()
|
||||
{
|
||||
_inner = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
}
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
{
|
||||
var handler = new RouteHandler(c =>
|
||||
{
|
||||
var feature = c.Features.Get<IEndpointFeature>();
|
||||
feature.Endpoint = endpoint;
|
||||
feature.Invoker = MatcherEndpoint.EmptyInvoker;
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
_inner.MapInbound(handler, TemplateParser.Parse(endpoint.Template), "default", 0);
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new TreeRouterMatcher(_inner.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class TreeRouterMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
{
|
||||
var builder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
|
||||
var handler = new RouteHandler(c =>
|
||||
{
|
||||
var feature = c.Features.Get<IEndpointFeature>();
|
||||
feature.Endpoint = endpoint;
|
||||
feature.Invoker = MatcherEndpoint.EmptyInvoker;
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
builder.MapInbound(handler, TemplateParser.Parse(endpoint.Template), "default", 0);
|
||||
|
||||
return new TreeRouterMatcher(builder.Build());
|
||||
var builder = new TreeRouterMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@
|
|||
<RootNamespace>Microsoft.AspNetCore.Routing</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
Loading…
Reference in New Issue