我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但是就具体的实现来说,由于其中涉及很多对象的交互,我想很少人能够地把它弄清楚。为了让读者朋友们能够更加容易地理解管道处理HTTP请求的总体流程,我们根据真实管道的实现原理再造了一个“模拟管道”并在此管道上开发了一个发布图片的应用,这篇文章旨在为你讲述管道是如何处理HTTP请求的

目录
一、HttpApplication
    FeatureCollection
    HostingApplication
二、HttpContext
    DefaultHttpContext
    HostingApplication
    小结
三、服务器
    HttpListenerServer
    ServerFactory
    小结

一、HttpApplication

ASP.NET Core请求处理管道由一个服务器和一组有序排列的中间件组合而成。我们可以在这基础上作进一步个抽象,将后者抽象成一个HttpApplication对象,那么该管道就成了一个Server和HttpApplication的综合体(如图5所示)。Server会将接收到的HTTP请求转发给HttpApplication对象,后者会针对当前请求创建一个上下文,并在此上下文中处理请求,请求处理完成并完成响应之后HttpApplication会对此上下文实施回收释放处理。

我们通过具有如下定义的IHttpApplication<TContext>来表示上述的这个HttpApplication,泛型参数TContext代表这个泛化的上下文类型。一个HttpApplication对象在接收到Server转发的请求之后需要完成三项基本的操作,即创建上下文、在上下文中处理请求以及请求处理完成之后释放上下文,这三个基本操作搞好通过对应的三个方法来完成。

   1: public interface IHttpApplication<TContext>

   2: {

   3:     TContext CreateContext(IFeatureCollection contextFeatures); 

   4:     Task ProcessRequestAsync(TContext context);

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

   6: }

FeatureCollection

用于创建上下文的CreateContext方法具有一个类型为IFeatureCollection接口的参数。顾名思义,这个接口用于描述某个对象所具有的一组特性,我们可以将它视为一个字典,它Vaue代表特性描述对象,Key则表示该对象的注册类型(可以是特性描述对象的真实类型或者真实类型的基类或者实现的接口)。我们可以调用Get方法根据类型得到设置的特性描述对象,后者的设置则通过Set方法来完成。FeatureCollection类型采用最简单的方式实现了这个接口。

   1: public interface IFeatureCollection

   2: {

   3:     TFeature Get<TFeature>();

   4:     void Set<TFeature>(TFeature instance);

   5: }

   6:  

   7: public class FeatureCollection : IFeatureCollection

   8: {

   9:     private ConcurrentDictionary<Type, object> features = new ConcurrentDictionary<Type, object>();

  10:  

  11:     public TFeature Get<TFeature>()

  12:     {

  13:         object feature;

  14:         return features.TryGetValue(typeof(TFeature), out feature) 

  15:             ? (TFeature)feature 

  16:             : default(TFeature);

  17:     }

  18:  

  19:     public void Set<TFeature>(TFeature instance)

  20:     {

  21:         features[typeof(TFeature)] = instance;

  22:     }

  23: }

HostingApplication

管道模式采用的HttpApplication是一个类型为 HostingApplication的对象。如下面的代码片段所示,这个类型实现了接口IHttpApplication<Context>,泛型参数Context是一个针对当前请求的上下文对象。一个Context是对一个HttpContext的封装,后者是真正描述当前HTTP请求的上下文。除此之外,Context还具有Scope和StartTimestamp两个属性,两者与日志记录和事件追踪有关,前者被用来将针对同一请求的多次日志记录关联到同一个上下文范围(即Logger的BeginScope方法的返回值),后者表示开始处理请求的时间戳,如果在完成请求处理的时候记录下当前的时间戳,我们就可以计算出整个请求处理所花费的时间。

   1: public class HostingApplication : IHttpApplication<Context>

   2: {

   3:     //省略成员定义

   4: }

   5:  

   6: public class Context

   7: {

   8:     public HttpContext     HttpContext { get; set; }

   9:     public IDisposable     Scope { get; set; }

  10:     public long            StartTimestamp { get; set; }

  11: }

右图所示的UML体现了与HttpApplication相关的核心接口/类型之间的关系。总得来说,通过泛型接口IHttpApplication<TContext>表示HttpApplication是对注册的中间件的封装。HttpApplication在一个自行创建的上下文中完成对服务器接收请求的处理,而上下文根据表述原始HTTP上下文的特性集合来创建,这个特性集合通过接口IFeatureCollection来表示,FeatureCollection是该接口的默认实现者。ASP.NET Core 默认使用的HttpApplication是一个HostingApplication对象,它创建的上下文类型为Context,一个Context对象是对一个HttpContext和其他与日志相关上下文信息的封装。

