.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方法關于這一塊的代碼大概如下:
?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個方法的
?{ ? ? ? ? ?
?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 variablesif (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接口的serverEnsureServer(); ?
????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。
?{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交給了統一的HostVerifyResponseContentLength(); }
這里的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方法中
?{ ? ? ? ? ? ?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跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的.net core 源码解析-web app是如何启动并接收处理请求的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Roslyn 编译器服务
- 下一篇: 消息队列 Kafka 的基本知识及 .N