ASP.NET Core程序现在变得如同控制台(Console)程序一般,同样通过Main方法启动整个应用。而Main方法要做的事情很简单,创建一个WebHostBuilder类,调用其Build方法生成一个WebHost类,最后启动之。

实现代码一目了然:

public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
} public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}

要想探寻其内部究竟做了哪些操作,则需要调查下WebHost类中CreateDefaultBuilder静态方法:

public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
{
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
if (args != null)
{
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
} builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
} config.AddEnvironmentVariables(); if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.ConfigureServices((hostingContext, services) =>
{
// Fallback
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
// "AllowedHosts": "localhost;127.0.0.1;[::1]"
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
// Fall back to "*" to disable.
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
// Change notification
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
})
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
}); return builder;
}

代码稍微有点多,但这里只关心WebHostBuilder类的创建,以及该builder使用了UseKestrel方法。

UseKestrel方法内部通过IoC的方式注入了KestrelServer类:

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices(services =>
{
// Don't override an already-configured transport
services.TryAddSingleton<ITransportFactory, SocketTransportFactory>(); services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
services.AddSingleton<IServer, KestrelServer>();
});
}

由此可以知道当一个ASP.NET Core应用程序运行起来时,其内部会有KestrelServer。

那么为什么会需要这个KestrelServer?因为它可以做为一个反向代理服务器,帮助ASP.NET Core实现跨平台的需要。

以传统Windows系统上的IIS为例,如下图所示,ASP.NET Core应用程序中的代码已经不再直接依赖于IIS容器,而是通过KestrelServer这个代理将HTTP请求转换为HttpContext对象,再对此对象进行处理。

图中的ASP.NET Core Module也是由ASP.NET Core的诞生而引入的新的IIS模块。它的主要功能是将Web请求重定向至ASP.NET Core应用程序。并且由于ASP.NET Core应用程序独立运行于IIS工作进程之外的进程,它还负责对进程的管理。

ASP.NET Core Module的源码由C++编写,入口是main文件中的RegisterModule函数。

其函数内部实例化了CProxyModuleFactory工厂类。

pFactory = new CProxyModuleFactory;

而由这个工厂类创建的CProxyModule实例中有一个关键的CProxyModule::OnExecuteRequestHandler方法。它会创建FORWARDING_HANDLER实例,并调用其OnExecuteRequestHandler方法。

__override
REQUEST_NOTIFICATION_STATUS
CProxyModule::OnExecuteRequestHandler(
IHttpContext * pHttpContext,
IHttpEventProvider *
)
{
m_pHandler = new FORWARDING_HANDLER(pHttpContext);
if (m_pHandler == NULL)
{
pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY);
return RQ_NOTIFICATION_FINISH_REQUEST;
} return m_pHandler->OnExecuteRequestHandler();
}

在此方法里就有那些核心的处理HTTP请求的操作。

// 实例化应用程序管理器
pApplicationManager = APPLICATION_MANAGER::GetInstance(); // 取得应用程序实例
hr = pApplicationManager->GetApplication(m_pW3Context, &m_pApplication); // 取得该应用程序的进程
hr = m_pApplication->GetProcess(m_pW3Context, pAspNetCoreConfig, &pServerProcess); // 创建HTTP请求
hr = CreateWinHttpRequest(pRequest,
pProtocol,
hConnect,
&struEscapedUrl,
pAspNetCoreConfig,
pServerProcess); // 发送HTTP请求
if (!WinHttpSendRequest(m_hRequest,
m_pszHeaders,
m_cchHeaders,
NULL,
0,
cbContentLength,
reinterpret_cast<DWORD_PTR>(static_cast<PVOID>(this))))
{
hr = HRESULT_FROM_WIN32(GetLastError());
DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO,
"FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed");
goto Failure;
}

在ASP.NET Core应用程序这端,CreateWebHostBuilder(args).Build().Run();代码执行之后,会调用其对应的异步方法:

private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage)
{
using (host)
{
await host.StartAsync(token); var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
var options = host.Services.GetRequiredService<WebHostOptions>(); if (!options.SuppressStatusMessages)
{
Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}");
Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}"); var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses;
if (serverAddresses != null)
{
foreach (var address in serverAddresses)
{
Console.WriteLine($"Now listening on: {address}");
}
} if (!string.IsNullOrEmpty(shutdownMessage))
{
Console.WriteLine(shutdownMessage);
}
} await host.WaitForTokenShutdownAsync(token);
}
}

该方法中又调用了WebHost的StartAsync方法:

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
HostingEventSource.Log.HostStart();
_logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
_logger.Starting(); var application = BuildApplication(); _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); // Fire IApplicationLifetime.Started
_applicationLifetime?.NotifyStarted(); // Fire IHostedService.Start
await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false); _logger.Started(); // Log the fact that we did load hosting startup assemblies.
if (_logger.IsEnabled(LogLevel.Debug))
{
foreach (var assembly in _options.GetFinalHostingStartupAssemblies())
{
_logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
}
} if (_hostingStartupErrors != null)
{
foreach (var exception in _hostingStartupErrors.InnerExceptions)
{
_logger.HostingStartupAssemblyError(exception);
}
}
}

