.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。

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

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处理请求

```C#
public Task ProcessRequestAsync(Context context)
{
return _application(context.HttpContext);
}

~~~
这里的_application就是Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));中的_application,也就是BuildApplication()构建出来的RequestDelegate。开启mvc处理流程

3 mvc接受请求,开始处理流程


mvc大致调用顺序:Startup.Configure方法中


//1app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });//2public 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的配置,启动,监听,接受请求,转交给上层服务的大概脉络逻辑就说完了。

原文地址:http://www.cnblogs.com/calvinK/p/6008915.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值