• 【ASP.NET Core】运行原理(2):启动WebHost


    本系列将分析ASP.NET Core运行原理

    本节将分析WebHost.StartAsync();代码,确定是如何一步一步到我们注册的中间件,并介绍几种Configure的方式。

    源代码参考.NET Core 2.0.0

    目录

    • Server.StartAsync
      • Server
      • IHttpApplication
      • HttpContextFactory
      • HttpContext
    • Configure
      • IApplicationBuilder
      • Use
      • Run
      • UseMiddleware
      • UseWhen
      • MapWhen
      • Map

    Server.StartAsync

    在上节我们知道WebHost.StartAsync内部是调用Server.StartAsync的。

    public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
    {
        async Task OnBind(ListenOptions endpoint)
        {
            var connectionHandler = new ConnectionHandler<TContext>(endpoint, ServiceContext, application);
            var transport = _transportFactory.Create(endpoint, connectionHandler);
            _transports.Add(transport);
    
            await transport.BindAsync().ConfigureAwait(false);
        }
    
        await AddressBinder.BindAsync(_serverAddresses, Options.ListenOptions, Trace, OnBind).ConfigureAwait(false);
    }
    

    参数application即为之前的new HostingApplication。在这里说下大概的流程:

    KestrelServer.StartAsync -> new ConnectionHandler<TContext>().OnConnection -> new FrameConnection().StartRequestProcessing() -> 
    new Frame<TContext>().ProcessRequestsAsync() -> _application.CreateContext(this) && _application.ProcessRequestAsync(context)
    

    如果你需要更细节的流程,可参考如下:

    LibuvTransportFactory -> LibuvTransport.BindAsync() -> ListenerPrimary.StartAsync() -> 
    listener.ListenSocket.Listen(LibuvConstants.ListenBacklog, ConnectionCallback, listener) -> listener.OnConnection(stream, status) -> ConnectionCallback() ->
    new LibuvConnection(this, socket).Start() -> ConnectionHandler.OnConnection() -> connection.StartRequestProcessing() -> 
    ProcessRequestsAsync -> CreateFrame -> await _frame.ProcessRequestsAsync()
    
    1. _application 为上面的HostingApplication;
    2. 每个WebHost.StartAsync 将创建唯一的一个HostingApplication实例并在每次请求时使用。
    3. 由Frame类调用HostingApplication的方法。

    下面展示Frame以及HostingApplication:

    Frame

    public class Frame<TContext> : Frame
    {
        public override async Task ProcessRequestsAsync()
        {
            while (!_requestProcessingStopping)
            {
                Reset();
    
                EnsureHostHeaderExists();
    
                var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
                InitializeStreams(messageBody);
    
                var context = _application.CreateContext(this);
                try
                {
                    await _application.ProcessRequestAsync(context);
                }
                finally
                {
                    _application.DisposeContext(context, _applicationException);
                }
            }
        }
    }
    

    HostingApplication

    public class HostingApplication : IHttpApplication<HostingApplication.Context>
    {
        private readonly RequestDelegate _application;
        private readonly IHttpContextFactory _httpContextFactory;
    
        public HostingApplication(
            RequestDelegate application,
            IHttpContextFactory httpContextFactory)
        {
            _application = application;
            _httpContextFactory = httpContextFactory;
        }
    
        // Set up the request
        public Context CreateContext(IFeatureCollection contextFeatures)
        {
            var context = new Context();
            var httpContext = _httpContextFactory.Create(contextFeatures);
            context.HttpContext = httpContext;
            return context;
        }
    
        // Execute the request
        public Task ProcessRequestAsync(Context context)
        {
            return _application(context.HttpContext);
        }
    
        // Clean up the request
        public void DisposeContext(Context context, Exception exception)
        {
            var httpContext = context.HttpContext;
            _httpContextFactory.Dispose(httpContext);
        }
    
        public struct Context
        {
            public HttpContext HttpContext { get; set; }
        }
    }
    

    由此我们发现HttpContext是由HttpContextFactory创建的,其中_httpContextFactory则是上节在WebHostBuilder的BuildCommon注入的
    同时在HostingApplication的ProcessRequestAsync方法中,我们看到我们的_application(Startup注册的中间件)被调用了。
    IHttpContextFactory

    HttpContextFactory

    public HttpContext Create(IFeatureCollection featureCollection)
    {
        var httpContext = new DefaultHttpContext(featureCollection);
        if (_httpContextAccessor != null)
            _httpContextAccessor.HttpContext = httpContext;
        return httpContext;
    }
    

    而创建的HttpContext则是DefaultHttpContext类型:

    public class DefaultHttpContext : HttpContext
    {
        public virtual void Initialize(IFeatureCollection features)
        {
            _features = new FeatureReferences<FeatureInterfaces>(features);
            _request = InitializeHttpRequest();
            _response = InitializeHttpResponse();
        }
    
        public override HttpRequest Request => _request;
    
        public override HttpResponse Response => _response;
    }
    

    Configure

    IApplicationBuilder

    我们知道在Startup的Configure方法中,通过IApplicationBuilder可以注册中间件。

    public interface IApplicationBuilder
    {
        IServiceProvider ApplicationServices { get; set; }
        RequestDelegate Build();
        IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
    }
    

    默认实现类为:

    public class ApplicationBuilder : IApplicationBuilder
    {
        private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
        public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
        {
            _components.Add(middleware);
            return this;
        }
    
        public RequestDelegate Build()
        {
            RequestDelegate app = context =>
            {
                context.Response.StatusCode = 404;
                return Task.CompletedTask;
            };
    
            foreach (var component in _components.Reverse())
                app = component(app);
    
            return app;
        }
    }
    

    其中Use方法为注册中间件。中间件的本质就是一个Func<RequestDelegate, RequestDelegate>对象。
    该对象的传入参数为下一个中间件,返回对象为本中间件。

    而Build方法为生成一个RequestDelegate,在HostingApplication构造函数中的参数即为该对象。
    在Build方法中,我们看到最后一个中间件为404中间件。其他的中间件都是通过Use方法注册到内部维护的_components对象上。

    Use

    我们通过一个Use示例,来看下中间件的流程:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.Use(next => async context =>
        {
            Console.WriteLine("A begin");
            await next(context);
            Console.WriteLine("A end");
        });
    
        app.Use(next => async context =>
        {
            Console.WriteLine("B begin");
            await next(context);
            Console.WriteLine("B end");
        });
    }
    

    访问结果:
    A begin
    B begin
    B end
    A end

    流程图:
    流程图

    Run

    当我们不使用next 下一个中间件的时候,我们可以使用Run方法来实现
    Run方法接受一个RequestDelegate对象,本身是IApplicationBuilder的扩展方法。

    public static void Run(this IApplicationBuilder app, RequestDelegate handler);
    {
        app.Use(_ => handler);
    }
    

    Run示例

    app.Run(context=>context.Response.WriteAsync("Run Core"));

    该示例相当于:

    app.Use(next => context => context.Response.WriteAsync("Run Core"));

    UseMiddleware

    而通常我们添加中间件的方式是通过UseMiddleware来更加方便的操作。

    先看下IMiddleware:

    public interface IMiddleware
    {
        Task InvokeAsync(HttpContext context, RequestDelegate next);
    }
    

    参数next即为下一个中间件。

    有2种实现UseMiddleware的方式:

    1. 实现IMiddleware接口。
    2. 基于接口约定的方法。

    IMiddleware接口

    public class DemoMiddle : IMiddleware
    {
        public Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            return context.Response.WriteAsync("hello middleware");
        }
    }
    

    在使用IMiddleware接口的时候,还需要注册该类到DI系统中。

    约定

    public class DemoMiddle
    {
        private RequestDelegate _next;
        public DemoMiddle(RequestDelegate next)
        {
            _next = next;
        }
        public Task InvokeAsync(HttpContext context)
        {
            return context.Response.WriteAsync("hello middleware");
        }
    }
    

    这种方式,不用再注册到DI中,如果需要对该类构造函数传入参数,直接在app.UseMiddleware<DemoMiddle>("hi1");传入参数即可。

    UseWhen

    app.Use(next => async context => { await context.Response.WriteAsync("Begin"); await next(context); });
    
    app.UseWhen(context => context.Request.Path.Value == "/hello", branch => branch.Use(
        next => async context => { await context.Response.WriteAsync("hello"); await next(context); }));
    
    app.Run(context => context.Response.WriteAsync("End"));
    

    当我们访问/hello时,结果为:BeginhelloEnd
    分析源码得知在构建管道的时候,克隆一个另外的IApplicationBuilder。

    public static IApplicationBuilder UseWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
    {
        var branchBuilder = app.New();
        configuration(branchBuilder);
    
        return app.Use(main =>
        {
            // This is called only when the main application builder
            // is built, not per request.
            branchBuilder.Run(main);// 添加(调用)原来的中间件
            var branch = branchBuilder.Build();
    
            return context => predicate(context) ? branch(context): main(context);
        });
    }
    

    MapWhen

    app.Use(next => async context => { await context.Response.WriteAsync("Begin"); await next(context); });
    
    app.MapWhen(context => context.Request.Path.Value == "/hello", app2 => app2.Run(context => context.Response.WriteAsync("hello")));
    
    app.Run(context => context.Response.WriteAsync("End"));
    

    当我们访问/hello时,结果为:Beginhello
    分析源码得知在构建管道的时候,新分支并没有再调用原来的中间件。

    public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
    {
        var branchBuilder = app.New();
        configuration(branchBuilder);
        var branch = branchBuilder.Build();
        return app.Use(next => context => predicate(context) ? branch(context): next(context));
    }
    

    Map

    app.Map("/hello", app2 => app2.Run(context => context.Response.WriteAsync("hello")));
    

    当我们访问/hello时,结果为:Beginhello。与MapWhen效果一样。
    如果我们只是判断URLPath的话,通常我们会使用Map方法。

    以上是常用的注册中间件的方式。

    本文链接:http://neverc.cnblogs.com/p/8029419.html

  • 相关阅读:
    JavaScript表单验证年龄
    PHP会话处理相关函数介绍
    SpringCloud(第一天)
    springboot加强
    SpringBoot的第一个demo
    ElasticSearch(分布式全文搜索引擎)
    Redis集群
    NoSql和Redis
    ElementUI实现CRUD(修改前端页面),前后台解决跨域问题
    SSM+ElementUI综合练习和Swagger和postman的使用(第二天)
  • 原文地址:https://www.cnblogs.com/neverc/p/8029419.html
Copyright © 2020-2023  润新知