Server是ASP .NET Core管道的第一个节点,负责完整请求的监听和接收,最终对请求的响应同样也由它完成。Server是我们对所有实现了IServer接口的所有类型以及对应对象的统称,如下面的代码片段所示,这个接口具有一个只读属性Features返回描述自身特性集合的FeatureCollection对象,另一个Start方法用于启动服务器。

   1: public interface IServer : IDisposable

   2: {

   3:     IFeatureCollection Features { get; }

   4:     void Start<TContext>(IHttpApplication<TContext> application);    

   5: }

当我们Start方法启动指定的Server的时候,它必须指定一个类型为IHttpApplication<TContext>的参数,我们将实现才接口的所有类型及其对应对象统称为HttpApplication。当Server在接收到抵达的请求之后,实际上会直接交给这个HttpApplication对象来处理,所以我们需要先来认识一下这个对象。

目录
一、HttpApplication
二、请求的处理与执行上下文的创建与释放
三、日志记录
    请求处理开始与结束时记录的日志
    针对请求的日志上下文范围
    请求唯一标识的生成

一、HttpApplication

对于ASP.NET Core管道来说,HttpApplication被用来处理Server接收的请求,这个对象可以视为对注册的所有中间件的封装,它对请求的处理工作实际上最终会委托这些中间件来完成。HttpApplication针对请求的处理实际上会在一个执行上下文中完成,这个上下文实际上为应用对单一请求的整个处理过程定义了一个边界。单纯描述HTTP请求的HttpContext是这个执行上下文中最为核心的部分,除此之外,我们还可以根据需要将其他相关的信息定义其中,所以IHttpApplication<TContext>接口采用泛型参数的形式来表示定义这个上下文的类型。

HttpApplication不仅仅需要在这个执行上下文中处理Server转发给它的请求,这个上下文对象的创建和回收释放同样需要由它来完成。如下面的代码片段所示,IHttpApplication<TContext>接口的CreateContext和DisposeContext方法分别体现了针对执行上下文的创建和释放,CreateContext方法的参数contextFeatures表示描述原始上下文的特性集合。在此上下文中针对请求的处理实现在另一个方法ProcessRequestAsync之中。

   1: public interface IHttpApplication<TContext>

   2: {

   3:     TContext CreateContext(IFeatureCollection contextFeatures);

   4:     void     DisposeContext(TContext context, Exception exception);

   5:     Task     ProcessRequestAsync(TContext context);

   6: }

在默认情况下创建的HttpApplication是一个HostingApplication对象。对于HostingApplication来说,它创建的执行上下文的类型是一个具有如下定义的结构体Context,它内嵌于HostingApplication类之中。对于这个Context对象表示的针对当前请求的执行上下文来说,描述当前HTTP请求的HttpContext是最为核心的部分。除了这个HttpContext属性之外,Context还具有额外两个属性,其中Scope是为追踪诊断而创建的日志上下文范围,该范围将针对同一个请求的多项日志记录进行关联,而另一个属性StartTimestamp表示应用开始处理请求的时间戳。

   1: public class HostingApplication : IHttpApplication<Context>

   2: {

   3:     //省略成员

   4:     public struct Context

   5:     {

   6:         public HttpContext     HttpContext { get; set; }

   7:         public IDisposable     Scope { get; set; }

   8:         public long            StartTimestamp { get; set; }

   9:     }

  10: }

二、请求的处理与执行上下文的创建与释放

由于HostingApplication针对请求的处理是通过注册的中间件来完成的,而后者最终会利用上面介绍的ApplicationBuilder对象转换成一个类型为RequestDelegate的委托对象,所以我们在创建HostingApplication的时候需要提供这么一个RequestDelegate对象。有HostingApplication创建的Context对象包含表示HTTP上下文的HttpContext对象,而后者是通过对应的工厂HttpContextFactory创建的,所以HttpContextFactory在创建时也是必须要提供的。如下面的代码片段所示,HostingApplication类型的构造函数需要将这两个对象作为输入参数,至于另外两个参数(logger和diagnosticSource),它们与日志记录有关,我们稍后会对此作专门的介绍。

   1: public class HostingApplication : IHttpApplication<HostingApplication.Context>

   2: {

   3:     private readonly RequestDelegate         _application;

   4:     private readonly DiagnosticSource        _diagnosticSource;

   5:     private readonly IHttpContextFactory     _httpContextFactory;

   6:     private readonly ILogger                 _logger;

   7:  

   8:     public HostingApplication(RequestDelegate application, ILogger logger,  DiagnosticSource diagnosticSource, IHttpContextFactory httpContextFactory)

   9:     {

  10:         _application         = application;

  11:         _logger              = logger;

  12:         _diagnosticSource    = diagnosticSource;

  13:         _httpContextFactory  = httpContextFactory;

  14:     }

  15: }

