public void Create_CreatesCandidateSet() { // Arrange var count = 10; var endpoints = new RouteEndpoint[count]; for (var i = 0; i < endpoints.Length; i++) { endpoints[i] = CreateEndpoint($"/{i}"); } var builder = CreateDfaMatcherBuilder(); var candidates = builder.CreateCandidates(endpoints); // Act var candidateSet = new CandidateSet(candidates); // Assert for (var i = 0; i < candidateSet.Count; i++) { ref var state = ref candidateSet[i]; Assert.True(candidateSet.IsValidCandidate(i)); Assert.Same(endpoints[i], state.Endpoint); Assert.Equal(candidates[i].Score, state.Score); Assert.Null(state.Values); candidateSet.SetValidity(i, false); Assert.False(candidateSet.IsValidCandidate(i)); }
internal static void Select( HttpContext httpContext, EndpointSelectorContext context, CandidateState[] candidateState) { // Fast path: We can specialize for trivial numbers of candidates since there can // be no ambiguities switch (candidateState.Length) { case 0: { // Do nothing break; } case 1: { ref var state = ref candidateState[0]; if (CandidateSet.IsValidCandidate(ref state)) { context.Endpoint = state.Endpoint; context.RouteValues = state.Values; } break; }
public sealed override Task MatchAsync(HttpContext httpContext, EndpointFeature feature) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (feature == null) { throw new ArgumentNullException(nameof(feature)); } // The sequence of actions we take is optimized to avoid doing expensive work // like creating substrings, creating route value dictionaries, and calling // into policies like versioning. var path = httpContext.Request.Path.Value; // First tokenize the path into series of segments. Span <PathSegment> buffer = stackalloc PathSegment[_maxSegmentCount]; var count = FastPathTokenizer.Tokenize(path, buffer); var segments = buffer.Slice(0, count); // FindCandidateSet will process the DFA and return a candidate set. This does // some preliminary matching of the URL (mostly the literal segments). var candidates = FindCandidateSet(httpContext, path, segments); if (candidates.Length == 0) { return(Task.CompletedTask); } // At this point we have a candidate set, defined as a list of endpoints in // priority order. // // We don't yet know that any candidate can be considered a match, because // we haven't processed things like route constraints and complex segments. // // Now we'll iterate each endpoint to capture route values, process constraints, // and process complex segments. // `candidates` has all of our internal state that we use to process the // set of endpoints before we call the EndpointSelector. // // `candidateSet` is the mutable state that we pass to the EndpointSelector. var candidateSet = new CandidateSet(candidates); for (var i = 0; i < candidates.Length; i++) { // PERF: using ref here to avoid copying around big structs. // // Reminder! // candidate: readonly data about the endpoint and how to match // state: mutable storarge for our processing ref var candidate = ref candidates[i]; ref var state = ref candidateSet[i];
public override Task SelectAsync( HttpContext httpContext, IEndpointFeature feature, CandidateSet candidateSet) { for (var i = 0; i < _selectorPolicies.Length; i++) { _selectorPolicies[i].Apply(httpContext, candidateSet); } MatcherEndpoint endpoint = null; RouteValueDictionary values = null; int?foundScore = null; for (var i = 0; i < candidateSet.Count; i++) { ref var state = ref candidateSet[i]; var isValid = state.IsValidCandidate; if (isValid && foundScore == null) { // This is the first match we've seen - speculatively assign it. endpoint = state.Endpoint; values = state.Values; foundScore = state.Score; } else if (isValid && foundScore < state.Score) { // This candidate is lower priority than the one we've seen // so far, we can stop. // // Don't worry about the 'null < state.Score' case, it returns false. break; } else if (isValid && foundScore == state.Score) { // This is the second match we've found of the same score, so there // must be an ambiguity. // // Don't worry about the 'null == state.Score' case, it returns false. ReportAmbiguity(candidateSet); // Unreachable, ReportAmbiguity always throws. throw new NotSupportedException(); } }
public override Task SelectAsync( HttpContext httpContext, CandidateSet candidateSet) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (candidateSet == null) { throw new ArgumentNullException(nameof(candidateSet)); } Select(httpContext, candidateSet.Candidates); return(Task.CompletedTask); }
public override async Task SelectAsync( HttpContext httpContext, EndpointFeature feature, CandidateSet candidateSet) { var selectorPolicies = _selectorPolicies; for (var i = 0; i < _selectorPolicies.Length; i++) { await selectorPolicies[i].ApplyAsync(httpContext, feature, candidateSet); if (feature.Endpoint != null) { // This is a short circuit, the selector chose an endpoint. return; } } ProcessFinalCandidates(httpContext, feature, candidateSet); }
public override Task SelectAsync( HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } if (candidateSet == null) { throw new ArgumentNullException(nameof(candidateSet)); } // Fast path: We can specialize for trivial numbers of candidates since there can // be no ambiguities switch (candidateSet.Count) { case 0: { // Do nothing break; } case 1: { if (candidateSet.IsValidCandidate(0)) { ref var state = ref candidateSet[0]; context.Endpoint = state.Endpoint; context.RouteValues = state.Values; } break; }
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { throw new NotImplementedException(); }
/// <summary> /// For framework use only. /// </summary> /// <param name="httpContext"></param> /// <param name="candidates"></param> /// <returns></returns> public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (candidates == null) { throw new ArgumentNullException(nameof(candidates)); } // Returning a 405 here requires us to return keep track of all 'seen' HTTP methods. We allocate to // keep track of this beause we either need to keep track of the HTTP methods or keep track of the // endpoints - both allocate. // // Those code only runs in the presence of dynamic endpoints anyway. // // We want to return a 405 iff we eliminated ALL of the currently valid endpoints due to HTTP method // mismatch. bool? needs405Endpoint = null; HashSet <string> methods = null; for (var i = 0; i < candidates.Count; i++) { // We do this check first for consistency with how 405 is implemented for the graph version // of this code. We still want to know if any endpoints in this set require an HTTP method // even if those endpoints are already invalid. var metadata = candidates[i].Endpoint.Metadata.GetMetadata <IHttpMethodMetadata>(); if (metadata == null || metadata.HttpMethods.Count == 0) { // Can match any method. needs405Endpoint = false; continue; } // Saw a valid endpoint. needs405Endpoint = needs405Endpoint ?? true; if (!candidates.IsValidCandidate(i)) { continue; } var httpMethod = httpContext.Request.Method; var headers = httpContext.Request.Headers; if (metadata.AcceptCorsPreflight && string.Equals(httpMethod, PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) && headers.ContainsKey(HeaderNames.Origin) && headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out var accessControlRequestMethod) && !StringValues.IsNullOrEmpty(accessControlRequestMethod)) { needs405Endpoint = false; // We don't return a 405 for a CORS preflight request when the endpoints accept CORS preflight. httpMethod = accessControlRequestMethod; } var matched = false; for (var j = 0; j < metadata.HttpMethods.Count; j++) { var candidateMethod = metadata.HttpMethods[j]; if (!string.Equals(httpMethod, candidateMethod, StringComparison.OrdinalIgnoreCase)) { methods = methods ?? new HashSet <string>(StringComparer.OrdinalIgnoreCase); methods.Add(candidateMethod); continue; } matched = true; needs405Endpoint = false; break; } if (!matched) { candidates.SetValidity(i, false); } } if (needs405Endpoint == true) { // We saw some endpoints coming in, and we eliminated them all. httpContext.SetEndpoint(CreateRejectionEndpoint(methods.OrderBy(m => m, StringComparer.OrdinalIgnoreCase))); httpContext.Request.RouteValues = null; } return(Task.CompletedTask); }
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (candidates == null) { throw new ArgumentNullException(nameof(candidates)); } for (var i = 0; i < candidates.Count; i++) { if (!candidates.IsValidCandidate(i)) { continue; } var hosts = candidates[i].Endpoint.Metadata.GetMetadata <IngressHostMetadata>()?.Hosts; if (hosts == null || hosts.Count == 0) { // Can match any host. continue; } var matched = false; var(requestHost, requestPort) = GetHostAndPort(httpContext); for (var j = 0; j < hosts.Count; j++) { var host = hosts[j].AsSpan(); var port = ReadOnlySpan <char> .Empty; // Split into host and port var pivot = host.IndexOf(':'); if (pivot >= 0) { port = host.Slice(pivot + 1); host = host.Slice(0, pivot); } if (host == null || host.Equals(WildcardHost, StringComparison.OrdinalIgnoreCase)) { // Can match any host } else if ( host.StartsWith(WildcardPrefix) && // Note that we only slice of the `*`. We want to match the leading `.` also. MemoryExtensions.EndsWith(requestHost, host.Slice(WildcardHost.Length), StringComparison.OrdinalIgnoreCase)) { // Matches a suffix wildcard. } else if (MemoryExtensions.Equals(requestHost, host, StringComparison.OrdinalIgnoreCase)) { // Matches exactly } else { // If we get here then the host doesn't match. continue; } if (port.Equals(WildcardHost, StringComparison.OrdinalIgnoreCase)) { // Port is a wildcard, we allow any port. } else if (port.Length > 0 && (!int.TryParse(port, out var parsed) || parsed != requestPort)) { // If we get here then the port doesn't match. continue; } matched = true; break; } if (!matched) { candidates.SetValidity(i, false); } } return(Task.CompletedTask); }
/// <summary> /// Asynchronously selects an <see cref="Endpoint"/> from the <see cref="CandidateSet"/>. /// </summary> /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param> /// <param name="context">The <see cref="EndpointSelectorContext"/> associated with the current request.</param> /// <param name="candidates">The <see cref="CandidateSet"/>.</param> /// <returns>A <see cref="Task"/> that completes asynchronously once endpoint selection is complete.</returns> /// <remarks> /// An <see cref="EndpointSelector"/> should assign the <see cref="EndpointSelectorContext.Endpoint"/> /// and <see cref="EndpointSelectorContext.RouteValues"/> properties once an endpoint is selected. /// </remarks> public abstract Task SelectAsync( HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates);
/// <summary> /// Asynchronously selects an <see cref="Endpoint"/> from the <see cref="CandidateSet"/>. /// </summary> /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param> /// <param name="feature">The <see cref="IEndpointFeature"/> associated with the current request.</param> /// <param name="candidates">The <see cref="CandidateSet"/>.</param> /// <returns>A <see cref="Task"/> that completes asynchronously once endpoint selection is complete.</returns> /// <remarks> /// An <see cref="EndpointSelector"/> should assign the <see cref="EndpointFeature.Endpoint"/> /// and <see cref="EndpointFeature.RouteValues"/> properties once an endpoint is selected. /// </remarks> public abstract Task SelectAsync( HttpContext httpContext, EndpointFeature feature, CandidateSet candidates);
/// <summary> /// Asynchronously selects an <see cref="Endpoint"/> from the <see cref="CandidateSet"/>. /// </summary> /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param> /// <param name="candidates">The <see cref="CandidateSet"/>.</param> /// <returns>A <see cref="Task"/> that completes asynchronously once endpoint selection is complete.</returns> /// <remarks> /// An <see cref="EndpointSelector"/> should assign the endpoint by calling /// <see cref="EndpointHttpContextExtensions.SetEndpoint(HttpContext, Endpoint)"/> /// and setting <see cref="HttpRequest.RouteValues"/> once an endpoint is selected. /// </remarks> public abstract Task SelectAsync(HttpContext httpContext, CandidateSet candidates);