二、HttpContext

用来描述当前HTTP请求的上下文的HttpContext对于ASP .NET Core请求处理管道来说是一个非常重要的对象,我们不仅仅可以利用它获取当前请求的所有细节,还可以直接利用它完成对请求的响应。HttpContext是一个抽象类,很多用于描述当前HTTP请求的上下文信息的属性被定义其中,对于这个模拟管道来说,我们仅仅保留了两个核心的属性,即表示请求和响应的Requst和Response属性。

   1: public abstract class HttpContext

   2: {

   3:     public abstract HttpRequest     Request { get; }

   4:     public abstract HttpResponse    Response { get; }

   5: }

表示请求和响应的HttpRequest和HttpResponse同样是两个抽象类,简单起见,我们仅仅保留少数几个与演示实例相关的属性成员。如下面的代码片段所示,仅仅为HttpRequest保留了表示当前请求地址的Url属性。对于HttpResponse来说,我们保留了三个分别表示输出流(OutputStream)、媒体类型(ContentType)和响应状态码(StatusCode)。

   1: public abstract class HttpRequest

   2: {

   3:     public abstract Uri Url { get; }

   4: }

   5:  

   6: public abstract class HttpResponse

   7: {

   8:     public abstract Stream     OutputStream { get; }

   9:     public abstract string     ContentType { get; set; }

  10:     public abstract int        StatusCode { get; set; }

  11:  

  12:     public void WriteFile(string fileName, string contentType)

  13:     {

  14:         if (File.Exists(fileName))

  15:         {

  16:             byte[] content         = File.ReadAllBytes(fileName);

  17:             this.ContentType       = contentType;

  18:             this.OutputStream.Write(content, 0, content.Length);

  19:         }

  20:         this.StatusCode = 404;

  21:     }

  22: }

DefaultHttpContext

ASP.NET Core默认使用的HttpContext是一个类型为DefaultHttpContext对象,在介绍DefaultContext的实现原理之前,我们必须了解这个一个事实:请求的接收者和最终响应者是服务器,一般来说服务器接收到请求之后会创建自己的上下文来描述当前请求,针对请求的相应也通过这个原始上下文来完成。在应用中不仅统一使用这个DefaultHttpContext对象来获取请求信息,同时还利用它来完成对请求的响应,所以它必然与服务器创建的原始上下文存在某个关联,这种关联是通过上面我们提到过的这个FeatureCollection对象来实现的。

如右图所示,不同类型的服务器在接收到请求的时候会创建一个原始的上下文,然后它会将操作原始上下文的操作封装成一系列标准的特性对象(特性类型实现统一的接口)。这些特性对象最终服务器被组装成一个FeatureCollection对象,应用程序中使用的DefaultHttpContext就是根据它创建的。当我们调用DefaultHttpContext相应的属性和方法时,在它的内部实际上借助封装的特性对象去操作原始的上下文。

一旦了解DefaultHttpContext是如何操作原始HTTP上下文之后,对于DefaultHttpContext的定义就很好理解了。如下面的代码片断所示,DefaultHttpContext具有一个IFeatureCollection类型的属性HttpContextFeatures,它表示的正是由服务器创建的用于封装原始HTTP上下文相关特性的FeatureCollection对象。通过构造函数的定义我们知道对于一个DefaultHttpContext对象来说,表示请求和响应的分别是一个DefaultHttpRequst和HttpResponse对象。

   1: public class DefaultHttpContext : HttpContext

   2: { 

   3:    public IFeatureCollection HttpContextFeatures { get;}

   4:  

   5:     public DefaultHttpContext(IFeatureCollection httpContextFeatures)

   6:     {

   7:         this.HttpContextFeatures = httpContextFeatures;

   8:         this.Request             = new DefaultHttpRequest(this);

   9:         this.Response            = new DefaultHttpResponse(this);

  10:     }   

  11:     public override HttpRequest     Request { get; }

  12:     public override HttpResponse    Response { get; }

  13: }

