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

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

  1. public static void Main(string[] args)
  2. {
  3. //dotnet NetCoreWebApp.dll --server.urls="http://localhost:5000/;http://localhost:5001/"
  4. var config = new ConfigurationBuilder().AddCommandLine(args).Build();
  5. new WebHostBuilder()
  6. .UseConfiguration(config)
  7. .UseKestrel()
  8. .UseContentRoot(Directory.GetCurrentDirectory())
  9. //.UseIISIntegration()
  10. .UseStartup<Startup>()
  11. //.Configure(confApp =>
  12. //{
  13. // confApp.Run(context =>
  14. // {
  15. // return context.Response.WriteAsync("hello");
  16. // });
  17. //})
  18. .Build()
  19. .Run();
  20. }

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

1.1 加载配置

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

WebHostBuilder对UseStartup()的解析实现

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

Build方法关于这一块的代码大概如下:

  1. private IServiceCollection BuildHostingServices()
  2. {
  3. var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
  4. if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
  5. {
  6. services.AddSingleton(typeof(IStartup), startupType);
  7. }
  8. else
  9. {
  10. services.AddSingleton(typeof(IStartup), sp =>
  11. {
  12. var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
  13. var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
  14. return new ConventionBasedStartup(methods);
  15. });
  16. }
  17. }

能看出来其实Startup可以是一个实现了IStartup接口的类。为什么官方还需要搞一个普通类的方式呢?其实这里还有一个小技巧:

针对Configure和ConfigureServices方法我们还可以做的更多,那就是根据不同的environmentName调用不同的方法。

Configure方法可以是Configure+EnvironmentName,ConfigureServices则是Configure+EnvironmentName+Services。这样的话还能做到区分环境进去不同的配置。

下面代码展示了builder是如何选择这2个方法的

  1. private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
  2. {
  3. var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
  4. return new ConfigureBuilder(configureMethod);
  5. }
  6. private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
  7. {
  8. var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
  9. ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
  10. return servicesMethod == null ? null : new ConfigureServicesBuilder(servicesMethod);
  11. }

1.2 Build()

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

  1. public IWebHost Build()
  2. {
  3. // Warn about deprecated environment variables
  4. if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
  5. {
  6. Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
  7. }
  8. if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
  9. {
  10. Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
  11. }
  12. if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
  13. {
  14. Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
  15. }
  16. var hostingServices = BuildHostingServices();
  17. var hostingContainer = hostingServices.BuildServiceProvider();
  18. var host = new WebHost(hostingServices, hostingContainer, _options, _config);
  19. host.Initialize();
  20. return host;
  21. }

2.1 构建WebHost

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

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

2.2 ApplicationBuilderFactory.Build();

根据Server.Features build ApplicationBuilderFactory对象。 完成ApplicationBuilderFactory的build过程。

大致就是注册各类中间件_components(middleware),也就是说的这个 https://docs.asp.net/en/latest/fundamentals/middleware.html

借用官方的图说明一下什么是middleware。

  1. public RequestDelegate Build()
  2. {
  3. RequestDelegate app = context =>
  4. {
  5. context.Response.StatusCode = 404;
  6. return TaskCache.CompletedTask;
  7. };
  8. foreach (var component in _components.Reverse())
  9. {
  10. app = component(app);
  11. }
  12. return app;
  13. }

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

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

  1. public virtual void Start()
  2. {
  3. Initialize();
  4. _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
  5. var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>();
  6. var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
  7. _logger.Starting();
  8. Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));
  9. _applicationLifetime.NotifyStarted();
  10. _logger.Started();
  11. }

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

简化代码大约这样子

  1. public void Start<TContext>(IHttpApplication<TContext> application)
  2. {
  3. var engine = new KestrelEngine(new ServiceContext
  4. {
  5. //接收到请求之后,回调FrameFactory方法,开始处理请求
  6. FrameFactory = context =>
  7. {
  8. return new Frame<TContext>(application, context);
  9. },
  10. //启动完成,停止等通知事件
  11. AppLifetime = _applicationLifetime,
  12. Log = trace,
  13. ThreadPool = new LoggingThreadPool(trace),
  14. DateHeaderValueManager = dateHeaderValueManager,
  15. ServerOptions = Options
  16. });
  17. //启动工作线程
  18. engine.Start(threadCount);
  19. foreach (var address in _serverAddresses.Addresses.ToArray())
  20. {
  21. //判断ipv4,ipv6,localhosts得到监听的地址,并启动对该端口的监听,等待请求进来
  22. engine.CreateServer(address)
  23. }
  24. }
  25. //engine.Start(threadCount);
  26. public void Start(int count)
  27. {
  28. for (var index = 0; index < count; index++)
  29. {
  30. Threads.Add(new KestrelThread(this));
  31. }
  32. foreach (var thread in Threads)
  33. {
  34. thread.StartAsync().Wait();
  35. }
  36. }

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();

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

这里的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);

  1. public Context CreateContext(IFeatureCollection contextFeatures)
  2. {
  3. var httpContext = _httpContextFactory.Create(contextFeatures);
  4. var diagnoticsEnabled = _diagnosticSource.IsEnabled("Microsoft.AspNetCore.Hosting.BeginRequest");
  5. var startTimestamp = (diagnoticsEnabled || _logger.IsEnabled(LogLevel.Information)) ? Stopwatch.GetTimestamp() : 0;
  6. var scope = _logger.RequestScope(httpContext);
  7. _logger.RequestStarting(httpContext);
  8. if (diagnoticsEnabled)
  9. {
  10. _diagnosticSource.Write("Microsoft.AspNetCore.Hosting.BeginRequest", new { httpContext = httpContext, timestamp = startTimestamp });
  11. }
  12. return new Context
  13. {
  14. HttpContext = httpContext,
  15. Scope = scope,
  16. StartTimestamp = startTimestamp,
  17. };
  18. }

