在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 《ASP.NET Core管道深度剖析[共4篇]》 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程。在这个系列 中,我们会还原构建模拟管道时刻意舍弃和改写的部分,想读者朋友们呈现一个真是的HTTP请求处理管道。 ASP.NET Core 的请求处理管道由一个Server和一组有序排列的中间件构成,前者仅仅完成基本的请求监听、接收和响应的工作,请求接收之后和响应之前的所有工作都交给注册的中间件来完成。ASP.NET Core的中间件通过一个类型Func<RequestDelegate, RequestDelegate>的委托对象来表示,而RequestDelegate也是一个委托,它代表一项请求处理任务。
当Server接受到抵达的HTTP请求之后,会构建一个描述当前请求的原始上下文,Server的类型决定了这个原始上下文的类型,比如在我们模拟管道默认采用的HttpListenerServer由于采用HttpListener来监听、接收并响应请求,所以它对应的原始上下文是一个HttpListenerContext对象。但是对于管道的后续部分,即由注册的中间件构建的链表,它们需要采用统一的方式来处理请求,所以Server最终会根据原始的上下文来创建一个抽象的HTTP上下文,后者通过抽象类HttpContext来表示。 我们不仅可以利用这个HttpContext获取描述当前请求的上下文信息,同样可以利用它来实现对响应的控制。针对当前请求的任何处理操作总是在么一个上下文中进行,所以一项请求处理任务完全可以抽象成一个类型Func<HttpContext,Task>的委托来表示,实际上具有如下定义的RequestDelegate委托具有类似的定义。 delegate Task RequestDelegate(HttpContext context);
每个中间件都承载着独立的请求处理任务,它本质上也体现了在当前HttpContext下针对请求的处理操作,那么为什么中间件不直接通过一个RequestDelegate对象来表示,而是表示为一个类型为Func<RequestDelegate, RequestDelegate>的委托对象呢?原因很简单,中间件并不孤立地存在,所有注册的中间件最终会根据注册的先后顺序组成一个链表,每个中间件不仅仅需要完成各自的请求处理任务外,还需要驱动链表中的下一个中间件。 如右图所示,对于一个由多个Func<RequestDelegate, RequestDelegate>对象组成的中间链表来说,某个中间件会将后一个Func<RequestDelegate, RequestDelegate>对象的返回值作为输入,而自身的返回值则作为前一个中间件的输入。某个中间件执行之后返回的RequestDelegate对象不仅仅体现了自身对请求的处理操作,而是体现了包含自己和后续中间件一次对请求的处理。那么对于第一个中间件来说,它执行后返回的RequestDelegate对象实际上体现了整个应用对请求的处理逻辑。 二、描述当前请求的上下文:HttpContext对当前上下文的抽象解除了管道对具体服务器类型的依赖, 这使我们为ASP.NET Core应用自由地选择寄宿方式,而不是像传统的ASP.NET应用一样只能寄宿在IIS之中。抽象HTTP上下文的目的是为了实现对请求处理流程的抽象,只有这样我们才能将针对请求的某项操作体现在一个标准的中间件上,有这个这个标准化的中间件才有所谓的请求处理管道。 ASP.NET Core通过具有如下所示的HttpContext类来表示这么一个抽象的HTTP上下文。对于一个HttpContext对象来说,它的核心体现在用于描述请求和响应的Request和Response属性之上。除此之外,我们还可以通过获取与当前请求相关的其他上下文信息,比如用来控制用户认证的AuthenticationManager对象和代表当前请求用户的ClaimsPrincipal对象,描述当前HTTP连接的ConnectionInfo对象和用于控制WebSocket的WebSocketManager。我们还可以获取并控制当前会话,也可以获取或者设置调试追踪的ID。 class HttpContext
2: {
3:
abstract HttpRequest Request { get; }
abstract HttpResponse Response { get; }
6:
abstract AuthenticationManager Authentication { get; }
abstract ClaimsPrincipal User { get; set; }
abstract ConnectionInfo Connection { get; }
abstract WebSocketManager WebSockets { get; }
abstract ISession Session { get; set; }
string TraceIdentifier { get; set; }
abstract CancellationToken RequestAborted { get; set; }
object> Items { get; set; }
15:
abstract IServiceProvider RequestServices { get; set; }
abstract IFeatureCollection Features { get; }
18: }
当需要中指对请求的处理时,我们可以通过为RequestAborted属性设置一个CancellationToken对象从而将终止通知发送给管道。如果需要对整个管道共享一些与当前上下文相关的数据,我们可以将它保存在通过Items属性表示的字典中。我们一再提到依赖注入被广泛地应用ASP.NET Core管道中,HttpContext的RequestServices属性返回利用应用启动时设置的服务注册信息创建的ServiceProvider,只要相应的服务被预先注册到指定的服务接口上,我们就可能利用这个ServiceProvider根据这个接口得到对应的服务对象。 class HttpRequest
2: {
abstract HttpContext HttpContext { get; }
string Method { get; set; }
string Scheme { get; set; }
bool IsHttps { get; set; }
abstract HostString Host { get; set; }
abstract PathString PathBase { get; set; }
abstract PathString Path { get; set; }
abstract QueryString QueryString { get; set; }
abstract IQueryCollection Query { get; set; }
string Protocol { get; set; }
abstract IHeaderDictionary Headers { get; } >
abstract IRequestCookieCollection Cookies { get; set; }
string ContentType { get; set; }
abstract Stream Body { get; set; }
bool HasFormContentType { get; }
abstract IFormCollection Form { get; set; }
19:
abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken);
21: }
在了解HttpContext表示请求的抽象类HttpRequest之后,我们再来认识一个与之相对的HttpResponse类型。如下面的代码片断所示,HttpResponse依然是一个抽象类,我们可以通过定义在它之上的属性和方法来控制对请求的响应。从原则上讲,我们对请求的所做的任意类型的响应都可以利用它来说实现。 class HttpResponse
2: {
abstract HttpContext HttpContext { get; }
int StatusCode { get; set; }
abstract IHeaderDictionary Headers { get; }
abstract Stream Body { get; set; }
long? ContentLength { get; set; }
abstract IResponseCookies Cookies { get; }
bool HasStarted { get; }
10:
object state);
void OnStarting(Func<Task> callback);
object state);
void RegisterForDispose(IDisposable disposable);
void OnCompleted(Func<Task> callback);
string location);
bool permanent);
18: }
当我们通过表示当前上下文的HttpContext对象得到表示响应的HttpResponse之后,我们可以不仅仅将希望的内容写入响应消息的主体,还可以设置响应状态码以及添加相应的首部。表2列出了定义在HttpResponse中的所有属性和方法所代表的含义。 封装“特性”的容器:FeatureCollectionHttpContext的另一个只读属性Features体现了HTTP上下文抽象的实现方式。Server在接收到请求之后会创建一个原始的上下文,管道不仅仅利用这个原始上下文来获取与请求相关的信息,它对请求的最终响应实际上也是通过这个原始上下文来完成的。所以对一个HttpContext对象来说,有它描述的上下文信息不仅仅来源于这个原始的上下文,我们针对HttpContext所做的任何响应操作最终都需要分发给这个原始上下文来完成, 否则是不会生效的。HttpContext和由Server创建的原始上下文之间的“双向绑定”究竟是如何实现的呢? 这个所谓的“双向绑定”即使其实很简单。当原始上下文被创建出来之后,Server会将它封装成一系列标准的特性对象,HttpContext正式针对这些特性对象而创建的。这些对象所对应的类型均实现了标准的接口,接口中不仅仅定义相应的属性来读写原始上下文中描述的信息,还定义了相应的方法来操作原始上下文。HttpContext的属性Features返回的就是这组特性对象的集合,它的返回类型为IFeatureCollection,我们将实现了该接口的类型以及对应的对象统称为FeatureCollection。 object>>
2: {
3: TFeature Get<TFeature>();
void Set<TFeature>(TFeature instance);
5:
bool IsReadOnly { get; }
this[Type key] { get; set; }
int Revision { get; }
9: }
一个FeatureCollection对象本质上就是一个Key和Value分别为Type和Object类型的字段。我们通过调用Set方法将一个特性对象针对指定的类型(一般为特性接口)注册到这个字典对象上,并通过Get方法根据注册的类型获取它,特性对象的注册和获取也可以利用定义的索引来完成。如果IsReadOnly属性返回True,我们将不能注册新的特性或者修改已经注册的特性。 整数类型的之都属性Revision可以视为整个FeatureCollection对象的版本,不论是采用何种方式注册新的特性还是修改现有的特性,这个属性的值都将改变。 具有如下定义的FeatureCollection类实现了IFeatureCollection接口,我们默认使用的FeatureCollection就是这么一个类型的对象。FeatureCollection具有两个构造函数重载,默认无参构造函数帮助我们创建一个空的特性集合,另一个构造函数则需要指定一个FeatureCollection对象来提供默认特性。对于采用第二个构造函数重载创建的 FeatureCollection对象来说,如果我们通过指定某个特性接口类型试图获取对应的特性对象时,如果对应的特性没有注册到当前FeatureCollection对象上,而是注册到提供默认特性的FeatureCollection对象上,后者将会与提供最终的特性。 class FeatureCollection : IFeatureCollection
2: {
//其他成员
public FeatureCollection();
public FeatureCollection(IFeatureCollection defaults);
6: }
对于FeatureCollection类型来说,它 的IsReadOnly总是返回False,所以它永远是可读可写的。对于调用默认无参构造函数创建的FeatureCollection对象来说,它 的Revision默认返回零。如果我们通过指定另一个FeatureCollection对象为参数调用第二个构造函数来创建一个FeatureCollection对象,前者的Revision属性值将成为后者同名属性的默认值。不论我们采用何种形式(调用Set方法或者索引)添加一个新的特性或者改变了一个已经注册的特性,FeatureCollection对象的Revision属性都将自动递增。上述的这些关于FeatureCollection的特性都体现在如下所示的代码片段中。 new FeatureCollection();
2: Debug.Assert(defaults.Revision == 0);
3:
new Foo());
5: Debug.Assert(defaults.Revision == 1);
6:
new Bar();
8: Debug.Assert(defaults.Revision == 2);
9:
new FeatureCollection(defaults);
11: Debug.Assert(features.Revision == 2);
typeof(Foo));
13:
new Baz());
15: Debug.Assert(features.Revision == 3);
HttpContext的默认实现:DefaultHttpContextASP.NET Core默认使用的HttpContext类型为DefaultHttpContext,上面我们介绍的针对描述原始上下文“特性集合”来创建HttpContext的策略就体现在这个类型之上。DefaultHttpContext具有一个如下的构造函数,作为参数的FeatureCollection对象就是这么一个特性集合。 class DefaultHttpContext : HttpContext
2: {
public DefaultHttpContext(IFeatureCollection features);
4: }
不论是对于组成管道的中间件还是建立在管道上的应用,在默认的情况下都是利用这个DefaultHttpContext对象来获取请求的相关信息,同时也是利用这个对象来控制最终发送的响应。但是DefaultHttpContext对象这个这个过程中仅仅是一个“代理”,针对它的调用(属性或者方法)最终都需要转发给由具体Server创建的那个原始上下文,在构造函数中指定的这个FeatureCollection对象所代表的特性集合成为了两者沟通的唯一渠道。对应定义在DefaultHttpContext中的所有属性,它们几乎都具有一个对应的特性,这些特性都对应着一个接口。表3列出了部分特性接口以及DefaultHttpContext对应的属性。 描述原始HTTP上下文的特性接口
对于上面列出的众多特性接口,在后续相关章节中都会涉及到,所以我们只需要了解一下两个最重要的特性接口,即表示请求和响应的IHttpRequestFeature和IHttpResponseFeature。从下面给出的代码片断我们不难看出,这两个接口的定义分别与抽象类HttpRequest和HttpResponse具有一致的定义。对于DefaultHttpContext类型来说,它的Request和Response属性分别返回的是一个DefaultHttpRequest和DefaultHttpResponse对象。DefaultHttpRequest和DefaultHttpResponse分别继承自HttpRequest和HttpResponse,它们分别利用这个两个特性实现了从基类继承下来的所有抽象成员。 interface IHttpRequestFeature
2: {
3: Stream Body { get; set; }
4: IHeaderDictionary Headers { get; set; }
string Method { get; set; }
string Path { get; set; }
string PathBase { get; set; }
string Protocol { get; set; }
string QueryString { get; set; }
string Scheme { get; set; }
11: }
12:
interface IHttpResponseFeature
14: {
15: Stream Body { get; set; }
bool HasStarted { get; }
17: IHeaderDictionary Headers { get; set; }
string ReasonPhrase { get; set; }
int StatusCode { get; set; }
20:
object state);
object state);
23: }
对于实现请求监听、接收和响应的Server来说,它们都需要通过实现上面这些特性接口来定义针对性的特性类。如下图所示,当成功接收到请求之后,Server会创建相应的特性并将它们组合成一个FeatureCollection对象,最后创建出一个DefaultHttpContext对象,我们注册的所有中间件针对这个DefaultHttpContext完成各自的请求处理工作。 上下文的创建者:HttpContextFactory在Server接收到抵达的请求时,它并不会直接利用原始的上下文去创建HttpContext对象,HttpContext在管道中的创建是间接地通过HttpContextFactory来完成的。 HttpContextFactory是对所有实现了IHttpContextFactory接口的所有类型及其对象的统称,如下面的代码片段所示,IHttpContextFactory接口除了定义创建HttpContext对象的Create方法之外,还定义了另一个方法Dispose来释放指定的HttpContext对象。HttpContextFactory类是该接口的默认实现者,由它的Create方法创建并返回的自然是一个DefaultHttpContext对象。 interface IHttpContextFactory
2: {
3: HttpContext Create(IFeatureCollection featureCollection);
void Dispose(HttpContext httpContext);
5: }
6:
class HttpContextFactory : IHttpContextFactory
8: {
//省略其他成员
public HttpContext Create(IFeatureCollection featureCollection);
void Dispose(HttpContext httpContext);
12: }
|
请发表评论