public Enumerator(PathTokenizer tokenizer) { _path = tokenizer._path; _index = -1; _length = -1; }
/// <inheritdoc /> public async Task RouteAsync(RouteContext context) { foreach (var tree in _trees) { var tokenizer = new PathTokenizer(context.HttpContext.Request.Path); var root = tree.Root; var treeEnumerator = new TreeEnumerator(root, tokenizer); // Create a snapshot before processing the route. We'll restore this snapshot before running each // to restore the state. This is likely an "empty" snapshot, which doesn't allocate. var snapshot = context.RouteData.PushState(router: null, values: null, dataTokens: null); while (treeEnumerator.MoveNext()) { var node = treeEnumerator.Current; foreach (var item in node.Matches) { var entry = item.Entry; var matcher = item.TemplateMatcher; if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values)) { continue; } try { if (!RouteConstraintMatcher.Match( entry.Constraints, context.RouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { continue; } _logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText); context.RouteData.Routers.Add(entry.Handler); await entry.Handler.RouteAsync(context); if (context.Handler != null) { return; } } finally { if (context.Handler == null) { // Restore the original values to prevent polluting the route data. snapshot.Restore(); } } } } } }
public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer) { _stack = new Stack <UrlMatchingNode>(); _tokenizer = tokenizer; Current = null; _stack.Push(root); }
public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act & Assert Assert.Equal <StringSegment>(expectedSegments, tokenizer); }
/// <inheritdoc /> public async Task RouteAsync(RouteContext context) { foreach (var tree in _trees) { var tokenizer = new PathTokenizer(context.HttpContext.Request.Path); var enumerator = tokenizer.GetEnumerator(); var root = tree.Root; var treeEnumerator = new TreeEnumerator(root, tokenizer); while (treeEnumerator.MoveNext()) { var node = treeEnumerator.Current; foreach (var item in node.Matches) { var values = item.TemplateMatcher.Match(context.HttpContext.Request.Path); if (values == null) { continue; } var match = new TemplateMatch(item, values); var snapshot = context.RouteData.PushState(match.Entry.Target, match.Values, dataTokens: null); try { if (!RouteConstraintMatcher.Match( match.Entry.Constraints, context.RouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { continue; } _logger.MatchedRoute(match.Entry.RouteName, match.Entry.RouteTemplate.TemplateText); await match.Entry.Target.RouteAsync(context); if (context.Handler != null) { return; } } finally { if (context.Handler == null) { // Restore the original values to prevent polluting the route data. snapshot.Restore(); } } } } } }
public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act & Assert for (var i = 0; i < expectedSegments.Length; i++) { Assert.Equal(expectedSegments[i], tokenizer[i]); } }
public void PathTokenizer_Count(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act var count = tokenizer.Count; // Assert Assert.Equal(expectedSegments.Length, count); }
public void PathTokenizer_Count(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act var count = tokenizer.Count; // Assert Assert.Equal(expectedSegments.Length, count); }
public override async Task MatchAsync(MatcherContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } EnsureTreeMatcherServicesInitialized(context); var cache = LazyInitializer.EnsureInitialized(ref _cache, ref _dataInitialized, ref _lock, _initializer); var values = new DispatcherValueCollection(); context.Values = values; for (var i = 0; i < cache.Trees.Length; i++) { var tree = cache.Trees[i]; var tokenizer = new PathTokenizer(context.HttpContext.Request.Path); var treenumerator = new TreeEnumerator(tree.Root, tokenizer); while (treenumerator.MoveNext()) { var node = treenumerator.Current; foreach (var item in node.Matches) { var entry = item.Entry; var matcher = item.RoutePatternMatcher; values.Clear(); if (!matcher.TryMatch(context.HttpContext.Request.Path, values)) { continue; } Logger.MatchedRoute(entry.RoutePattern.RawText); if (!MatchConstraints(context.HttpContext, values, entry.Constraints)) { continue; } await SelectEndpointAsync(context, (entry.Endpoints)); if (context.ShortCircuit != null) { Logger.RequestShortCircuited(context); return; } if (context.Endpoint != null) { if (context.Endpoint is IRoutePatternEndpoint templateEndpoint) { foreach (var kvp in templateEndpoint.Values) { if (!context.Values.ContainsKey(kvp.Key)) { context.Values[kvp.Key] = kvp.Value; } } } return; } } } } }
public ExpressionToken(PathTokenizer value, CharacterLocation location) { TokenType = ExpressionTokenType.Path; Value = value; Location = location; }
public bool TryMatch(PathString path, DispatcherValueCollection values) { if (values == null) { throw new ArgumentNullException(nameof(values)); } var i = 0; var pathTokenizer = new PathTokenizer(path); // Perf: We do a traversal of the request-segments + route-segments twice. // // For most segment-types, we only really need to any work on one of the two passes. // // On the first pass, we're just looking to see if there's anything that would disqualify us from matching. // The most common case would be a literal segment that doesn't match. // // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values' // and start capturing strings. foreach (var stringSegment in pathTokenizer) { if (stringSegment.Length == 0) { return(false); } var pathSegment = i >= RoutePattern.PathSegments.Count ? null : RoutePattern.PathSegments[i]; if (pathSegment == null && stringSegment.Length > 0) { // If pathSegment is null, then we're out of route segments. All we can match is the empty // string. return(false); } else if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameter parameter && parameter.IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } if (!TryMatchLiterals(i++, stringSegment, pathSegment)) { return(false); } } for (; i < RoutePattern.PathSegments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. These need // to be all single-part parameter segments with default values or else they won't match. var pathSegment = RoutePattern.PathSegments[i]; Debug.Assert(pathSegment != null); if (!pathSegment.IsSimple) { // If the segment is a complex segment, it MUST contain literals, and we've parsed the full // path so far, so it can't match. return(false); } var part = pathSegment.Parts[0]; if (part.IsLiteral || part.IsSeparator) { // If the segment is a simple literal - which need the URL to provide a value, so we don't match. return(false); } var parameter = (RoutePatternParameter)part; if (parameter.IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the // defaults to have a value. if (!_hasDefaultValue[i] && !parameter.IsOptional) { // There's no default for this (non-optional) parameter so it can't match. return(false); } } // At this point we've very likely got a match, so start capturing values for real. i = 0; foreach (var requestSegment in pathTokenizer) { var pathSegment = RoutePattern.PathSegments[i++]; if (SavePathSegmentsAsValues(i, values, requestSegment, pathSegment)) { break; } if (!pathSegment.IsSimple) { if (!MatchComplexSegment(pathSegment, requestSegment.ToString(), Defaults, values)) { return(false); } } } for (; i < RoutePattern.PathSegments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. We already know these // are simple parameters that either have a default, or don't need to produce a value. var pathSegment = RoutePattern.PathSegments[i]; Debug.Assert(pathSegment != null); Debug.Assert(pathSegment.IsSimple); var part = pathSegment.Parts[0]; Debug.Assert(part.IsParameter); // It's ok for a catch-all to produce a null value if (part is RoutePatternParameter parameter && (parameter.IsCatchAll || _hasDefaultValue[i])) { // Don't replace an existing value with a null. var defaultValue = _defaultValues[i]; if (defaultValue != null || !values.ContainsKey(parameter.Name)) { values[parameter.Name] = defaultValue; } } } // Copy all remaining default values to the route data foreach (var kvp in Defaults) { if (!values.ContainsKey(kvp.Key)) { values.Add(kvp.Key, kvp.Value); } } return(true); }
public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act & Assert for (var i = 0; i < expectedSegments.Length; i++) { Assert.Equal(expectedSegments[i], tokenizer[i]); } }
public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act & Assert Assert.Equal<StringSegment>(expectedSegments, tokenizer); }
public RouteValueDictionary Match(PathString path) { var i = 0; var pathTokenizer = new PathTokenizer(path); // Perf: We do a traversal of the request-segments + route-segments twice. // // For most segment-types, we only really need to any work on one of the two passes. // // On the first pass, we're just looking to see if there's anything that would disqualify us from matching. // The most common case would be a literal segment that doesn't match. // // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values' // and start capturing strings. foreach (var requestSegment in pathTokenizer) { var routeSegment = Template.GetSegment(i++); if (routeSegment == null && requestSegment.Length > 0) { // If pathSegment is null, then we're out of route segments. All we can match is the empty // string. return null; } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsLiteral) { // This is a literal segment, so we need to match the text, or the route isn't a match. var part = routeSegment.Parts[0]; if (!requestSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase)) { return null; } } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter) { // For a parameter, validate that it's a has some length, or we have a default, or it's optional. var part = routeSegment.Parts[0]; if (requestSegment.Length == 0 && !_hasDefaultValue[i] && !part.IsOptional) { // There's no value for this parameter, the route can't match. return null; } } else { Debug.Assert(!routeSegment.IsSimple); // Don't attempt to validate a complex segment at this point other than being non-emtpy, // do it in the second pass. } } for (; i < Template.Segments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. These need // to be all single-part parameter segments with default values or else they won't match. var routeSegment = Template.GetSegment(i); Debug.Assert(routeSegment != null); if (!routeSegment.IsSimple) { // If the segment is a complex segment, it MUST contain literals, and we've parsed the full // path so far, so it can't match. return null; } var part = routeSegment.Parts[0]; if (part.IsLiteral) { // If the segment is a simple literal - which need the URL to provide a value, so we don't match. return null; } if (part.IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the // defaults to have a value. Debug.Assert(routeSegment.IsSimple && part.IsParameter); if (!_hasDefaultValue[i] && !part.IsOptional) { // There's no default for this (non-optional) parameter so it can't match. return null; } } // At this point we've very likely got a match, so start capturing values for real. var values = new RouteValueDictionary(); i = 0; foreach (var requestSegment in pathTokenizer) { var routeSegment = Template.GetSegment(i++); if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll) { // A catch-all captures til the end of the string. var part = routeSegment.Parts[0]; var captured = requestSegment.Buffer.Substring(requestSegment.Offset); if (captured.Length > 0) { values.Add(part.Name, captured); } else { // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue. values.Add(part.Name, _defaultValues[i]); } // A catch-all has to be the last part, so we're done. break; } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter) { // A simple parameter captures the whole segment, or a default value if nothing was // provided. var part = routeSegment.Parts[0]; if (requestSegment.Length > 0) { values.Add(part.Name, requestSegment.ToString()); } else { if (_hasDefaultValue[i]) { values.Add(part.Name, _defaultValues[i]); } } } else if (!routeSegment.IsSimple) { if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values)) { return null; } } } for (; i < Template.Segments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. We already know these // are simple parameters that either have a default, or don't need to produce a value. var routeSegment = Template.GetSegment(i); Debug.Assert(routeSegment != null); Debug.Assert(routeSegment.IsSimple); var part = routeSegment.Parts[0]; Debug.Assert(part.IsParameter); // It's ok for a catch-all to produce a null value if (_hasDefaultValue[i] || part.IsCatchAll) { values.Add(part.Name, _defaultValues[i]); } } // Copy all remaining default values to the route data foreach (var kvp in Defaults) { if (!values.ContainsKey(kvp.Key)) { values.Add(kvp.Key, kvp.Value); } } return values; }
public bool TryMatch(PathString path, RouteValueDictionary values) { if (values == null) { throw new ArgumentNullException(nameof(values)); } var i = 0; var pathTokenizer = new PathTokenizer(path); // Perf: We do a traversal of the request-segments + route-segments twice. // // For most segment-types, we only really need to any work on one of the two passes. // // On the first pass, we're just looking to see if there's anything that would disqualify us from matching. // The most common case would be a literal segment that doesn't match. // // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values' // and start capturing strings. foreach (var pathSegment in pathTokenizer) { if (pathSegment.Length == 0) { return(false); } var routeSegment = Template.GetSegment(i++); if (routeSegment == null && pathSegment.Length > 0) { // If routeSegment is null, then we're out of route segments. All we can match is the empty // string. return(false); } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsLiteral) { // This is a literal segment, so we need to match the text, or the route isn't a match. var part = routeSegment.Parts[0]; if (!pathSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase)) { return(false); } } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter) { // For a parameter, validate that it's a has some length, or we have a default, or it's optional. var part = routeSegment.Parts[0]; if (pathSegment.Length == 0 && !_hasDefaultValue[i] && !part.IsOptional) { // There's no value for this parameter, the route can't match. return(false); } } else { Debug.Assert(!routeSegment.IsSimple); // Don't attempt to validate a complex segment at this point other than being non-emtpy, // do it in the second pass. } } for (; i < Template.Segments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. These need // to be all single-part parameter segments with default values or else they won't match. var routeSegment = Template.GetSegment(i); Debug.Assert(routeSegment != null); if (!routeSegment.IsSimple) { // If the segment is a complex segment, it MUST contain literals, and we've parsed the full // path so far, so it can't match. return(false); } var part = routeSegment.Parts[0]; if (part.IsLiteral) { // If the segment is a simple literal - which need the URL to provide a value, so we don't match. return(false); } if (part.IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the // defaults to have a value. Debug.Assert(routeSegment.IsSimple && part.IsParameter); if (!_hasDefaultValue[i] && !part.IsOptional) { // There's no default for this (non-optional) parameter so it can't match. return(false); } } // At this point we've very likely got a match, so start capturing values for real. i = 0; foreach (var requestSegment in pathTokenizer) { var routeSegment = Template.GetSegment(i++); if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll) { // A catch-all captures til the end of the string. var part = routeSegment.Parts[0]; var captured = requestSegment.Buffer.Substring(requestSegment.Offset); if (captured.Length > 0) { values[part.Name] = captured; } else { // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue. values[part.Name] = _defaultValues[i]; } // A catch-all has to be the last part, so we're done. break; } else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter) { // A simple parameter captures the whole segment, or a default value if nothing was // provided. var part = routeSegment.Parts[0]; if (requestSegment.Length > 0) { values[part.Name] = requestSegment.ToString(); } else { if (_hasDefaultValue[i]) { values[part.Name] = _defaultValues[i]; } } } else if (!routeSegment.IsSimple) { if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values)) { return(false); } } } for (; i < Template.Segments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. We already know these // are simple parameters that either have a default, or don't need to produce a value. var routeSegment = Template.GetSegment(i); Debug.Assert(routeSegment != null); Debug.Assert(routeSegment.IsSimple); var part = routeSegment.Parts[0]; Debug.Assert(part.IsParameter); // It's ok for a catch-all to produce a null value if (_hasDefaultValue[i] || part.IsCatchAll) { // Don't replace an existing value with a null. var defaultValue = _defaultValues[i]; if (defaultValue != null || !values.ContainsKey(part.Name)) { values[part.Name] = defaultValue; } } } // Copy all remaining default values to the route data foreach (var kvp in Defaults) { if (!values.ContainsKey(kvp.Key)) { values.Add(kvp.Key, kvp.Value); } } return(true); }
public override async Task MatchAsync(HttpContext httpContext, IEndpointFeature feature) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (feature == null) { throw new ArgumentNullException(nameof(feature)); } var values = new RouteValueDictionary(); feature.Values = values; var cache = _cache.Value; for (var i = 0; i < cache.Length; i++) { var tree = cache[i]; var tokenizer = new PathTokenizer(httpContext.Request.Path); var treenumerator = new TreeEnumerator(tree.Root, tokenizer); while (treenumerator.MoveNext()) { var node = treenumerator.Current; foreach (var item in node.Matches) { var entry = item.Entry; var matcher = item.TemplateMatcher; values.Clear(); if (!matcher.TryMatch(httpContext.Request.Path, values)) { continue; } Log.MatchedTemplate(_logger, httpContext, entry.RouteTemplate); if (!MatchConstraints(httpContext, values, entry.Constraints)) { continue; } await SelectEndpointAsync(httpContext, feature, (MatcherEndpoint[])entry.Tag); if (feature.Endpoint != null) { if (feature.Endpoint is MatcherEndpoint endpoint) { foreach (var kvp in endpoint.Values) { if (!feature.Values.ContainsKey(kvp.Key)) { feature.Values[kvp.Key] = kvp.Value; } } } return; } } } } }