封装各种原始HTTP上下文的特性能够统一被DefaultHttpContext所用,它们的类型需要实现统一的接口,在这里我们定义了如下两个针对请求和响应的特性接口IHttpRequestFeature和IHttpResponseFeature,它们与HttpRequest和HttpResponse具有类似的成员定义。

   1: public interface IHttpRequestFeature

   2: {

   3:     Uri Url { get; }

   4: }

   5:  

   6: public interface IHttpResponseFeature

   7: {

   8:     Stream     OutputStream { get; }

   9:     string     ContentType { get; set; }

  10:     int        StatusCode { get; set; }

  11: }

实际上DefaultHttpContext对象中表示请求和响应的DefaultHttpRequest和DefaultHttpResponse对象就是分别根据从提供的FeatureCollection中获取的HttpRequestFeature和HttpResponseFeature对象创建的,具体的实现如下面的代码片断所示。

   1: public class DefaultHttpRequest : HttpRequest

   2: {

   3:     public IHttpRequestFeature RequestFeature { get; }

   4:     public DefaultHttpRequest(DefaultHttpContext context)

   5:     {

   6:         this.RequestFeature = context.HttpContextFeatures.Get<IHttpRequestFeature>();

   7:     }

   8:     public override Uri Url

   9:     {

  10:         get { return this.RequestFeature.Url; }

  11:     }

  12: }

  13:  

  14: public class DefaultHttpResponse : HttpResponse

  15: {

  16:     public IHttpResponseFeature ResponseFeature { get; }

  17:  

  18:     public override Stream OutputStream

  19:     {

  20:         get { return this.ResponseFeature.OutputStream; }

  21:     }

  22:  

  23:     public override string ContentType

  24:     {

  25:         get { return this.ResponseFeature.ContentType; }

  26:         set { this.ResponseFeature.ContentType = value; }

  27:     }

  28:  

  29:     public override int StatusCode

  30:     {

  31:         get { return this.ResponseFeature.StatusCode; }

  32:         set { this.ResponseFeature.StatusCode = value; }

  33:     }

  34:  

  35:     public DefaultHttpResponse(DefaultHttpContext context)

  36:     {

  37:         this.ResponseFeature = context.HttpContextFeatures.Get<IHttpResponseFeature>();

  38:     }

  39: }

HostingApplication

在了解了DefaultHttpContext的实现原理之后,我们在回头看看上面作为默认HttpApplication类型的HostingApplication的定义。由于对请求的处理总是在一个由HttpContext对象表示的上下文中进行,所以针对请求的处理最终可以通过具有如下定义的RequestDelegate委托对象来完成。一个HttpApplication对象可以视为对一组中间件的封装,它对请求的处理工作最终交给这些中间件来完成,所有中间件对请求的处理最终可以转换成通过属性Application表示的RequestDelegate对象。

   1: public class HostingApplication : IHttpApplication<Context>

   2: {

   3:     public RequestDelegate Application { get; }

   4:  

   5:     public HostingApplication(RequestDelegate application)

   6:     {

   7:         this.Application = application;

   8:     }

   9:  

  10:     public Context CreateContext(IFeatureCollection contextFeatures)

  11:     {

  12:         HttpContext httpContext = new DefaultHttpContext(contextFeatures);

  13:         return new Context

  14:         {

  15:             HttpContext     = httpContext,

  16:             StartTimestamp     = Stopwatch.GetTimestamp()

  17:         };

  18:     }

  19:  

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

  21:        => context.Scope?.Dispose();

  22:  

  23:     public Task ProcessRequestAsync(Context context) 

  24:        => this.Application(context.HttpContext);

  25: }

当我们创建一个HostingApplication对象的时候,需要将所有注册的中间件转换成一个RequestDelegate类型的委托对象,并将其作为构造函数的参数,ProcessRequestAsync方法会直接利用这个委托对象来处理请求。当CreateContext方法被执行的时候,它会直接利用封装原始HTTP上下文的FeatureCollection对象创建一个DefaultHttpContext对象,进而创建返回的Context对象。在简化的DisposeContext方法中,我们只是调用了Context对象的Scope属性的Dispose方法(如果Scope存在),实际上我们在创建Context的时候并没有Scope属性进行初始化。

小结

我们依然通过一个UML对表示HTTP上下文相关的接口/类型及其相互关系进行总结。如右图所示,针对当前请求的HTTP上下文通过抽象类HttpContext表示,请求和响应是HttpContext表述的两个最为核心的上下文请求,它们分别通过抽象类HttpRequest和HttpResponse表示。ASP.NET Core 默认采用的HttpContext类型为DefaultHttpContext,它描述的请求和响应分别是一个DefaultHttpRequst和DefaultHttpResponse对象。一个DefaultHttpContext对象由描述原始HTTP上下文的特性集合来创建,其中描述请求与相应的特性分别通过接口IHttpRequestFeature和IHttpResponseFeature表示,DefaultHttpRequst和DefaultHttpResponse正是分别根据它们创建的。

三、服务器

管道中的服务器通过接口IServer表示,在模拟管道对应的应用编程接口中,我们只保留其核心的方法Start。顾名思义,Start方法被执行的时候,服务会马上开始实施监听工作。HTTP请求一旦抵达,该方法会利用作为参数的HttpApplication对象创建一个上下文,并在此上下文中完成对请求的所有处理操作。当完成了对请求的处理任务之后,HttpApplication对象会自行负责回收释放由它创建的上下文。

   1: public interface IServer

   2: {

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

   4: }

HttpListenerServer