下面给出的代码片段基本体现了HostingApplication创建和释放Context对象,以及在此上下文中处理请求的逻辑。在CreateContext方法中,它直接利用初始化提供的HttpContextFactory创建一个HttpContext并将其作为Context对象的同名属性,至于Context额外两个属性(Scope和StartTimestamp)该作何设置,我们会在本节后续部分对此作专门介绍。实现在ProcessRequestAsync方法中针对请求的处理最终体现在对构造时指定的这个RequestDelegate对象的执行。当DisposeContext方法被执行的时候,Context的Scope属性会率先被释放,在此之后HttpContextFactory的Dispose方法被调用以完成对Context对象自身的回收释放。

   1: public class HostingApplication : IHttpApplication<HostingApplication.Context>

   2: {

   3:     public Context CreateContext(IFeatureCollection contextFeatures)

   4:     {

   5:         //省略其他实现代码

   6:         return new Context

   7:         {

   8:                HttpContext      = _httpContextFactory.Create(contextFeatures),

   9:                Scope            = ...,

  10:                StartTimestamp   = ...

  11:         };

  12:     }

  13:  

  14:     public Task ProcessRequestAsync(Context context)

  15:     {

  16:         Return _application(context.HttpContext);

  17:     }

  18:  

  19:     public void DisposeContext(Context context, Exception exception)

  20:     {        

  21:         //省略其他实现代码

  22:         context.Scope.Dispose();

  23:         _httpContextFactory.Dispose(context.HttpContext);

  24:     }

  25: }

三、日志记录

由于管道处理其中总是在一个由HttpApplication创建的执行上下文中进行,所有上下文的创建和回收释放可以视为 整个请求处理流程开始和结束的标识。对于HostingApplication来说,CreateContext和DisposeContext方法分别被调用的时候,它会利用初始化时指定的Logger对象作相应的日志记录。除此之外,作为开始处理请求标志的CreateContext方法还是创建一个日志上下文范围,其目的是将针对同一请求的日志时间关联起来。这个上下文范围对应着Context对象的Scope对象,通过上面的代码片段我们可以看出针对这个日志上下文范围的释放同样发生在DisposeContext方法中。

请求处理开始与结束时记录的日志

接下来我们通过实例演示的形式来看看究竟怎样的日志消息分别被它的CreateContext和DisposeContext方法记录下来。在一个ASP.NET Core控制台应用中,为了将记录的日志消息直接打印到控制台上,我们需要为管道使用的LoggerFactory注册一个ConsoleLoggerProvider。在添加相应NuGet包(“Microsoft.Extensions.Logging.Console”)之后,我们定义了如下一个Startup类型,它采用构造函数注入的方式得到这个LoggerFactory并调用扩展方法AddConsole实现了对ConsoleLoggerProvider的注册。

   1: public class Startup

   2: {

   3:     public Startup(ILoggerFactory loggerFactory)

   4:     {

   5:         loggerFactory.AddConsole();

   6:     }

   7:  

   8:     public void Configure(IApplicationBuilder app)

   9:     {

  10:         app.Run(context => context.Response.WriteAsync("Hello World!"));

  11:     }

  12: }

