欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET Core开发日志——从ASP.NET Core Module到KestrelServer

程序员文章站 2022-07-04 11:11:17
ASP.NET Core程序现在变得如同控制台(Console)程序一般,同样通过Main方法启动整个应用。而Main方法要做的事情很简单,创建一个WebHostBuilder类,调用其Build方法生成一个WebHost类,最后启动之。 实现代码一目了然: 要想探寻其内部究竟做了哪些操作,则需要调 ......

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对象,再对此对象进行处理。

.NET Core开发日志——从ASP.NET Core Module到KestrelServer

图中的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应用程序也可以开始其自身的任务处理了。