Example #1
0
        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));
            }
Example #2
0
        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;
            }
Example #3
0
        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);
        }
Example #6
0
        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);
        }
Example #7
0
        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;
            }
Example #8
0
 public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
 {
     throw new NotImplementedException();
 }
Example #9
0
        /// <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);
        }
Example #11
0
 /// <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);
Example #12
0
 /// <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);