.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<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的配置,启动,监听,接受请求,转交给上层服务的大概脉络逻辑就说完了。
.net core 源码解析-web app是如何启动并接收处理请求的更多相关文章
- .net core 源码解析-web app是如何启动并接收处理请求(二) kestrel的启动
上篇讲到.net core web app是如何启动并接受请求的,下面接着探索kestrel server是如何完成此任务的. 1.kestrel server的入口KestrelServer.Sta ...
- [源码解析] 分布式任务队列 Celery 之启动 Consumer
[源码解析] 分布式任务队列 Celery 之启动 Consumer 目录 [源码解析] 分布式任务队列 Celery 之启动 Consumer 0x00 摘要 0x01 综述 1.1 kombu.c ...
- .net core 源码解析-mvc route的注册,激活,调用流程(三)
.net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...
- Spring源码解析-Web容器启动过程
Web容器启动过程,主要讲解Servlet和Spring容器结合的内容. 流程图如下: Web容器启动的Root Context是有ContextLoaderListener,一般使用spring,都 ...
- Spring源码解析之:Spring Security启动细节和工作模式--转载
原文地址:http://blog.csdn.net/bluishglc/article/details/12709557 Spring-Security的启动加载细节 Spring-Securit ...
- scrapy 源码解析 (三):启动流程源码分析(三) ExecutionEngine执行引擎
ExecutionEngine执行引擎 上一篇分析了CrawlerProcess和Crawler对象的建立过程,在最终调用CrawlerProcess.start()之前,会首先建立Execution ...
- scrapy 源码解析 (五):启动流程源码分析(五) Scraper刮取器
Scraper刮取器 对ExecutionEngine执行引擎篇出现的Scraper进行展开.Scraper的主要作用是对spider中间件进行管理,通过中间件完成请求.响应.数据分析等工作. Scr ...
- scrapy 源码解析 (四):启动流程源码分析(四) Scheduler调度器
Scheduler调度器 对ExecutionEngine执行引擎篇出现的Scheduler进行展开.Scheduler用于控制Request对象的存储和获取,并提供了过滤重复Request的功能. ...
- scrapy 源码解析 (二):启动流程源码分析(二) CrawlerProcess主进程
CrawlerProcess主进程 它控制了twisted的reactor,也就是整个事件循环.它负责配置reactor并启动事件循环,最后在所有爬取结束后停止reactor.另外还控制了一些信号操作 ...
随机推荐
- Dapper-据说stackoverflow使用的orm
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; usin ...
- 全局变量:global与$GLOBALS的区别和使用
今天在写框架的时候想把SaeMySQL初始化之后作为全局变量使用.但是后来发现PHP中的全局变量和Java或者OC中的全局变量还是有较大区别的.下面记录一下php里面的global的使用相关注意事项. ...
- asp.net MVC5 学习笔记(一)
Html.ActionLink("linkText","actionName") 该重载的第一个参数是该链接要显示的文字,第二个参数是对应的控制器的方法,默认控 ...
- Configure bridge on a team interface using NetworkManager in RHEL 7
SOLUTION IN PROGRESS February 29 2016 KB2181361 environment Red Hat Enterprise Linux 7 Teaming,Bridg ...
- JavaWeb_day05cookie_session_HttpSession
本文为博主辛苦总结,希望自己以后返回来看的时候理解更深刻,也希望可以起到帮助初学者的作用. 转载请注明 出自 : luogg的博客园 谢谢配合! 两个会话的技术cookie session 会话概念 ...
- Scrapy开发指南
一.Scrapy简介 Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中. Scrapy基于事件驱动网络框架 Twis ...
- WCF入门教程3——WCF通信模式
本章内容 请求/响应模式 单工模式 双工模式 WCF异步调用 请求与响应模式 请求/响应 请求/响应通信是指客户端向服务端发送消息后,服务端会向客户端发送响应.这也意味着在接收到服务的响应以前 ...
- storm0.9.5集群安装
安装前的准备工作 关闭防火墙 chkconfig iptables off && setenforce 0 创建用户 groupadd realtime && user ...
- iOS:GCD组
组内异步会与组外顺序执行的事件争抢资源 1).创建一个组 dispatch_group_t group = dispatch_group_create(); 2).组内异步ST1,DISPATCH_Q ...
- 旅游公司招聘Java工程师
公司招聘:岗位要求如下 Java开发工程师工作内容1.根据需求完成软件系统代码的开发,测试以及文档撰写工作:2.分析并解决客户的问题:3.配合业务部门进行数据分析以及系统优化 岗位要求:1.本科以上学 ...