Server是如何完成针对请求的监听、接收与响应1

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-08-09 21:57:32

Server是如何完成针对请求的监听、接收与响应1的相关文章

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

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

Android 开发事件响应之基于监听的事件响应

Android 开发事件响应之基于监听的事件响应 本文将介绍Android 操作系统如何通过监听来实现对事件的响应. Android 开发事件响应之基于监听的事件响应 背景介绍 Android 开发事件响应类型 内部类 匿名内部类 外部类 直接绑定标签 总结 背景介绍 对于任何可视化开发来说,都会涉及到对控件的响应.我们通过举例:实现对Button 按钮的点击来讲解Android 里面对事件相应的办法. Android 开发事件响应类型 在Android 开发中,有两种方式可以对事件作出响应,分

js监听rem实现响应式

原文链接:http://caibaojian.com/web-app-rem.html (function (doc, win) { var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function () { var clientWidth = docEl.clientWidth; if (!clientWid

Android studio之广播监听接收短信

一. 在清单文件中(AndroidManifest.xml)添加短信权限 这里我用的android studio版本是3.3的 <uses-permission android:name="android.permission.READ_SMS" /><uses-permission android:name="android.permission.RECEIVE_SMS" /><uses-permission android:name

[Java聊天室server]实战之二 监听类

前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识,但学习之前,更想和广大程序猿分享的是一种心境:学习是一个循序渐进的过程,心态应该随时调节,保持戒骄戒躁的状态.比方近期在看网易公开课MIT<算法导论>,老师提到,学习算法之前要计算机数学+离散数学+概率论等课程的知识,所以一直学不好算法的程序猿最好还是从基础入手,这都是中国式教育惹的祸啊!(此处省略一万字...

简述Java中Http/Https请求监听方法

一.工欲善其事必先利其器 做Web开发的人总免不了与Http/Https请求打交道,很多时候我们都希望能够直观的的看到我们发送的请求参数和服务器返回的响应信息,这个时候就需要借助于某些工具啦.本文将采用Fiddler2作为分析工具,Fiddler很强大,它能记录所有客户端和服务器的http和https请求,允许你监视,设置断点,甚至修改输入输出数据,是越墙抓包之利器.关于工具的介绍可以参考下面的链接: http://www.cnblogs.com/TankXiao/archive/2012/02

监听服务管理(转)

如果你想管理(启动.关闭等)ORACLE数据库的监听服务,在Windows/Linux平台,你都可以通过lsnrctl命令来实现,下面 我们主要讲一下如何用lsnrctl命令来管理监听服务(我在两台数据库(不同平台.不同版本)分别操作,暂且叫为服务器A. 服务器B): 1:查看服务监听命令的详细帮助信息 服务器A:Red Hat Enterprise Linux Server release 6.0 (Santiago) ORACLE 10 [[email protected] ~]$ lsnr

oracle 监听启动、停止、查看命令

1.su oracle 然后启动监听器 1.lsnrctl start  会看到启动成功的界面; 1.lsnrctl stop  停止监听器命令. 1.lsnrctl status  查看监听器命令. oracle数据库的安全包含两部分: 1.一部分是os的安全2.网络的安全3.oracle软件本身的安全 os的安全依赖相应的操作系统及管理员的技术水平,我们这里只谈谈oracle的安全 我了解的oracle软件的安全包含: 1.更改oracle的默认监听端口号 2.给监听器加密码 3.oracl

用nodejs搭建一个简单的服务监听程序

作为一个从业三年左右的,并且从事过半年左右PHP开发工作的前端,对于后台,尤其是对以js语言进行开发的nodejs,那是比较有兴趣的,虽然本身并没有接触过相关的工作,只是自己私下做的一下小实验,但是还是记录一下方便以后复习! 今天主要记录一下,很久以前用nodejs制作一个简单的服务监听程序的一些过程! 大家都知道,通过nodejs可以对前台请求进行监听,这里就放一个官网的hello world例子吧: var http = require('http'); http.createServer(