.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。
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的配置,啟動,監聽,接受請求,轉交給上層服務的大概脈絡邏輯就說完了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM