源碼解析.Net中Middleware的實現


前言

本篇繼續之前的思路,不注重用法,如果還不知道有哪些用法的小伙伴,可以點擊這里,微軟文檔說的很詳細,在閱讀本篇文章前,還是希望你對中間件有大致的了解,這樣你讀起來可能更加能夠意會到意思。廢話不多說,咱們進入正題(ps:讀者要注意關注源碼的注釋哦😜)。

Middleware類之間的關系

下圖也是只列出重要的類和方法,其主要就是就ApplicationBuilder類,如下圖:

源碼解析

1.在使用中間件時,需要在StartUp類的Config方法中來完成(.Net自帶的中間件,官方有明確過使用的順序,可以看文檔),例如Use,Map,Run等方法,它們都通過IApplicationBuilder內置函數調用,所以我們先看ApplicationBuilder類的主體構造,代碼如下圖:

//這個是所有中間件的委托
public delegate Task RequestDelegate(HttpContext context);
public class ApplicationBuilder : IApplicationBuilder
{
    //服務特性集合key
    private const string ServerFeaturesKey = "server.Features";
    //注入的服務集合key
    private const string ApplicationServicesKey = "application.Services";
    //添加的中間件集合
    private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new();
    
    public ApplicationBuilder(IServiceProvider serviceProvider)
    {
        Properties = new Dictionary<string, object?>(StringComparer.Ordinal);
        ApplicationServices = serviceProvider;
    }

    public ApplicationBuilder(IServiceProvider serviceProvider, object server)
        : this(serviceProvider)
    {
        SetProperty(ServerFeaturesKey, server);
    }

    private ApplicationBuilder(ApplicationBuilder builder)
    {
        Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal);
    }

    public IServiceProvider ApplicationServices
    {
        get
        {
            return GetProperty<IServiceProvider>(ApplicationServicesKey)!;
        }
        set
        {
            SetProperty<IServiceProvider>(ApplicationServicesKey, value);
        }
    }

    public IFeatureCollection ServerFeatures
    {
        get
        {
            return GetProperty<IFeatureCollection>(ServerFeaturesKey)!;
        }
    }
    //緩存結果,方便讀取
    public IDictionary<string, object?> Properties { get; }

    private T? GetProperty<T>(string key)
    {
        return Properties.TryGetValue(key, out var value) ? (T?)value : default(T);
    }

    private void SetProperty<T>(string key, T value)
    {
        Properties[key] = value;
    }
    //添加委托調用,將中間件添加到集合中
    public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        _components.Add(middleware);
        return this;
    }
    //創建新的AppBuilder
    public IApplicationBuilder New()
    {
        return new ApplicationBuilder(this);
    }
    //執行Build,構造委托鏈
    public RequestDelegate Build()
    {
        RequestDelegate app = context =>
        {
            var endpoint = context.GetEndpoint();
            var endpointRequestDelegate = endpoint?.RequestDelegate;
            if (endpointRequestDelegate != null)
            {
                var message =
                    $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
                    $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                    $"routing.";
                throw new InvalidOperationException(message);
            }

            context.Response.StatusCode = StatusCodes.Status404NotFound;
            return Task.CompletedTask;
        };
        //后添加的在末端,先添加的先執行
        for (var c = _components.Count - 1; c >= 0; c--)
        {
            app = _components[c](app);
        }

        return app;
    }
}

根據上述代碼可以看出,向集合中添加項只能調用Use方法,然后在Build方法時將委托全部構造成鏈,請求參數是HttpContext,也就是說,每次請求時,直接調用這個鏈路頭部的委托就可以把所有方法走一遍。

  1. 接下來,看一下那些自定義的中間件是怎么加入到管道,並且在.net中是怎么處理的,源碼如下:
public static class UseMiddlewareExtensions
{
    internal const string InvokeMethodName = "Invoke";
    internal const string InvokeAsyncMethodName = "InvokeAsync";