BuildApplication方法内部从IoC容器取出KestrelServer的实例:

private void EnsureServer()
{
if (Server == null)
{
Server = _applicationServices.GetRequiredService<IServer>(); var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
var addresses = serverAddressesFeature?.Addresses;
if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
{
var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
if (!string.IsNullOrEmpty(urls))
{
serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey); foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
addresses.Add(value);
}
}
}
}
}

最后调用KestrelServer的StartAsync方法:

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
try
{
if (!BitConverter.IsLittleEndian)
{
throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
} ValidateOptions(); if (_hasStarted)
{
// The server has already started and/or has not been cleaned up yet
throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
}
_hasStarted = true;
_heartbeat.Start(); async Task OnBind(ListenOptions endpoint)
{
// Add the HTTP middleware as the terminal connection middleware
endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application, endpoint.Protocols); var connectionDelegate = endpoint.Build(); // Add the connection limit middleware
if (Options.Limits.MaxConcurrentConnections.HasValue)
{
connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
} var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
var transport = _transportFactory.Create(endpoint, connectionDispatcher);
_transports.Add(transport); await transport.BindAsync().ConfigureAwait(false);
} await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
}
catch (Exception ex)
{
Trace.LogCritical(0, ex, "Unable to start Kestrel.");
Dispose();
throw;
}
}

到了这一步,KestrelServer终于可以监听来自ASP.NET Core Module发出的HTTP请求,而ASP.NET Core应用程序也可以开始其自身的任务处理了。

.NET Core开发日志——从ASP.NET Core Module到KestrelServer的更多相关文章

  1. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  2. .NET Core开发日志——RequestDelegate

    本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是 ...

  3. 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(1)

    最近使用vscode比较多. 学习了一下如何在mac上使用vscode开发asp.netcore项目. 这里是我写的关于vscode的一篇文章: https://www.cnblogs.com/cgz ...

  4. .NET Core开发日志——Entity Framework与PostgreSQL

    Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...

  5. 在CentOS7 开发与部署 asp.net core app笔记

    原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...

  6. .NET Core 2.0和ASP.NET Core 2.0正式版抢先体验

    .NET Core 2.0和ASP.NET Core 2.0正式版抢先体验 .NET Standard 2.0 is final Broad platform support. .NET Standa ...

  7. List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac

    List多个字段标识过滤 class Program{  public static void Main(string[] args) { List<T> list = new List& ...

  8. ASP.NET Core 基础教程总结 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 基础教程总结 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 基础教程总结 ASP.NET Core 基础教程总算是有了个简单 ...

  9. ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 迁移数据 上一章节中我们配置了 ...

随机推荐

  1. 如何在页面中获取到ModelAndView绑定的值

    以下内容转自:https://blog.csdn.net/qq_16071145/article/details/51341052 springMVC中通过ModelAndView进行后台与页面的数据 ...

  2. 编写SHELL脚本--判断用户的参数

    测试语句格式: [ 条件表达式 ] 常见的几种形式: [ -d /etc ]  判断/etc是不是一个目录类型, [ -e /etc/php.ini ] 判断/etc/php.ini 文件是否存在 [ ...

  3. ql.io来自ebay的api快速集成的构建api的框架

    说的有点绕口,实际上是为了减轻在Web上请求数据的复杂度,eBay推出了自己的Web查询语言——ql.io,ql.io将多个独立的API请求绑定成一个单独的请求. ---待续

  4. Service discovery

    https://www.cnblogs.com/dirt2/p/5987067.html Use Assigned Numbers in the Service Discovery Protocol ...

  5. 并发和多线程-说说面试长提平时少用的volatile

    说到volatile,一些参加过面试的同学对此肯定不陌生. 它是面试官口中的常客,但是平时的编码却很少打照面(起码,我是这样的). 最近的面试,我也经常会问到volatile相关的问题,比如volat ...

  6. epoll的由来

    reference https://www.zhihu.com/question/20122137 感谢 @静海听风 @蓝形参 数据流有两个重要的参与者: 1.往流中写入数据者 2.从流中读取数据者 ...

  7. 【Linux高级驱动】平台设备驱动机制的编程流程与编译进内核

    [平台设备驱动机制的编程流程] [如何将驱动静态的编译进内核镜像] 1.添加资源(dev-led.c) 1.1:一般来说,系统习惯上将资源放在arch/arm/plat-samsung/目录中 cp ...

  8. Asp.Net AutoMapper用法

    1.AutoMapper简介 用于两个对象映射,例如把Model的属性值赋值给View Model.传统写法会一个一个属性的映射很麻烦,使用AutoMapper两句代码搞定. 2.AutoMapper ...

  9. Oracle分割字符串 REGEXP_SUBSTR用法

    分割字符串中所有指定字符,然后成多行参数说明,参数1: 待分割字符串参数2:正则表达式参数3:起始位置,从第几个字符开始正则表达式匹配(默认为1)参数4:标识第几个匹配组,默认为1参数5:模式('i' ...

  10. windows后门

    原文:揭秘Windows系统的四个后门 组策略欺骗后门 创建一个批处理文件add.bat,内容是: @echo off net user hack$ test168 /add net localgro ...