131 lines
4.5 KiB
C#
131 lines
4.5 KiB
C#
// 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;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Routing.Matchers;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Microsoft.AspNetCore.Routing
|
|
{
|
|
internal sealed class GlobalRoutingMiddleware
|
|
{
|
|
private readonly MatcherFactory _matcherFactory;
|
|
private readonly ILogger _logger;
|
|
private readonly CompositeEndpointDataSource _endpointDataSource;
|
|
private readonly RequestDelegate _next;
|
|
|
|
private Task<Matcher> _initializationTask;
|
|
|
|
public GlobalRoutingMiddleware(
|
|
MatcherFactory matcherFactory,
|
|
CompositeEndpointDataSource endpointDataSource,
|
|
ILogger<GlobalRoutingMiddleware> logger,
|
|
RequestDelegate next)
|
|
{
|
|
if (matcherFactory == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(matcherFactory));
|
|
}
|
|
|
|
if (endpointDataSource == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(endpointDataSource));
|
|
}
|
|
|
|
if (logger == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
if (next == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(next));
|
|
}
|
|
|
|
_matcherFactory = matcherFactory;
|
|
_endpointDataSource = endpointDataSource;
|
|
_logger = logger;
|
|
_next = next;
|
|
}
|
|
|
|
public async Task Invoke(HttpContext httpContext)
|
|
{
|
|
var feature = new EndpointFeature();
|
|
httpContext.Features.Set<IEndpointFeature>(feature);
|
|
|
|
// Back compat support for users of IRoutingFeature
|
|
httpContext.Features.Set<IRoutingFeature>(feature);
|
|
|
|
// There's an inherent race condition between waiting for init and accessing the matcher
|
|
// this is OK because once `_matcher` is initialized, it will not be set to null again.
|
|
var matcher = await InitializeAsync();
|
|
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
if (feature.Endpoint != null)
|
|
{
|
|
Log.MatchSuccess(_logger, feature);
|
|
}
|
|
else
|
|
{
|
|
Log.MatchFailure(_logger);
|
|
}
|
|
|
|
await _next(httpContext);
|
|
}
|
|
|
|
// Initialization is async to avoid blocking threads while reflection and things
|
|
// of that nature take place.
|
|
//
|
|
// We've seen cases where startup is very slow if we allow multiple threads to race
|
|
// while initializing the set of endpoints/routes. Doing CPU intensive work is a
|
|
// blocking operation if you have a low core count and enough work to do.
|
|
private Task<Matcher> InitializeAsync()
|
|
{
|
|
if (_initializationTask != null)
|
|
{
|
|
return _initializationTask;
|
|
}
|
|
|
|
var initializationTask = new TaskCompletionSource<Matcher>();
|
|
if (Interlocked.CompareExchange<Task<Matcher>>(
|
|
ref _initializationTask,
|
|
initializationTask.Task,
|
|
null) == null)
|
|
{
|
|
// This thread won the race, do the initialization.
|
|
var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
|
|
initializationTask.SetResult(matcher);
|
|
}
|
|
|
|
return _initializationTask;
|
|
}
|
|
|
|
private static class Log
|
|
{
|
|
private static readonly Action<ILogger, string, Exception> _matchSuccess = LoggerMessage.Define<string>(
|
|
LogLevel.Debug,
|
|
new EventId(1, "MatchSuccess"),
|
|
"Request matched endpoint '{EndpointName}'.");
|
|
|
|
private static readonly Action<ILogger, Exception> _matchFailure = LoggerMessage.Define(
|
|
LogLevel.Debug,
|
|
new EventId(2, "MatchFailure"),
|
|
"Request did not match any endpoints.");
|
|
|
|
public static void MatchSuccess(ILogger logger, EndpointFeature feature)
|
|
{
|
|
_matchSuccess(logger, feature.Endpoint.DisplayName, null);
|
|
}
|
|
|
|
public static void MatchFailure(ILogger logger)
|
|
{
|
|
_matchFailure(logger, null);
|
|
}
|
|
}
|
|
}
|
|
}
|