我们启动这个控制台应用让它开始利用KestrelServer在默认的端口(5000)进行请求监听,然后利用浏览器向对应的地址(我们将目标地址设定为“http://localhost:5000/helloworld”)发送请求,控制台上将会输出管道在请求处理过程中写入的日志消息。如下所示的两条等级为Information的日志就是在开始和完成请求时分别被HostingApplication的CreateContext和DisposeContext方法写入的。第一条日志包含不仅仅包含请求的目标地址,还包括请求采用的协议(HTTP/1.1)和HTTP方法(GET),第二条则反映了整个请求处理过程所花的时间。

上面演示的时候请求被正常处理的情况下管道自身记录的日志,如果在处理过程中抛出异常,该异常会作为参数传递给HostingApplication的DisposeContext方法,后者会额外写入一条等级为Error的日志记录发生的错误。下面的代码片段展现了出现异常情况下写入的三条日志。

针对请求的日志上下文范围

为了查看HostingApplication在CreateContext方法针对当前请求创建的日志上下文范围,我们在为LoggerFactory注册ConsoleLoggerProvider的时候需要显式开始针对日志上下文范围的支持,所以我们在调用AddConsole方法的时候将true作为额外的参数。除此之外,我们在Configure方法中利用注入的LoggerFactory创建相应的Logger,并利用它记录一条等级为Information的日志,日志内容为“Write \"Hello World!\"”。

   1: public class Startup

   2: {

   3:     public Startup(ILoggerFactory loggerFactory)

   4:     {

   5:         loggerFactory.AddConsole(true);

   6:     }

   7:  

   8:     public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

   9:     {

  10:         app.Run(context =>

  11:         {

  12:             loggerFactory.CreateLogger("App").LogInformation("Write \"Hello World!\"");

  13:             return context.Response.WriteAsync("Hello World!");

  14:         });

  15:     }

  16: }

程序启动后我们采用浏览器向相同的目标地址(“http://localhost:5000/helloworld”)发送两次请求。对于这两次请求记录的日志,它们分别是在不同的日志上下文中被写入的,我们可以根据这个上下文范围对记录下来的日志消息进行有效地分组。针对这两次请求,服务端一共有如下6条日志消息被记录下来,针对同一请求的三条日志具有相同的上下文范围信息,该体现不仅仅包含请求的路径(“/helloworld”),还具有一个唯一标识请求的ID。

请求唯一标识的生成

日志上下文范围携带的用于唯一标识当前请求的ID,同时也可以视为当前HttpContext的唯一标识,它对应着HttpContext的TranceIdentifier属性。对于DefaultHttpContext来说,针对这个属性的读写是借助一个名为HttpRequestIdentifierFeature的特性实现的,下面的代码提供了该对象对应的接口IHttpRequestIdentifierFeature和默认实现类HttpRequestIdentifierFeature的定义。

   1: public abstract class HttpContext

   2: {

   3:     //省略其他成员

   4:     public abstract string TraceIdentifier { get; set; }

   5: }

   6:  

   7: public interface IHttpRequestIdentifierFeature

   8: {

   9:     string TraceIdentifier { get; set; }

  10: }

  11:  

  12: public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature

  13: {

  14:     private string _id;

  15:     private static long _requestId = DateTime.UtcNow.Ticks;

  16:     private static unsafe string GenerateRequestId(long id);

  17:     public string TraceIdentifier

  18:     {

  19:         get { return _id??(id= GenerateRequestId(Interlocked.Increment(ref _requestId)));}

  20:         set { this._id = value; }

  21:     }

  22: }

HttpRequestIdentifierFeature生成TraceIdentifier的逻辑很简单。如上面的代码片断所示,它具有一个静态长整型字段_requestId,其初始值为当前时间戳。对于某个具体的HttpRequestIdentifierFeature对象来说,它的TraceIdentifier属性的默认值返回的是这个字段_requestId加1之后转换的字符串。具体的转换逻辑定义在GenerateRequestId方法中,它会采用相应的算法 将指定的整数转换一个长度为13的字符串(比如“0HKSDQNPC0424”)。

ASP.NET Core真实管道详解[2]:Server是如何完成针对请求的监听、接收与响应的【上】的更多相关文章

  1. ASP.NET Core真实管道详解[1]:中间件是个什么东西?

    ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 <ASP.NET Core管道深度剖析[共4篇]> 中围绕着一个经过极度简化的模拟 ...

  2. ASP.NET Core真实管道详解[1]

    ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 <ASP.NET Core管道深度剖析[共4篇]> 中围绕着一个经过极度简化的模拟 ...

  3. 【转】asp.net core环境变量详解

    asp.net core环境变量详解 环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的环境 ...

  4. ASP.NET Core 中 HttpContext 详解与使用 | Microsoft.AspNetCore.Http 详解 (转载)

    “传导体” HttpContext 要理解 HttpContext 是干嘛的,首先,看图 图一 内网访问程序 图二 反向代理访问程序 ASP.NET Core 程序中,Kestrel 是一个基于 li ...

  5. asp.net core环境变量详解

    环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的环境变量在其它文件里面,不多说了,有兴趣的 ...

  6. ASP.NET Core 中 HttpContext 详解与使用 | Microsoft.AspNetCore.Http 详解

    笔者没有学 ASP.NET,直接学 ASP.NET Core ,学完 ASP.NET Core MVC 基础后,开始学习 ASP.NET Core 的运行原理.发现应用程序有一个非常主要的 “传导体” ...

  7. Asp.net页面生命周期详解任我行(3)-服务器处理请求详细过程

    前言 百度了一下才知道,传智的邹老师桃李满天下呀,我也是邹老师的粉丝,最开始学习页面生命周期的时候也是看了邹老师的视频. 本人是参考了以下前辈的作品,本文中也参合了本人心得,绝非有意盗版,旨在传播,最 ...

  8. net core体系-web应用程序-4net core2.0大白话带你入门-5asp.net core环境变量详解

    asp.net core环境变量详解   环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的 ...

  9. 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server

    我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer& ...

随机推荐

  1. 谈谈DOMContentLoaded:Javascript中的domReady引入机制

    一.扯淡部分 回想当年,在摆脱写页面时js全靠从各种DEMO中copy出来然后东拼西凑的幽暗岁月之后,毅然决然地打算放弃这种处处“拿来主义”的不正之风,然后开启通往高大上的“前端攻城狮”的飞升之旅.想 ...

  2. 继电器是如何成为CPU的(1)

    继电器是如何成为CPU的(1) ——<穿越计算机的迷雾>整理和总结 究竟是如何设计的电路,具有计算和控制的智力? 这一点也不高深.本系列文章从初中学的最简单的电路图说起,看看能不能从最初的 ...

  3. pt-table-checksum

    pt-table-checksum是percona公司提供的一个用于在线比对主从数据一致性的工具. 实现原理 将一张大表分成多个chunk,每次针对一个chunk进行校验,同时将校验的结果通过REPL ...

  4. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  5. CRL快速开发框架系列教程六(分布式缓存解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  6. 高效而稳定的企业级.NET Office 组件Spire(.NET组件介绍之二)

    在项目开发中,尤其是企业的业务系统中,对文档的操作是非常多的,有时几乎给人一种错觉的是”这个系统似乎就是专门操作文档的“.毕竟现在的很多办公中大都是在PC端操作文档等软件,在这些庞大而繁重的业务中,单 ...

  7. Spring的数据库开发

                                Spring JDBC框架操作mysql数据库 Spring中的JDBC为我们省去连接和关闭数据库的代码,我们着重关注对数据库的操作.Sprin ...

  8. celery使用的一些小坑和技巧(非从无到有的过程)

    纯粹是记录一下自己在刚开始使用的时候遇到的一些坑,以及自己是怎样通过配合redis来解决问题的.文章分为三个部分,一是怎样跑起来,并且怎样监控相关的队列和任务:二是遇到的几个坑:三是给一些自己配合re ...

  9. 15个C++项目列表

    实验楼上有很多C++的实战项目,从简单到进阶,学习每个项目都可以掌握相应的知识点. 如果你还是C++新手的话,那么这个C++的项目列表你可以拿去练手实战开发,毕竟学编程动手实践是少不了的! 如果你不知 ...

  10. 敏捷转型历程 - Sprint3 Grooming

    我: Tech Leader 团队:团队成员分布在两个城市,我所在的城市包括我有4个成员,另外一个城市包括SM有7个成员.另外由于我们的BA离职了,我暂代IT 的PO 职位.PM和我在一个城市,但他不 ...