    public static IApplicationBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]TMiddleware>(this IApplicationBuilder app, params object?[] args)
    {
        return app.UseMiddleware(typeof(TMiddleware), args);
    }

    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
    {    
        //判斷如果是以依賴注入的形式加入的中間件,需要繼承IMiddleware,則不允許有參數
        if (typeof(IMiddleware).IsAssignableFrom(middleware))
        {
            if (args.Length > 0)
            {
                throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
            }

            return UseMiddlewareInterface(app, middleware);
        }

        var applicationServices = app.ApplicationServices;
        return app.Use(next =>
        {  
            //檢查是否有Invoke或者InvokeAsync方法
            var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
            var invokeMethods = methods.Where(m =>
                string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
                || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
                ).ToArray();
            //且不能超過一個
            if (invokeMethods.Length > 1)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
            }
            //也不能等於零個
            if (invokeMethods.Length == 0)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
            }
            
            var methodInfo = invokeMethods[0];
            //返回類型必須是Task
            if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
            }
            //獲取Invoke方法參數
            var parameters = methodInfo.GetParameters();
            if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
            }
            //第一個參數是RequestDelegate
            var ctorArgs = new object[args.Length + 1];
            ctorArgs[0] = next;
            Array.Copy(args, 0, ctorArgs, 1, args.Length);
            //根據構造函數參數創建實例
            var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
            if (parameters.Length == 1)
            {   
                //如果是只有一個參數,直接根據實例Invoke方法,創建RequestDelegate委托
                return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
            }
            
            //說明Invoke有容器注入的其他服務,則這個方法就是獲取那些服務
            var factory = Compile<object>(methodInfo, parameters);

            return context =>
            {  
                //默認是請求的Scope容器,如果是null,則返回根容器
                var serviceProvider = context.RequestServices ?? applicationServices;
                if (serviceProvider == null)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                }
                //執行Invoke方法
                return factory(instance, context, serviceProvider);
            };
        });
    }

    private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType)
    {
        //調用Use方法,將委托添加到ApplicationBuilder的內存集合里
        return app.Use(next =>
        {
            return async context =>
            {
                //獲取中間件工廠類,從Scope容器中獲取注入的中間件
                var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));
                if (middlewareFactory == null)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                }
                //獲取中間件注入的對象實例
                var middleware = middlewareFactory.Create(middlewareType);
                if (middleware == null)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
                }

                try
                { 
                    //調用InvokeAsync方法
                    await middleware.InvokeAsync(context, next);
                }
                finally
                {
                    //實際上沒做處理,和容器的生命周期一致
                    middlewareFactory.Release(middleware);
                }
            };
        });
    }
}

根據上面的代碼可以看出,根據不同方式注入的中間件,.Net做了不同的處理,並且對自定義的中間件做類型檢查,但是最后必須調用app.Use方法,將委托加入到ApplicationBuilder的內存集合里面,到Build階段處理。

  1. 上面介紹了自定義中間件的處理方式,接下里我們依次介紹下Use,Map和Run方法的處理,源碼如下:
public static class UseExtensions
{
    public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
    {
        //調用Use方法,添加到內存集合里
        return app.Use(next =>
        {
            return context =>
            {
                //next就是下一個處理,也就是RequestDelegate
                Func<Task> simpleNext = () => next(context);
                return middleware(context, simpleNext);
            };
        });
    }
}
public static class MapExtensions
{
    public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
    {
        return Map(app, pathMatch, preserveMatchedPathSegment: false, configuration);
    }
    public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, bool preserveMatchedPathSegment, Action<IApplicationBuilder> configuration)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }
        //不能是/結尾,這個!.用法我也是學習到了
        if (pathMatch.HasValue && pathMatch.Value!.EndsWith("/", StringComparison.Ordinal))
        {
            throw new ArgumentException("The path must not end with a '/'", nameof(pathMatch));
        }
        //構建新的ApplicationBuilder對象,里面不包含之前添加的中間件
        var branchBuilder = app.New();
        //新分支里面的中間件
        configuration(branchBuilder);
        //執行Build方法構建分支管道
        var branch = branchBuilder.Build();

        var options = new MapOptions
        {
            Branch = branch,
            PathMatch = pathMatch,
            PreserveMatchedPathSegment = preserveMatchedPathSegment
        };
        //內部其實是檢查是否匹配,匹配的話執行Branch,不匹配繼續執行next
        return app.Use(next => new MapMiddleware(next, options).Invoke);
    }
}
public static class RunExtensions
{
    public static void Run(this IApplicationBuilder app, RequestDelegate handler)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }
        //只執行Handle,不做其他處理,也就是管道終端,給短路了
        app.Use(_ => handler);
    }
}

上面的代碼分別介紹了Use,Map,Run的方法實現,它們還是在需要將中間件加入到內存集合里面,但是對於不同的方法,它們達到的效果也不一樣。

  1. 總結上面的代碼可以看出,它執行完Build方法,把委托鏈構造出來之后,然后在每次請求的時候只需要將構造完成的HttpContext當作請求參數傳入之后,即可依次執行中間件的內容,那么應用程序是如何構建ApplicationBuilder對象實例,又是在哪里調用Build方法的呢?我們繼續往下看。
    我們知道在使用.Net通用模板的創建項目的時候,在Program里面有一句代碼,如下:
