zoukankan      html  css  js  c++  java
  • 重新整理 .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 介绍一下。

  • 相关阅读:
    【Elasticsearch 技术分享】—— ES 常用名词及结构
    【Elasticsearch 技术分享】—— Elasticsearch ?倒排索引?这都是什么?
    除了读写锁,JUC 下面还有个 StampedLock!还不过来了解一下么?
    小伙伴想写个 IDEA 插件么?这些 API 了解一下!
    部署Microsoft.ReportViewe
    关于TFS强制undo他人check out
    几段查看数据库表占用硬盘空间的tsql
    How to perform validation on sumbit only
    TFS 2012 Disable Multiple Check-out
    在Chrome Console中加载jQuery
  • 原文地址:https://www.cnblogs.com/aoximin/p/15614194.html
Copyright © 2011-2022 走看看