zoukankan      html  css  js  c++  java
  • .net core 源码解析-web app是如何启动并接收处理请求

    最近.net core 1.1也发布了,蹒跚学步的小孩又长高了一些,园子里大家也都非常积极的在学习,闲来无事,扒拔源码,涨涨见识。

    先来见识一下web站点是如何启动的,如何接受请求,.net core web app最简单的例子,大约长这样

            public static void Main(string[] args)
            {
                //dotnet NetCoreWebApp.dll --server.urls="http://localhost:5000/;http://localhost:5001/"
                var config = new ConfigurationBuilder().AddCommandLine(args).Build();
    
                new WebHostBuilder()
                    .UseConfiguration(config)
                    .UseKestrel()
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    //.UseIISIntegration()
                    .UseStartup<Startup>()
                    //.Configure(confApp =>
                    //{
                    //    confApp.Run(context =>
                    //    {
                    //        return context.Response.WriteAsync("hello");
                    //    });
                    //})
                    .Build()
                    .Run();
            }
    

    WebHostBuilder看名字也知道是为了构建WebHost而存在的。在构建WebHost的路上他都做了这些:如加载配置,注册服务,配置功能等。

    1.1 加载配置

    builder内部维护了一个IConfiguration _config,可以简单的理解为key-value集合对象。可以通过UseSetting增加,也可以通过UseConfiguration增加

    WebHostBuilder对UseStartup()的解析实现

    我们从官方代码例子中能看到Startup类只是一个普通的类,builder是如何调用到这个类的方法的呢?
    Build方法关于这一块的代码大概如下:

    private IServiceCollection BuildHostingServices()
    {
        var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
    
        if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
        {
            services.AddSingleton(typeof(IStartup), startupType);
        }
        else
        {
            services.AddSingleton(typeof(IStartup), sp =>
            {
                var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
                var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
                return new ConventionBasedStartup(methods);
            });
        }
    }
    

    能看出来其实Startup可以是一个实现了IStartup接口的类。为什么官方还需要搞一个普通类的方式呢?其实这里还有一个小技巧:
    针对Configure和ConfigureServices方法我们还可以做的更多,那就是根据不同的environmentName调用不同的方法。
    Configure方法可以是Configure+EnvironmentName,ConfigureServices则是Configure+EnvironmentName+Services。这样的话还能做到区分环境进去不同的配置。
    下面代码展示了builder是如何选择这2个方法的

            private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
            {
                var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
                return new ConfigureBuilder(configureMethod);
            }
    
            private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
            {
                var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
                    ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
                return servicesMethod == null ? null : new ConfigureServicesBuilder(servicesMethod);
            }
    

    1.2 Build()

    根据之前use的各类配置,服务,参数等构建WebHost

    public IWebHost Build()
    {
        // Warn about deprecated environment variables
        if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
        {
            Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
        }
    
        if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
        {
            Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
        }
    
        if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
        {
            Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
        }
    
        var hostingServices = BuildHostingServices();
        var hostingContainer = hostingServices.BuildServiceProvider();
    
        var host = new WebHost(hostingServices, hostingContainer, _options, _config);
    
        host.Initialize();
    
        return host;
    }
    

    2.1 构建WebHost

    调用Initialize完成,host的初始化工作。Initialize 调用一次BuildApplication();

    public void Initialize()
    {
        if (_application == null)
        {
            _application = BuildApplication();
        }
    }
    private RequestDelegate BuildApplication()
    {
    
        //获取ServiceCollection中的IStartup,完成我们Startup.ConfigureService方法的调用,将我们代码注册的service加入到系统
        EnsureApplicationServices();
        //解析可以为urls或server.urls的value为绑定的address。以;分割的多个地址
        //初始化UseKestrel(),UseIISIntegration()等指定的 实现了IServer接口的server
        EnsureServer();
    
        var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
        var builder = builderFactory.CreateBuilder(Server.Features);
        builder.ApplicationServices = _applicationServices;
    
        var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
        Action<IApplicationBuilder> configure = _startup.Configure;
        foreach (var filter in startupFilters.Reverse())
        {
            configure = filter.Configure(configure);
        }
    
        configure(builder);
    
        return builder.Build();
    }
    

    2.2 ApplicationBuilderFactory.Build();

    根据Server.Features build ApplicationBuilderFactory对象。 完成ApplicationBuilderFactory的build过程。
    大致就是注册各类中间件_components(middleware),也就是说的这个 https://docs.asp.net/en/latest/fundamentals/middleware.html
    借用官方的图说明一下什么是middleware。
    middleware调用图

    public RequestDelegate Build()
            {
                RequestDelegate app = context =>
                {
                    context.Response.StatusCode = 404;
                    return TaskCache.CompletedTask;
                };
    
                foreach (var component in _components.Reverse())
                {
                    app = component(app);
                }
    
                return app;
            }
    

    2.3 builder完成之后,接着执行Run方法启动web服务

    启动host。host.Run();最终调用到WebHost.Start(),并调用当前app指定的Server对象启动web服务

    public virtual void Start()
            {
                Initialize();
    
                _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
                var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>();
                var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
    
                _logger.Starting();
    
                Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));
    
                _applicationLifetime.NotifyStarted();
                _logger.Started();
            }
    

    2.4 KestrelHttpServer的Start方法,启动对监听的监听接收请求

    简化代码大约这样子

    public void Start<TContext>(IHttpApplication<TContext> application)
    {
    	var engine = new KestrelEngine(new ServiceContext
    	                {
                                //接收到请求之后,回调FrameFactory方法,开始处理请求
    	                    FrameFactory = context =>
    	                    {
    	                        return new Frame<TContext>(application, context);
    	                    },
                                //启动完成,停止等通知事件
    	                    AppLifetime = _applicationLifetime,
    	                    Log = trace,
    	                    ThreadPool = new LoggingThreadPool(trace),
    	                    DateHeaderValueManager = dateHeaderValueManager,
    	                    ServerOptions = Options
    	                });
    	//启动工作线程
    	engine.Start(threadCount);
    	foreach (var address in _serverAddresses.Addresses.ToArray())
    	{
    		//判断ipv4,ipv6,localhosts得到监听的地址,并启动对该端口的监听,等待请求进来
    		engine.CreateServer(address)
    	}
    }
    //engine.Start(threadCount);
    public void Start(int count)
            {
                for (var index = 0; index < count; index++)
                {
                    Threads.Add(new KestrelThread(this));
                }
    
                foreach (var thread in Threads)
                {
                    thread.StartAsync().Wait();
                }
            }
    

    engine.CreateServer(address)
    先不说了,是tcpListener的一堆代码。看了代码感觉这里又是深不可测,先放着,有空了在撸这一部分。需要理解tcpListener为何如此设计,需要精读这部分代码

    2.5 接收请求后的处理

    listerner接到请求之后 实例化Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Connection,并调用该对象的Start()
    接着由Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.Start() 异步启动task开始处理请求。

    KestrelHttpServer处理请求:Frame.RequestProcessingAsync();

    public override async Task RequestProcessingAsync()
    {
    	var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
    	_keepAlive = messageBody.RequestKeepAlive;
    	_upgrade = messageBody.RequestUpgrade;
    	InitializeStreams(messageBody);
    	var context = _application.CreateContext(this);
    	 await _application.ProcessRequestAsync(context).ConfigureAwait(false);
    	//经过一系列的检查,各种判断,请求终于由KestrelHttpServer交给了统一的Host
    	 VerifyResponseContentLength();
    }
    

    这里的application 就是Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));这里实例化的HostingApplication
    也就是Microsoft.AspNetCore.Hosting.Internal下面的public class HostingApplication : IHttpApplication<HostingApplication.Context>

    2.6 httpcontext的创建 _application.CreateContext(this);

    public Context CreateContext(IFeatureCollection contextFeatures)
            {
                var httpContext = _httpContextFactory.Create(contextFeatures);
                var diagnoticsEnabled = _diagnosticSource.IsEnabled("Microsoft.AspNetCore.Hosting.BeginRequest");
                var startTimestamp = (diagnoticsEnabled || _logger.IsEnabled(LogLevel.Information)) ? Stopwatch.GetTimestamp() : 0;
    
                var scope = _logger.RequestScope(httpContext);
                _logger.RequestStarting(httpContext);
                if (diagnoticsEnabled)
                {
                    _diagnosticSource.Write("Microsoft.AspNetCore.Hosting.BeginRequest", new { httpContext = httpContext, timestamp = startTimestamp });
                }
    
                return new Context
                {
                    HttpContext = httpContext,
                    Scope = scope,
                    StartTimestamp = startTimestamp,
                };
            }
    

    2.7 Host处理请求

    public Task ProcessRequestAsync(Context context)
            {
                return _application(context.HttpContext);
            }
    
    ~~~
    这里的_application就是Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));中的_application,也就是BuildApplication()构建出来的RequestDelegate。开启mvc处理流程
    <h3 id='id3'>3 mvc接受请求,开始处理流程</h3>
    mvc大致调用顺序:Startup.Configure方法中
    ```C#
    //1
    app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
    //2
    public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> onfigureRoutes)
            {
                return app.UseRouter(routes.Build());
            }
    //3
    public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
    {
        if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
        {
            throw new InvalidOperationException(Resources.FormatUnableToFindServices(
                nameof(IServiceCollection),
                nameof(RoutingServiceCollectionExtensions.AddRouting),
                "ConfigureServices(...)"));
        }
        //注册一个Middleware接收请求,开始处理.如2.2所展示的代码,RouterMiddleware将加入到_components,由2.7完成调用
        return builder.UseMiddleware<RouterMiddleware>(router);
    }
    

    至此,mvc框架才真正开始处理我们的web请求。host的配置,启动,监听,接受请求,转交给上层服务的大概脉络逻辑就说完了。

  • 相关阅读:
    字符串替换
    Problem E: Automatic Editing
    正则表达式学习(1)
    python中的enumerate使用
    使用bottle进行web开发(9):文件上传;json传递
    使用bottle进行web开发(8):get的参数传递,form里的额数据传递等
    dict的setdefault(学习bottle源代码)
    使用bottle进行web开发(6):Response 对象
    使用bottle进行web开发(5):Generating Content
    使用bottle进行web开发(4):HTTPError
  • 原文地址:https://www.cnblogs.com/calvinK/p/6008915.html
Copyright © 2011-2022 走看看