在我们演示的发布图片应用中使用的服务器是一个类型为HttpListenerServer的服务器。顾名思义,这个简单的服务器直接利用HttpListener来完成对请求的监听、接收和响应工作。如下面的代码片断所示,我们创建一个HttpListenerServer对象时需要为HttpListener指定一个监听地址前缀,如果没有指定会自动使用默认的地址(“http://localhost:3721/”)。

   1: public class HttpListenerServer : IServer

   2: {

   3:     public HttpListener Listener { get; }

   4:  

   5:     public HttpListenerServer(string url)

   6:     {

   7:         this.Listener = new HttpListener();

   8:         this.Listener.Prefixes.Add(url ?? "http://localhost:3721/");

   9:     }

  10:  

  11:     public void Start<TContext>(IHttpApplication<TContext> application)

  12:     {

  13:         this.Listener.Start();

  14:         while (true)

  15:         {

  16:             HttpListenerContext httpListenerContext = this.Listener.GetContext();

  17:  

  18:             HttpListenerContextFeature feature = new HttpListenerContextFeature(httpListenerContext);

  19:             FeatureCollection contextFeatures = new FeatureCollection();

  20:             contextFeatures.Set<IHttpRequestFeature>(feature);

  21:             contextFeatures.Set<IHttpResponseFeature>(feature);

  22:             TContext context = application.CreateContext(contextFeatures);

  23:  

  24:             application.ProcessRequestAsync(context)

  25:                 .ContinueWith(_ => httpListenerContext.Response.Close())

  26:                 .ContinueWith(_ => application.DisposeContext(context, _.Exception));

  27:         }

  28:     }

  29: }

在Start方法中,我们调用HttpListener的Start方法开始监听来自网络的HTTP请求。HTTP请求一旦抵达,表示原始上下文的HttpListenerContext对象通过调用HttpListener的GetContext方法返回。我们创建了一个表述这个原始上下文相关特性的HttpListenerContextFeature对象,并将它分别针对类型IHttpRequestFeature和IHttpResponseFeature添加到创建的FeatureCollection对象上。作为参数的HttpApplication对象将它作为参数调用CreateContext方法创建上下文,并调用ProcessRequestAsync方法在这个上下文中处理当前请求。当所有的请求处理工作结束之后,我们会调用HttpApplication对象的DisposeContext方法回收释放这个上下文。

ServerFactory

当WebHost在创建管道的时候并不会直接创建服务器对象,服务器对象是通过它的工厂ServerFactory创建的。ServerFactory是对所有实现了IServerFactory接口的所有类型及其对象的统称,我们在模拟管道中对这个对象作了如下的简化,除去了创建服务器的CreateServer方法的参数。作为HttpListenerServer的工厂类,HttpListenerServerFactory直接利用构造函数中指定的监听地址创建了在CreateServer方法中返回的HttpListenerServer对象。

   1: public interface IServerFactory

   2: {

   3:     IServer CreateServer();

   4: }

   5:  

   6: public class HttpListenerServerFactory : IServerFactory

   7: {

   8:     private string listenUrl;

   9:  

  10:     public HttpListenerServerFactory(string listenUrl = null)

  11:     {

  12:         this.listenUrl = listenUrl?? "http://localhost:3721/";

  13:     }

  14:  

  15:     public IServer CreateServer()

  16:     {

  17:         return new HttpListenerServer(listenUrl);

  18:     }

  19: }

小结

右图所示的UML体现了与服务器相关的接口/类型之间的关系。通过接口IServer表示的服务器表示管道中完成请求监听、接收与相应的组件,我们自定义的HttpListenerServer利用一个HttpListener实现了这三项基本操作。当HttpListenerServer接收到抵达的HTTP请求之后,它会将表示原始HTTP上下文的特性封装成一个HttpListenerContextFeature对象,HttpListenerContextFeature实现了分别用于描述请求和响应特性的接口IHttpRequestFeature和IHttpResponseFeature,HostingApplication可以利用这个HttpListenerContextFeature对象来创建DefaultHttpContext对象。


一、采用管道处理HTTP请求
二、创建一个“迷你版”的管道来模拟真实管道请求处理流程
三、管道如何处理HTTP请求的
四、管道是如何被创建出来的

ASP.NET Core管道深度剖析(3):管道是如何处理HTTP请求的?的更多相关文章

  1. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  2. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  3. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  4. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  5. ASP.NET Core管道深度剖析[共4篇]

    之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...

  6. ASP.NET Core管道深度剖析(4):管道是如何建立起来的?

    在<管道是如何处理HTTP请求的?>中,我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.这样一 ...

  7. ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求

    之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...

  8. ASP.NET Core管道深度剖析

    ASP.NET管道 以IIS 6.0为例,在工作进程w3wp.exe中,利用Aspnet_ispai.dll加载.NET运行时(如果.NET运行时尚未加载).IIS 6引入了应用程序池的概念,一个工作 ...

  9. ASP.NET Core框架深度学习(二) 管道对象

    4.HttpContext 第一个对象 我们的ASP.NET Core Mini由7个核心对象构建而成.第一个就是大家非常熟悉的HttpContext对象,它可以说是ASP.NET Core应用开发中 ...

随机推荐

  1. 对抗密码破解 —— Web 前端慢 Hash

    (更新:https://www.cnblogs.com/index-html/p/frontend_kdf.html ) 0x00 前言 天下武功,唯快不破.但在密码学中则不同.算法越快,越容易破. ...

  2. ASP.NET MVC5+EF6+EasyUI 后台管理系统(75)-微信公众平台开发-用户管理

    系列目录 前言 本节主要是关注者(即用户)和用户组的管理,微信公众号提供了用户和用户组的管理,我们可以在微信公众号官方里面进行操作,添加备注和标签,以及移动用户组别,同时,微信公众号也提供了相应的接口 ...

  3. Golang 编写的图片压缩程序,质量、尺寸压缩,批量、单张压缩

    目录: 前序 效果图 简介 全部代码 前序: 接触 golang 不久,一直是边学边做,边总结,深深感到这门语言的魅力,等下要跟大家分享是最近项目 服务端 用到的图片压缩程序,我单独分离了出来,做成了 ...

  4. MySQL 系列(四)主从复制、备份恢复方案生产环境实战

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 第三篇:MySQL 系列(三)你不知道的 视图.触发器.存储过程.函数 ...

  5. 【手记】注意BinaryWriter写string的小坑——会在string前加上长度前缀length-prefixed

    之前以为BinaryWriter写string会严格按构造时指定的编码(不指定则是无BOM的UTF8)写入string的二进制,如下面的代码: //将字符串"a"写入流,再拿到流的 ...

  6. 修改session垃圾回收几率

    <?php //修改session垃圾回收几率 ini_set('session.gc_probability','1'); ini_set('session.gc_divisor','2'); ...

  7. 根据ip判断返回城市名称查询当地天气

    <?phpheader("content-type:text/html;charset=utf-8");date_default_timezone_set("Asi ...

  8. 如何区别数据库删除语句drop与delete与truncate?

    1.delete:删除数据表中的行(可以删除某一行,也可以在不删除数据表的情况下删除所有行) 删除某一行:delete from 数据表名称 where 列名称=值: 删除所有行:delete*fro ...

  9. 超详细mysql left join,right join,inner join用法分析

    下面是例子分析表A记录如下: aID        aNum 1           a20050111 2           a20050112 3           a20050113 4   ...

  10. MMORPG大型游戏设计与开发(攻击区域 扇形)

    距离上次发布已经有了很长一段时间,期间由于各种原因没有更新这方面的技术分享,在这里深表遗憾.在MMO或其他的游戏中,会有针对各种形状的计算,通常在攻击区域里不会很复杂,常见的为矩形.圆形.扇形.今天分 ...