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

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”)。

时间: 2024-10-25 14:39:04

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

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

ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 <ASP.NET Core管道深度剖析[共4篇]> 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程.在这个系列 中,我们会还原构建模拟管道时刻意舍弃和改写的部分,想读者朋友们呈现一个真是的HTTP请求处理管道. ASP.NET Core 的请求处理管道由一个Server和一组有序排列的中间件构成,前者仅仅完成基本的请求监听.接收和响应的工作,请求接收之

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

ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 <ASP.NET Core管道深度剖析[共4篇]> 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程.在这个系列 中,我们会还原构建模拟管道时刻意舍弃和改写的部分,想读者朋友们呈现一个真是的HTTP请求处理管道. ASP.NET Core 的请求处理管道由一个Server和一组有序排列的中间件构成,前者仅仅完成基本的请求监听.接收和响应的工作,请求接收之

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

环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的环境变量在其它文件里面,不多说了,有兴趣的可以网上查下. 当我们的应用程序发布到生产环境时,如果用到了环境变量,就需要对服务器操作系统的环境变量进行设置.这里只是进行提前说明. 设置环境变量 环境变量可以在launchSettings.json文件里面设置,也可以右键项目->属性->调试->环境变量进行设置.这两种方式的内容是同步的

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

我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer>),为了让读者朋友们对管道中的Server具有更加深刻的认识,接下来我们采用实例演示的形式创建一个自定义的Server.这个自定义的Server直接利用HttpListener来完成针对请求的监听.接收和响应,我们将其命名为HttpListenerServer.在正式介绍HttpListenerServ

asp.net 三层架构图文详解

什么是三层架构? 先说说饭店中的三层结构 服务员:待客/提交菜单 厨 师:取材/炒菜/交菜 采购员:采购 三层结构分析 (1)表示层 为用户提供交互式操作界面. (2)业务逻辑层 负责关键业务的处理,负责与表示层和数据访问层的数据传递. (3)数据访问层 实现对数据的保存和读取操作. 三层结构各层间的依整关系 -----高层依赖低层 三层结构各层间的数据传递关系 ---高层请求低层.低层响应高层 三层结构搭建方法 1.搭建表示层(创建一个Windows应用程序) 2.搭建业务逻辑层(类库) 3.

【转】Asp.Net MVC3 简单入门详解过滤器Filter

原文地址:http://www.cnblogs.com/boruipower/archive/2012/11/18/2775924.html 前言 在开发大项目的时候总会有相关的AOP面向切面编程的组件,而MVC(特指:Asp.Net MVC,以下皆同)项目中不想让MVC开发人员去关心和写类似身份验证,日志,异常,行为截取等这部分重复的代码,那我们可以通过AOP截取实现,而在MVC项目中我们就可以直接使用它提供的Filter的特性帮我们解决,不用自己实现复杂的AOP了. 在Asp.net Mvc

Linux core dump file详解

Linux core dump file详解 http://www.cnblogs.com/langqi250/archive/2013/03/05/2944931.html

Asp.net中GridView使用详解(引)【转】

Asp.net中GridView使用详解(引) GridView无代码分页排序 GridView选中,编辑,取消,删除 GridView正反双向排序 GridView和下拉菜单DropDownList结合 GridView和CheckBox结合 鼠标移到GridView某一行时改变该行的背景色方法一 鼠标移到GridView某一行时改变该行的背景色方法二 GridView实现删除时弹出确认对话框 GridView实现自动编号 GridView实现自定义时间货币等字符串格式 GridView实现用

asp.net的sessionState节点详解

web.config关于sessionState节点的配置方案,sessionState有四种模式:off,inProc,StateServer,SqlServer. 1.off模式 从字面上就可以看出这个是关闭模式,如果当前页面不需要session的值,为了减少服务器资源,你可以去掉Session的开销. <sessionState mode="off">或者页面上 <%@ Page EnableSessionState="false" %>