Host.CreateDefaultBuilder(args)
//這個方法主要是構建web主機,如Kestrel,集成IIS等操作
.ConfigureWebHostDefaults(webBuilder =>
{
    webBuilder.UseStartup<Startup>();
});

追溯其源碼的位置時,實際上它是作為了IHostedService服務來運行的,如果有不清楚IHostedService的小伙伴可以點擊這里,先看下官方文檔的解釋和用法,看完之后你就明白了。我們再來看源碼:

public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder)
{
    if (configure is null)
    {
        throw new ArgumentNullException(nameof(configure));
    }

    if (configureWebHostBuilder is null)
    {
        throw new ArgumentNullException(nameof(configureWebHostBuilder));
    }

    if (builder is ISupportsConfigureWebHost supportsConfigureWebHost)
    {
        return supportsConfigureWebHost.ConfigureWebHost(configure, configureWebHostBuilder);
    }

    var webHostBuilderOptions = new WebHostBuilderOptions();
    //下面兩行執行的代碼,是關於構建Host主機的,以后新的文章來說
    configureWebHostBuilder(webHostBuilderOptions);
    var webhostBuilder = new GenericWebHostBuilder(builder, webHostBuilderOptions);
    //執行自定的方法,例如模板方法里面的UseStartUp
    configure(webhostBuilder);
    //主要看這里,將其添加到HostedService
    builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
    return builder;
}
internal sealed partial class GenericWebHostService : IHostedService
{
  public async Task StartAsync(CancellationToken cancellationToken)
  {
      HostingEventSource.Log.HostStart();

      var serverAddressesFeature = Server.Features.Get<IServerAddressesFeature>();
      var addresses = serverAddressesFeature?.Addresses;
      //配置服務地址
      if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
      {
          var urls = Configuration[WebHostDefaults.ServerUrlsKey];
          if (!string.IsNullOrEmpty(urls))
          {
              serverAddressesFeature!.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey);

              foreach (var value in urls.Split(';', StringSplitOptions.RemoveEmptyEntries))
              {
                  addresses.Add(value);
              }
          }
      }
      //定義最終返回的委托變量
      RequestDelegate? application = null;

      try
      {  
          //默認StartUp類里面的Config
          var configure = Options.ConfigureApplication;

          if (configure == null)
          {
              throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration.");
          }
          //構建ApplicationBuilder
          var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
          //如果存在IStartupFilter,那么把要執行的中間件放到前面
          foreach (var filter in StartupFilters.Reverse())
          {
              configure = filter.Configure(configure);
          }
          configure(builder);
          //執行Build,開始構建委托鏈
          application = builder.Build();
      }
      catch (Exception ex)
      {
          Logger.ApplicationError(ex);

          if (!Options.WebHostOptions.CaptureStartupErrors)
          {
              throw;
          }

          var showDetailedErrors = HostingEnvironment.IsDevelopment() || Options.WebHostOptions.DetailedErrors;

          application = ErrorPageBuilder.BuildErrorPageApplication(HostingEnvironment.ContentRootFileProvider, Logger, showDetailedErrors, ex);
      }

      var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory);
      //啟動服務
      await Server.StartAsync(httpApplication, cancellationToken);
  }
}

從上面可以看出,最終是作為IHostedService來運行的,而StartAsync方法,則是在Host.Build().Run()中的Run方法里面統一執行所有注冊過IHostedService服務的集合,也就是在Run階段才開始構建管道(讀者可以自行看下源碼,以后的文章我也會講到)。

總結

通過解讀源碼可以看出中間件有以下特點:

  • 目前自定義的中間件要么需要繼承IMiddleware(不能傳遞參數),要么需要構造指定規則的類。
  • Use不會使管道短路(除非調用方不調用next),Map和Run會使管道短路,更多的是,Run不會再往下傳遞,也就是終止,而Map可能會往下傳遞。
  • 委托鏈的構造是在Run方法中執行的,並且作為IHostedService托管服務執行的。

上述文章中所展示的源碼並不是全部的源碼,筆者只是挑出其重點部分展示。由於文采能力有限😊,如果有沒說明白的或者沒有描述清楚的,又或者有錯誤的地方,還請評論指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM