• 重新整理 .net core 实践篇——— endpoint[四十七]


    前言

    简单整理一些endpoint的一些东西,主要是介绍一个这个endpoint是什么。

    正文

    endpoint 从表面意思是端点的意思,也就是说比如客户端的某一个action 是一个点,那么服务端的action也是一个点,这个端点的意义更加具体,而不是服务端和客户端这么泛指。

    比如说客户端的action请求用户信心,那么服务端的action就是GetUserInfo,那么endpoint 在这里是什么意思呢?是GetUserInfo的抽象,或者是GetUserInfo的描述符。

    那么netcore 对endpoint的描述是什么呢?

    派生 Microsoft.AspNetCore.Routing.RouteEndpoint

    RouteEndpoint 是:

    那么来看一下:

    app.UseRouting();
    

    代码为:

    public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
    {
      if (builder == null)
    	throw new ArgumentNullException(nameof (builder));
      EndpointRoutingApplicationBuilderExtensions.VerifyRoutingServicesAreRegistered(builder);
      DefaultEndpointRouteBuilder endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);
      builder.Properties["__EndpointRouteBuilder"] = (object) endpointRouteBuilder;
      return builder.UseMiddleware<EndpointRoutingMiddleware>((object) endpointRouteBuilder);
    }
    

    VerifyRoutingServicesAreRegistered 主要验证路由标记服务是否注入了:

    private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
    {
      if (app.ApplicationServices.GetService(typeof (RoutingMarkerService)) == null)
    	throw new InvalidOperationException(Resources.FormatUnableToFindServices((object) "IServiceCollection", (object) "AddRouting", (object) "ConfigureServices(...)"));
    }
    

    然后可以查看一下DefaultEndpointRouteBuilder:

      internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
      {
        public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
        {
          IApplicationBuilder applicationBuilder1 = applicationBuilder;
          if (applicationBuilder1 == null)
            throw new ArgumentNullException(nameof (applicationBuilder));
          this.ApplicationBuilder = applicationBuilder1;
          this.DataSources = (ICollection<EndpointDataSource>) new List<EndpointDataSource>();
        }
    
        public IApplicationBuilder ApplicationBuilder { get; }
    
        public IApplicationBuilder CreateApplicationBuilder()
        {
          return this.ApplicationBuilder.New();
        }
    
        public ICollection<EndpointDataSource> DataSources { get; }
    
        public IServiceProvider ServiceProvider
        {
          get
          {
            return this.ApplicationBuilder.ApplicationServices;
          }
        }
      }
    

    我们知道builder 是用来构造某个东西的,从名字上来看是用来构造DefaultEndpointRoute。

    建设者,一般分为两种,一种是内部就有构建方法,另一种是构建方法在材料之中或者操作机器之中。

    这个怎么说呢?比如说一个构建者相当于一个工人,那么这个工人可能只带材料去完成一个小屋。也可能构建者本身没有带材料,那么可能材料之中包含了制作方法(比如方便面)或者机器中包含了制作方法比如榨汁机。

    然后来看一下中间件EndpointRoutingMiddleware:

    public EndpointRoutingMiddleware(
      MatcherFactory matcherFactory,
      ILogger<EndpointRoutingMiddleware> logger,
      IEndpointRouteBuilder endpointRouteBuilder,
      DiagnosticListener diagnosticListener,
      RequestDelegate next)
    {
      if (endpointRouteBuilder == null)
    	throw new ArgumentNullException(nameof (endpointRouteBuilder));
      MatcherFactory matcherFactory1 = matcherFactory;
      if (matcherFactory1 == null)
    	throw new ArgumentNullException(nameof (matcherFactory));
      this._matcherFactory = matcherFactory1;
      ILogger<EndpointRoutingMiddleware> logger1 = logger;
      if (logger1 == null)
    	throw new ArgumentNullException(nameof (logger));
      this._logger = (ILogger) logger1;
      DiagnosticListener diagnosticListener1 = diagnosticListener;
      if (diagnosticListener1 == null)
    	throw new ArgumentNullException(nameof (diagnosticListener));
      this._diagnosticListener = diagnosticListener1;
      RequestDelegate requestDelegate = next;
      if (requestDelegate == null)
    	throw new ArgumentNullException(nameof (next));
      this._next = requestDelegate;
      this._endpointDataSource = (EndpointDataSource) new CompositeEndpointDataSource((IEnumerable<EndpointDataSource>) endpointRouteBuilder.DataSources);
    }
    

    里面就做一些判断,是否服务注入了。然后值得关注的是几个新鲜事物了,比如MatcherFactory、DiagnosticListener、CompositeEndpointDataSource。

    把这些都看一下吧。

    internal abstract class MatcherFactory
    {
       public abstract Matcher CreateMatcher(EndpointDataSource dataSource);
    }
    

    CreateMatcher 通过某个端点资源,来创建一个Matcher。

    这里猜测,是这样子的,一般来说客户端把某个路由都比作某个资源,也就是uri,这里抽象成EndpointDataSource,那么这个匹配器的作用是:

    Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
    

    也就是匹配出Endpoint。

    那么再看一下DiagnosticListener,DiagnosticListener 源码就不看了,因为其实一个系统类,也就是system下面的类,比较复杂,直接看其描述就会。

    DiagnosticListener 是一个 NotificationSource,这意味着返回的结果可用于记录通知,但它也有 Subscribe 方法,因此可以任意转发通知。 因此,其工作是将生成的作业从制造者转发到所有侦听器 (多转换) 。 通常情况下,不应 DiagnosticListener 使用,而是使用默认设置,以便通知尽可能公共。
    

    是一个监听作用的,模式是订阅模式哈。https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.diagnosticlistener?view=net-6.0 有兴趣可以去看一下。

    最后这个看一下CompositeEndpointDataSource,表面意思是综合性EndpointDataSource,继承自EndpointDataSource。

    public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
    {
    	_dataSources = new List<EndpointDataSource>();
    
    	foreach (var dataSource in endpointDataSources)
    	{
    		_dataSources.Add(dataSource);
    	}
    }
    

    是用来管理endpointDataSources的,暂且不看其作用。

    public Task Invoke(HttpContext httpContext)
    {
      Endpoint endpoint = httpContext.GetEndpoint();
      if (endpoint != null)
      {
    	EndpointRoutingMiddleware.Log.MatchSkipped(this._logger, endpoint);
    	return this._next(httpContext);
      }
      Task<Matcher> matcherTask = this.InitializeAsync();
      if (!matcherTask.IsCompletedSuccessfully)
    	return AwaitMatcher(this, httpContext, matcherTask);
      Task matchTask = matcherTask.Result.MatchAsync(httpContext);
      return !matchTask.IsCompletedSuccessfully ? AwaitMatch(this, httpContext, matchTask) : this.SetRoutingAndContinue(httpContext);
    
      async Task AwaitMatcher(
    	EndpointRoutingMiddleware middleware,
    	HttpContext httpContext,
    	Task<Matcher> matcherTask)
      {
    	await (await matcherTask).MatchAsync(httpContext);
    	await middleware.SetRoutingAndContinue(httpContext);
      }
    
      async Task AwaitMatch(
    	EndpointRoutingMiddleware middleware,
    	HttpContext httpContext,
    	Task matchTask)
      {
    	await matchTask;
    	await middleware.SetRoutingAndContinue(httpContext);
      }
    }
    

    一段一段看:

    Endpoint endpoint = httpContext.GetEndpoint();
    if (endpoint != null)
    {
    EndpointRoutingMiddleware.Log.MatchSkipped(this._logger, endpoint);
    return this._next(httpContext);
    }
    

    如果有endpoint,就直接运行下一个中间件。

    Task<Matcher> matcherTask = this.InitializeAsync();
    

    看InitializeAsync。

    private Task<Matcher> InitializeAsync()
    {
    	var initializationTask = _initializationTask;
    	if (initializationTask != null)
    	{
    		return initializationTask;
    	}
    
    	return InitializeCoreAsync();
    }
    

    接着看:InitializeCoreAsync

    private Task<Matcher> InitializeCoreAsync()
    {
    	var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
    	var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
    	if (initializationTask != null)
    	{
    		// This thread lost the race, join the existing task.
    		return initializationTask;
    	}
    
    	// This thread won the race, do the initialization.
    	try
    	{
    		var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
    
    		// Now replace the initialization task with one created with the default execution context.
    		// This is important because capturing the execution context will leak memory in ASP.NET Core.
    		using (ExecutionContext.SuppressFlow())
    		{
    			_initializationTask = Task.FromResult(matcher);
    		}
    
    		// Complete the task, this will unblock any requests that came in while initializing.
    		initialization.SetResult(matcher);
    		return initialization.Task;
    	}
    	catch (Exception ex)
    	{
    		// Allow initialization to occur again. Since DataSources can change, it's possible
    		// for the developer to correct the data causing the failure.
    		_initializationTask = null;
    
    		// Complete the task, this will throw for any requests that came in while initializing.
    		initialization.SetException(ex);
    		return initialization.Task;
    	}
    }
    

    这里面作用就是创建matcher,值得注意的是这一句代码:var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);

    创建匹配器的资源对象是_endpointDataSource。

     this._endpointDataSource = (EndpointDataSource) new CompositeEndpointDataSource((IEnumerable<EndpointDataSource>) endpointRouteBuilder.DataSources);
    

    这个_endpointDataSource 并不是值某一个endpointDataSource,而是全部的endpointDataSource,这一点前面就有介绍CompositeEndpointDataSource。

    然后看最后一段:

    if (!matcherTask.IsCompletedSuccessfully)
    	return AwaitMatcher(this, httpContext, matcherTask);
      Task matchTask = matcherTask.Result.MatchAsync(httpContext);
      return !matchTask.IsCompletedSuccessfully ? AwaitMatch(this, httpContext, matchTask) : this.SetRoutingAndContinue(httpContext);
    
      async Task AwaitMatcher(
    	EndpointRoutingMiddleware middleware,
    	HttpContext httpContext,
    	Task<Matcher> matcherTask)
      {
    	await (await matcherTask).MatchAsync(httpContext);
    	await middleware.SetRoutingAndContinue(httpContext);
      }
    
      async Task AwaitMatch(
    	EndpointRoutingMiddleware middleware,
    	HttpContext httpContext,
    	Task matchTask)
      {
    	await matchTask;
    	await middleware.SetRoutingAndContinue(httpContext);
      }
    

    这里面其实就表达一个意思哈,如果task完成了,然后就直接运行下一步,如果没有完成就await,总之是要执行MatchAsync然后再执行SetRoutingAndContinue。

    internal abstract class Matcher
    {
    	/// <summary>
    	/// Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
    	/// </summary>
    	/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
    	/// <returns>A <see cref="Task"/> which represents the asynchronous completion of the operation.</returns>
    	public abstract Task MatchAsync(HttpContext httpContext);
    }
    

    MatchAsync 前面也提到过就是匹配出Endpoint的。

    然后SetRoutingAndContinue:

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private Task SetRoutingAndContinue(HttpContext httpContext)
    {
    	// If there was no mutation of the endpoint then log failure
    	var endpoint = httpContext.GetEndpoint();
    	if (endpoint == null)
    	{
    		Log.MatchFailure(_logger);
    	}
    	else
    	{
    		// Raise an event if the route matched
    		if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
    		{
    			// We're just going to send the HttpContext since it has all of the relevant information
    			_diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
    		}
    
    		Log.MatchSuccess(_logger, endpoint);
    	}
    
    	return _next(httpContext);
    }
    

    这里没有匹配到也没有终结哈,为什么这么做呢?因为匹配不到,下一个中间件可以做到如果没有endpoint,那么就指明做什么事情,不要一下子写死。

    如何如果匹配成功了,那么发送一个事件,这个事件是DiagnosticsEndpointMatchedKey,然后打印匹配成功的一些log了。

    private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
    

    那么这里我们可以监听做一些事情,看项目需求了。

    其实看到这里有两点疑问了Matcher的具体实现和EndpointDataSource 是怎么来的。

    先看一下Matcher吧,这个肯定要在路由服务中去查看了。

    public static IServiceCollection AddRouting(
    	this IServiceCollection services,
    	Action<RouteOptions> configureOptions)
    {
    	if (services == null)
    	{
    		throw new ArgumentNullException(nameof(services));
    	}
    
    	if (configureOptions == null)
    	{
    		throw new ArgumentNullException(nameof(configureOptions));
    	}
    
    	services.Configure(configureOptions);
    	services.AddRouting();
    
    	return services;
    }
    

    然后在AddRouting 中查看,这里面非常多,这里我直接放出这个的依赖注入:

    services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
    

    那么这里进入看DfaMatcherFactory。

    public override Matcher CreateMatcher(EndpointDataSource dataSource)
    {
    	if (dataSource == null)
    	{
    		throw new ArgumentNullException(nameof(dataSource));
    	}
    
    	// Creates a tracking entry in DI to stop listening for change events
    	// when the services are disposed.
    	var lifetime = _services.GetRequiredService<DataSourceDependentMatcher.Lifetime>();
    
    	return new DataSourceDependentMatcher(dataSource, lifetime, () =>
    	{
    		return _services.GetRequiredService<DfaMatcherBuilder>();
    	});
    }
    

    查看DataSourceDependentMatcher.Lifetime:

    public sealed class Lifetime : IDisposable
    {
    	private readonly object _lock = new object();
    	private DataSourceDependentCache<Matcher>? _cache;
    	private bool _disposed;
    
    	public DataSourceDependentCache<Matcher>? Cache
    	{
    		get => _cache;
    		set
    		{
    			lock (_lock)
    			{
    				if (_disposed)
    				{
    					value?.Dispose();
    				}
    
    				_cache = value;
    			}
    		}
    	}
    
    	public void Dispose()
    	{
    		lock (_lock)
    		{
    			_cache?.Dispose();
    			_cache = null;
    
    			_disposed = true;
    		}
    	}
    }
    

    Lifetime 是生命周期的意思,里面实现的比较简单,单纯从功能上看似乎只是缓存,但是其之所以取这个名字是因为Dispose,在Lifetime结束的时候带走了DataSourceDependentCache? _cache。

    为了让大家更清晰一写,DataSourceDependentCache,将其标红。

    然后CreateMatcher 创建了DataSourceDependentMatcher。

    return new DataSourceDependentMatcher(dataSource, lifetime, () =>
    {
    	return _services.GetRequiredService<DfaMatcherBuilder>();
    });
    

    看一下DataSourceDependentMatcher:

    public DataSourceDependentMatcher(
    	EndpointDataSource dataSource,
    	Lifetime lifetime,
    	Func<MatcherBuilder> matcherBuilderFactory)
    {
    	_matcherBuilderFactory = matcherBuilderFactory;
    
    	_cache = new DataSourceDependentCache<Matcher>(dataSource, CreateMatcher);
    	_cache.EnsureInitialized();
    
    	// This will Dispose the cache when the lifetime is disposed, this allows
    	// the service provider to manage the lifetime of the cache.
    	lifetime.Cache = _cache;
    }
    

    这里创建了一个DataSourceDependentCache。

    public DataSourceDependentCache(EndpointDataSource dataSource, Func<IReadOnlyList<Endpoint>, T> initialize)
    {
    	if (dataSource == null)
    	{
    		throw new ArgumentNullException(nameof(dataSource));
    	}
    
    	if (initialize == null)
    	{
    		throw new ArgumentNullException(nameof(initialize));
    	}
    
    	_dataSource = dataSource;
    	_initializeCore = initialize;
    
    	_initializer = Initialize;
    	_initializerWithState = (state) => Initialize();
    	_lock = new object();
    }
    
    // Note that we don't lock here, and think about that in the context of a 'push'. So when data gets 'pushed'
    // we start computing a new state, but we're still able to perform operations on the old state until we've
    // processed the update.
    public T Value => _value;
    
    public T EnsureInitialized()
    {
    	return LazyInitializer.EnsureInitialized<T>(ref _value, ref _initialized, ref _lock, _initializer);
    }
    
    private T Initialize()
    {
    	lock (_lock)
    	{
    		var changeToken = _dataSource.GetChangeToken();
    		_value = _initializeCore(_dataSource.Endpoints);
    
    		// Don't resubscribe if we're already disposed.
    		if (_disposed)
    		{
    			return _value;
    		}
    
    		_disposable = changeToken.RegisterChangeCallback(_initializerWithState, null);
    		return _value;
    	}
    }
    

    这里Initialize可以看到调用了_initializeCore,这个_initializeCore就是传递进来的DataSourceDependentMatcher的CreateMatcher,如下:

    private Matcher CreateMatcher(IReadOnlyList<Endpoint> endpoints)
    {
    	var builder = _matcherBuilderFactory();
    	for (var i = 0; i < endpoints.Count; i++)
    	{
    		// By design we only look at RouteEndpoint here. It's possible to
    		// register other endpoint types, which are non-routable, and it's
    		// ok that we won't route to them.
    		if (endpoints[i] is RouteEndpoint endpoint && endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching != true)
    		{
    			builder.AddEndpoint(endpoint);
    		}
    	}
    
    	return builder.Build();
    }
    

    这里可以看到这里endpoint 必须没有带ISuppressMatchingMetadata,否则将不会被匹配。

    这个_matcherBuilderFactory 是:

    也就是:

    看一下builder:

    public override Matcher Build()
    {
    #if DEBUG
    	var includeLabel = true;
    #else
    	var includeLabel = false;
    #endif
    
    	var root = BuildDfaTree(includeLabel);
    
    	// State count is the number of nodes plus an exit state
    	var stateCount = 1;
    	var maxSegmentCount = 0;
    	root.Visit((node) =>
    	{
    		stateCount++;
    		maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth);
    	});
    	_stateIndex = 0;
    
    	// The max segment count is the maximum path-node-depth +1. We need
    	// the +1 to capture any additional content after the 'last' segment.
    	maxSegmentCount++;
    
    	var states = new DfaState[stateCount];
    	var exitDestination = stateCount - 1;
    	AddNode(root, states, exitDestination);
    
    	// The root state only has a jump table.
    	states[exitDestination] = new DfaState(
    		Array.Empty<Candidate>(),
    		Array.Empty<IEndpointSelectorPolicy>(),
    		JumpTableBuilder.Build(exitDestination, exitDestination, null),
    		null);
    
    	return new DfaMatcher(_loggerFactory.CreateLogger<DfaMatcher>(), _selector, states, maxSegmentCount);
    }
    

    BuildDfaTree 这个是一个算法哈,这里就不介绍哈,是dfa 算法,这里理解为将endpoint创建为一颗数,有利于匹配就好。

    最后返回了一个return new DfaMatcher(_loggerFactory.CreateLogger(), _selector, states, maxSegmentCount);

    DfaMatcher 就是endpoint匹配器。

    那么进去看DfaMatcher 匹配方法MatchAsync。

    public sealed override Task MatchAsync(HttpContext httpContext)
    {
    	if (httpContext == null)
    	{
    		throw new ArgumentNullException(nameof(httpContext));
    	}
    
    	// All of the logging we do here is at level debug, so we can get away with doing a single check.
    	var log = _logger.IsEnabled(LogLevel.Debug);
    
    	// 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, policies) = FindCandidateSet(httpContext, path, segments);
    	var candidateCount = candidates.Length;
    	if (candidateCount == 0)
    	{
    		if (log)
    		{
    			Logger.CandidatesNotFound(_logger, path);
    		}
    
    		return Task.CompletedTask;
    	}
    
    	if (log)
    	{
    		Logger.CandidatesFound(_logger, path, candidates);
    	}
    
    	var policyCount = policies.Length;
    
    	// This is a fast path for single candidate, 0 policies and default selector
    	if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector)
    	{
    		ref readonly var candidate = ref candidates[0];
    
    		// Just strict path matching (no route values)
    		if (candidate.Flags == Candidate.CandidateFlags.None)
    		{
    			httpContext.SetEndpoint(candidate.Endpoint);
    
    			// We're done
    			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 candidateState = new CandidateState[candidateCount];
    
    	for (var i = 0; i < candidateCount; 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 readonly var candidate = ref candidates[i];
    		ref var state = ref candidateState[i];
    		state = new CandidateState(candidate.Endpoint, candidate.Score);
    
    		var flags = candidate.Flags;
    
    		// First process all of the parameters and defaults.
    		if ((flags & Candidate.CandidateFlags.HasSlots) != 0)
    		{
    			// The Slots array has the default values of the route values in it.
    			//
    			// We want to create a new array for the route values based on Slots
    			// as a prototype.
    			var prototype = candidate.Slots;
    			var slots = new KeyValuePair<string, object?>[prototype.Length];
    
    			if ((flags & Candidate.CandidateFlags.HasDefaults) != 0)
    			{
    				Array.Copy(prototype, 0, slots, 0, prototype.Length);
    			}
    
    			if ((flags & Candidate.CandidateFlags.HasCaptures) != 0)
    			{
    				ProcessCaptures(slots, candidate.Captures, path, segments);
    			}
    
    			if ((flags & Candidate.CandidateFlags.HasCatchAll) != 0)
    			{
    				ProcessCatchAll(slots, candidate.CatchAll, path, segments);
    			}
    
    			state.Values = RouteValueDictionary.FromArray(slots);
    		}
    
    		// Now that we have the route values, we need to process complex segments.
    		// Complex segments go through an old API that requires a fully-materialized
    		// route value dictionary.
    		var isMatch = true;
    		if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
    		{
    			state.Values ??= new RouteValueDictionary();
    			if (!ProcessComplexSegments(candidate.Endpoint, candidate.ComplexSegments, path, segments, state.Values))
    			{
    				CandidateSet.SetValidity(ref state, false);
    				isMatch = false;
    			}
    		}
    
    		if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
    		{
    			state.Values ??= new RouteValueDictionary();
    			if (!ProcessConstraints(candidate.Endpoint, candidate.Constraints, httpContext, state.Values))
    			{
    				CandidateSet.SetValidity(ref state, false);
    				isMatch = false;
    			}
    		}
    
    		if (log)
    		{
    			if (isMatch)
    			{
    				Logger.CandidateValid(_logger, path, candidate.Endpoint);
    			}
    			else
    			{
    				Logger.CandidateNotValid(_logger, path, candidate.Endpoint);
    			}
    		}
    	}
    
    	if (policyCount == 0 && _isDefaultEndpointSelector)
    	{
    		// Fast path that avoids allocating the candidate set.
    		//
    		// We can use this when there are no policies and we're using the default selector.
    		DefaultEndpointSelector.Select(httpContext, candidateState);
    		return Task.CompletedTask;
    	}
    	else if (policyCount == 0)
    	{
    		// Fast path that avoids a state machine.
    		//
    		// We can use this when there are no policies and a non-default selector.
    		return _selector.SelectAsync(httpContext, new CandidateSet(candidateState));
    	}
    
    	return SelectEndpointWithPoliciesAsync(httpContext, policies, new CandidateSet(candidateState));
    }
    

    这一段代码我看了一下,就是通过一些列的判断,来设置httpcontext 的 Endpoint,这个有兴趣可以看一下.

    这里提一下上面这个_selector:

     services.TryAddSingleton<EndpointSelector, DefaultEndpointSelector>();
    

    看一下DefaultEndpointSelector:

    internal static void Select(HttpContext httpContext, 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))
    				{
    					httpContext.SetEndpoint(state.Endpoint);
    					httpContext.Request.RouteValues = state.Values!;
    				}
    
    				break;
    			}
    
    		default:
    			{
    				// Slow path: There's more than one candidate (to say nothing of validity) so we
    				// have to process for ambiguities.
    				ProcessFinalCandidates(httpContext, candidateState);
    				break;
    			}
    	}
    }
    
    private static void ProcessFinalCandidates(
    	HttpContext httpContext,
    	CandidateState[] candidateState)
    {
    	Endpoint? endpoint = null;
    	RouteValueDictionary? values = null;
    	int? foundScore = null;
    	for (var i = 0; i < candidateState.Length; i++)
    	{
    		ref var state = ref candidateState[i];
    		if (!CandidateSet.IsValidCandidate(ref state))
    		{
    			continue;
    		}
    
    		if (foundScore == null)
    		{
    			// This is the first match we've seen - speculatively assign it.
    			endpoint = state.Endpoint;
    			values = state.Values;
    			foundScore = state.Score;
    		}
    		else if (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 (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(candidateState);
    
    			// Unreachable, ReportAmbiguity always throws.
    			throw new NotSupportedException();
    		}
    	}
    
    	if (endpoint != null)
    	{
    		httpContext.SetEndpoint(endpoint);
    		httpContext.Request.RouteValues = values!;
    	}
    }
    

    这里可以看一下Select。

    如果候选项是0,那么跳过这个就没什么好说的。

    如果候选项是1,那么就设置这个的endpoint。

    如果匹配到多个,如果两个相同得分相同的,就会抛出异常,否则就是最后一个,也就是说我们不能设置完全相同的路由。

    然后对于匹配策略而言呢,是:

    感兴趣可以看一下。

    HttpMethodMatcherPolicy 这个是匹配405的。

    HostMatcherPolicy 这个是用来匹配host的,如果不符合匹配到的endpoint,会设置验证不通过。

    下一节把app.UseEndpoints 介绍一下。

  • 相关阅读:
    接口类和抽象类
    生成器和迭代器
    装饰器
    KMP算法(java,c++)
    java EE
    XML
    SQL语法
    单元测试
    JDBC
    SpringCloud
  • 原文地址:https://www.cnblogs.com/aoximin/p/15614194.html
Copyright © 2020-2023  润新知