2.7 Host处理请求

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

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

.net core 源码解析-web app是如何启动并接收处理请求的更多相关文章

  1. .net core 源码解析-web app是如何启动并接收处理请求(二) kestrel的启动

    上篇讲到.net core web app是如何启动并接受请求的,下面接着探索kestrel server是如何完成此任务的. 1.kestrel server的入口KestrelServer.Sta ...

  2. [源码解析] 分布式任务队列 Celery 之启动 Consumer

    [源码解析] 分布式任务队列 Celery 之启动 Consumer 目录 [源码解析] 分布式任务队列 Celery 之启动 Consumer 0x00 摘要 0x01 综述 1.1 kombu.c ...

  3. .net core 源码解析-mvc route的注册,激活,调用流程(三)

    .net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...

  4. Spring源码解析-Web容器启动过程

    Web容器启动过程,主要讲解Servlet和Spring容器结合的内容. 流程图如下: Web容器启动的Root Context是有ContextLoaderListener,一般使用spring,都 ...

  5. Spring源码解析之:Spring Security启动细节和工作模式--转载

    原文地址:http://blog.csdn.net/bluishglc/article/details/12709557 Spring-Security的启动加载细节   Spring-Securit ...

  6. scrapy 源码解析 (三):启动流程源码分析(三) ExecutionEngine执行引擎

    ExecutionEngine执行引擎 上一篇分析了CrawlerProcess和Crawler对象的建立过程,在最终调用CrawlerProcess.start()之前,会首先建立Execution ...

  7. scrapy 源码解析 (五):启动流程源码分析(五) Scraper刮取器

    Scraper刮取器 对ExecutionEngine执行引擎篇出现的Scraper进行展开.Scraper的主要作用是对spider中间件进行管理,通过中间件完成请求.响应.数据分析等工作. Scr ...

  8. scrapy 源码解析 (四):启动流程源码分析(四) Scheduler调度器

    Scheduler调度器 对ExecutionEngine执行引擎篇出现的Scheduler进行展开.Scheduler用于控制Request对象的存储和获取,并提供了过滤重复Request的功能. ...

  9. scrapy 源码解析 (二):启动流程源码分析(二) CrawlerProcess主进程

    CrawlerProcess主进程 它控制了twisted的reactor,也就是整个事件循环.它负责配置reactor并启动事件循环,最后在所有爬取结束后停止reactor.另外还控制了一些信号操作 ...

随机推荐

  1. 【转载】iOS屏幕适配设计

    移动app开发中多种设备尺寸适配问题,过去只属于Android阵营的头疼事儿,只是很多设计师选择性地忽视android适配问题,只出一套iOS平台设计稿.随着苹果发布两种新尺寸的大屏iPhone 6, ...

  2. js第三方

    1.0 https://github.com/aui/artDialog 2.0 前端开发仓库 http://code.ciaoca.com/ 3.0 打赏 https://github.com/gr ...

  3. c#面向对象基础技能——学习笔记(五)委托技术在开发中的应用

    委托 delegate 1.是一种全新的面向对象语言的特性: 2.开发事件驱动程序变得非常简单: 3.简化多线程难度. 理解委托:可以理解成一个方法的指针.(接收的变量是方法) 步骤: 1.声明委托, ...

  4. centos下升级mysql后遇到的小问题

    记录今天遇到的一个小问题, 写一个app访问接口涉及到通过存储过程反馈多个结果集,但是反回多个结果集的存储过程,调用之后只能反回一个了,而且奇怪的是,即使直接在mysql上同时执行两条查询语句,第一条 ...

  5. arcgis api for js入门开发系列二不同地图服务展示(含源代码)

    上一篇介绍了arcgis api离线部署,这篇开始正式介绍arcgis api for js开发:想要学习webgis开发,首先得熟悉了解前端技术,比如界面布局设计的html+css,核心的是java ...

  6. listview侧滑删除

    自定义Listview,向左滑动,右边刚好显示删除按钮: public class SlideListView extends ListView { private int mScreenWidth; ...

  7. mac下查看.mobileprovision文件及钥匙串中证书.cer文件

    mac下查看.mobileprovision文件及钥匙串中证书.cer文件 一. mobileprovision文件查看 xxx.mobileprovision是ios开发中的设备描述文件,里面有证书 ...

  8. 关于DOM的一些总结(未完待续......)

    DOM 实例1:购物车实例(数量,小计和总计的变化) 这里主要是如何获取页面元素的节点: document.getElementById("...") cocument.query ...

  9. EF里如何定制实体的验证规则和实现IObjectWithState接口进行验证以及多个实体的同时验证

    之前的Code First系列文章已经演示了如何使用Fluent API和Data Annotation的方式配置实体的属性,比如配置Destination类的Name属性长度不大于50等.本文介绍E ...

  10. jQuery 获取 radio 选中后的文字

    如果html为 <input type="radio" id="test" name="test" value="1&quo ...