.net core 源码解析-web app是如何启动并接收处理请求

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 最近.net core 1.1也发布了,蹒跚学步的小孩又长高了一些,园子里大家也都非常积极的在学习,闲来无事,扒拔源码,涨涨见识。先来见识一下web站点是如何启动的,如何接受请求,.net core web app最简单的例子,大约长这样 public static void M...

最近.net core 1.1也发布了,蹒跚学步的小孩又长高了一些,园子里大家也都非常积极的在学习,闲来无事,扒拔源码,涨涨见识。

img_edda1390b225987dc831cb10d2e727cf.png

先来见识一下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处理请求

```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方法中
//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的配置,启动,监听,接受请求,转交给上层服务的大概脉络逻辑就说完了。

目录
相关文章
|
8天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
8天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
8天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2天前
|
开发框架 小程序 前端开发
圈子社交app前端+后端源码,uniapp社交兴趣圈子开发,框架php圈子小程序安装搭建
本文介绍了圈子社交APP的源码获取、分析与定制,PHP实现的圈子框架设计及代码编写,以及圈子小程序的安装搭建。涵盖环境配置、数据库设计、前后端开发与接口对接等内容,确保平台的安全性、性能和功能完整性。通过详细指导,帮助开发者快速搭建稳定可靠的圈子社交平台。
|
6天前
|
前端开发 算法 安全
一站式搭建相亲交友APP丨交友系统源码丨语音视频聊天社交软件平台系统丨开发流程步骤
本文详细介绍了一站式搭建相亲交友APP的开发流程,涵盖需求分析、技术选型、系统设计、编码实现、测试、部署上线及后期维护等环节。通过市场调研明确平台定位与功能需求,选择适合的技术栈(如React、Node.js、MySQL等),设计系统架构和数据库结构,开发核心功能如用户注册、匹配算法、音视频聊天等,并进行严格的测试和优化,确保系统的稳定性和安全性。最终,通过云服务部署上线,并持续维护和迭代,提供一个功能完善、安全可靠的社交平台。
63 6
|
8天前
|
JSON 数据格式
.net HTTP请求类封装
`HttpRequestHelper` 是一个用于简化 HTTP 请求的辅助类,支持发送 GET 和 POST 请求。它使用 `HttpClient` 发起请求,并通过 `Newtonsoft.Json` 处理 JSON 数据。示例展示了如何使用该类发送请求并处理响应。注意事项包括:简单的错误处理、需安装 `Newtonsoft.Json` 依赖,以及建议重用 `HttpClient` 实例以优化性能。
49 2
|
6天前
|
前端开发 搜索推荐 PHP
大开眼界!uniapp秀操作,陪玩系统新功能,陪玩app源码,可实时互动随心优化!
多客游戏陪玩系统采用前端uniapp与PHP语言,实现全开源、易改造,RTC传输协议确保低延迟语音连麦,分布式部署应对高并发。功能创新包括游戏约单、多人语音聊天室、动态广场、私信聊天等,提供高端社交和个性化服务,满足各类需求,让玩家畅享游戏乐趣。
|
9天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
3月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
171 3
|
27天前
|
前端开发 安全 JavaScript
2025年,Web3开发学习路线全指南
本文提供了一条针对Dapp应用开发的学习路线,涵盖了Web3领域的重要技术栈,如区块链基础、以太坊技术、Solidity编程、智能合约开发及安全、web3.js和ethers.js库的使用、Truffle框架等。文章首先分析了国内区块链企业的技术需求,随后详细介绍了每个技术点的学习资源和方法,旨在帮助初学者系统地掌握Dapp开发所需的知识和技能。
2025年,Web3开发学习路线全指南

推荐镜像

更多