asp.net core 核心对象解析
首先声明这篇文章的所有内容均来自https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
----感谢大内老A(artech)对于.net core的贡献,我们都是受益人。
HttpContext
HttpContext是Http请求到达服务器后被服务器根据接口定义(这个接口定义实际上就是Feature层,由各种Feature转换而来的)转换而成的一个对象,它代表了当前请求的所有内容,它有两个核心的对象,一个是HttpRequest,另一个就是HttpResponse,HttpContext在已注册的各个中间件中传递、加工后形成最终的HttpResponse然后反馈给请求者。一个HttpContext类似于下面这样的结构:
public class HttpContext { public HttpRequest Request { get; } public HttpResponse Response { get; } } public class HttpRequest { public Uri Url { get; } public NameValueCollection Headers { get; } public Stream Body { get; } } public class HttpResponse { public NameValueCollection Headers { get; } public Stream Body { get; } public int StatusCode { get; set;} }
RequestDelegate
从命名上面可以看出这个是一个委托对象,这个RequestDelegate是由一连串的Func<RequestDelegate,RequestDelegate>转换而来的(在ApplicationBuilder中)。RequestDelegate在asp.net core中被用作处理Http请求,它对传入的Http上下文(httpContext)进行处理,最终返回结果,它是整个asp.net core管道的一个重要组成部分(pipeline=server+requestdelegate)
MiddleWare
中间件在asp.net core中起着举足轻重的作用,中间件在asp.net core中最终被转换成一个RequestDelegate(在ApplicationBuilder中)。那如何表示这个中间件呢?第一种方式是由一个Func<RequestDelegate,RequestDelegate>来表示,还有一种方式是一个中间件类:
public class Middleware { private readonly RequestDelegate _next; public Middleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext httpContext) { return _next(httpContext); } }
中间件在asp.net core的作用就是处理每一个传入的HttpContext,然后决定是否由下一个中间件继续处理,但是中间件是编译时的产物,在运行时,他们都被转换成唯一的一个RequestDelegate了。
ApplicationBuilder
ApplicationBuilder顾名思义是用来构建一个Application的,那么在asp.net core 的语义下面,这个Application实际上就是一个RequestDelegate。所以,ApplicationBuilder实际上就是用来构建一个ReqesutDelegate。构建所需原料便是你添加的各种中间件(Func<RequestDelegate,RequestDelegate>).
下面代表表述一个ApplicationBuilder的最核心的接口信息:
public interface IApplicationBuilder { IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); }
下面代码是一个ApplicationBuilder最核心的实现:
public class ApplicationBuilder : IApplicationBuilder { private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>(); public RequestDelegate Build() { _middlewares.Reverse(); return httpContext => { RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var middleware in _middlewares) { next = middleware(next); } return next(httpContext); }; } public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _middlewares.Add(middleware); return this; } }
Server
服务器在管道中的职责非常明确,当我们自动作应用宿主的WebHost的时候,服务它被自动启动。启动后的服务器会绑定到指定的端口进行请求监听,一旦有请求抵达,服务器会根据该请求创建出代表上下文的HttpContext对象,并将该上下文作为输入调用由所有注册中间件构建而成的RequestDelegate对象。
下面代码表示一个Server的核心接口功能:
public interface IServer { Task StartAsync(RequestDelegate handler); }
HttpContext和Server之间的适配
面向应用层的HttpContext对象是对请求和响应的封装,但是请求最初来源于服务器,针对HttpContext的任何响应操作也必需作用于当前的服务器才能真正起作用。现在问题来了,所有的ASP.NET Core应用使用的都是同一个HttpContext类型,但是却可以注册不同类型的服务器,我们必需解决两者之间的适配问题。
首先把HttpContext的转换过程总结一遍:HttpContext的转换过程是由①Server上面监听请求到达时得到一个ServerContext(具体看是什么server)②这个ServerContext上下文用于创建一个ServerFeature,ServerFeature实现了各种中间Feature,包括IRequestFeature和IResponseFeature等③得到的这个ServerFeature用于创建适配HttpContext的FeatureCollection④用FeatureCollection创建HttpContext。就是这么一个过程。这个过程实在Server中完成的,代码如下:
//位于Server的StartAsync方法 public async Task StartAsync(RequestDelegate handler) { Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url)); _httpListener.Start(); Console.WriteLine("Server started and is listening on: {0}", string.Join(';', _urls)); while (true) { var listenerContext = await _httpListener.GetContextAsync(); var feature = new HttpListenerFeature(listenerContext); var features = new FeatureCollection() .Set<IHttpRequestFeature>(feature) .Set<IHttpResponseFeature>(feature); var httpContext = new HttpContext(features); await handler(httpContext); listenerContext.Response.Close(); } }
计算机领域有一句非常经典的话:“任何问题都可以通过添加一个抽象层的方式来解决,如果解决不了,那就再加一层”。同一个HttpContext类型与不同服务器类型之间的适配问题也可可以通过添加一个抽象层来解决,我们定义在该层的对象称为Feature。如上图所示,我们可以定义一系列的Feature接口来为HttpContext提供上下文信息,其中最重要的就是提供请求的IRequestFeature和完成响应的IResponseFeature接口。那么具体的服务器只需要实现这些Feature接口就可以了。
我们接着从代码层面来看看具体的实现。如下面的代码片段所示,我们定义了一个IFeatureCollection接口来表示存放Feature对象的集合。从定义可以看出这是一个以Type和Object作为Key和Value的字典,Key代表注册Feature所采用的类型,而Value自然就代表Feature对象本身,话句话说我们提供的Feature对象最终是以对应Feature类型(一般为接口类型)进行注册的。为了编程上便利,我们定义了两个扩展方法Set<T>和Get<T>来设置和获取Feature对象。
public interface IFeatureCollection : IDictionary<Type, object> { } public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { } public static partial class Extensions { public static T Get<T>(this IFeatureCollection features) => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T); public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature) { features[typeof(T)] = feature; return features; } }
如下所示的用来提供请求和响应IHttpRequestFeature和IHttpResponseFeature接口的定义,可以看出它们具有与HttpRequest和HttpResponse完全一致的成员定义。
public interface IHttpRequestFeature { Uri Url { get; } NameValueCollection Headers { get; } Stream Body { get; } } public interface IHttpResponseFeature { int StatusCode { get; set; } NameValueCollection Headers { get; } Stream Body { get; } }
接下来我们来看看HttpContext的具体实现。简约起见在这里HttpContext只包含Request和Response两个属性成员,对应的类型分别为HttpRequest和HttpResponse,如下所示的就是这两个类型的具体实现。我们可以看出HttpRequest和HttpResponse都是通过一个IFeatureCollection对象构建而成的,它们对应的属性成员均有分别由包含在这个Feature集合中的IHttpRequestFeature和IHttpResponseFeature对象来提供的。
public class HttpRequest { private readonly IHttpRequestFeature _feature; public Uri Url => _feature.Url; public NameValueCollection Headers => _feature.Headers; public Stream Body => _feature.Body; public HttpRequest(IFeatureCollection features) => _feature = features.Get<IHttpRequestFeature>(); } public class HttpResponse { private readonly IHttpResponseFeature _feature; public NameValueCollection Headers => _feature.Headers; public Stream Body => _feature.Body; public int StatusCode { get => _feature.StatusCode; set => _feature.StatusCode = value; } public HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>(); }
HttpContext的实现就更加简单了。如下面的代码片段所示,我们在创建一个HttpContext对象是同样会提供一个IFeatureCollection对象,我们利用该对象创建对应的HttpRequest和HttpResponse对象,并作为对应的属性值。
public class HttpContext { public HttpRequest Request { get; } public HttpResponse Response { get; } public HttpContext(IFeatureCollection features) { Request = new HttpRequest(features); Response = new HttpResponse(features); } }
HttpListenerServer
在对服务器和它与HttpContext的适配原理具有清晰的认识之后,我们来尝试着自己定义一个服务器。在前面的Hello World实例中,我们利用WebHostBuilder的扩展方法UseHttpListener注册了一个HttpListenerServer,我们现在就来看看这个采用HttpListener作为监听器的服务器类型是如何实现的。
由于所有的服务器都需要将自己的Feature实现来为HttpContext提供对应的上下文信息,所以我们得先来为HttpListenerServer定义相应的接口。对HttpListener稍微了解的朋友应该知道它在接收到请求之后会创建一个自己的上下文对象,对应的类型为HttpListenerContext。如果采用HttpListenerServer作为应用的服务器,意味着HttpContext承载的上下文信息最初来源于这个HttpListenerContext,所以Feature的目的旨在解决这两个上下文之间的适配问题。
如下所示的HttpListenerFeature就是我们为HttpListenerServer定义的Feature。HttpListenerFeature同时实现了IHttpRequestFeature和IHttpResponseFeature,实现的6个属性成员最初都来源于创建该Feature对象提供的HttpListenerContext对象。
public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature { private readonly HttpListenerContext _context; public HttpListenerFeature(HttpListenerContext context) => _context = context; Uri IHttpRequestFeature.Url => _context.Request.Url; NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers; NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers; Stream IHttpRequestFeature.Body => _context.Request.InputStream; Stream IHttpResponseFeature.Body => _context.Response.OutputStream; int IHttpResponseFeature.StatusCode { get => _context.Response.StatusCode; set => _context.Response.StatusCode = value; } }
如下所示的是HttpListenerServer的最终定义。我们在构造一个HttpListenerServer对象的时候可以提供一组监听地址,如果没有提供,会采用“localhost:5000”作为默认的监听地址。在实现的StartAsync方法中,我们启动了在构造函数中创建的HttpListenerServer对象,并在一个循环中通过调用其GetContextAsync方法实现了针对请求的监听和接收。
public class HttpListenerServer : IServer { private readonly HttpListener _httpListener; private readonly string[] _urls; public HttpListenerServer(params string[] urls) { _httpListener = new HttpListener(); _urls = urls.Any()?urls: new string[] { "http://localhost:5000/"}; } public async Task StartAsync(RequestDelegate handler) { Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url)); _httpListener.Start(); while (true) { var listenerContext = await _httpListener.GetContextAsync(); var feature = new HttpListenerFeature(listenerContext); var features = new FeatureCollection() .Set<IHttpRequestFeature>(feature) .Set<IHttpResponseFeature>(feature); var httpContext = new HttpContext(features); await handler(httpContext); listenerContext.Response.Close(); } } }
当HttpListener监听到抵达的请求后,我们会得到一个HttpListenerContext对象,此时我们只需要据此创建一个HttpListenerFeature对象并它分别以IHttpRequestFeature和IHttpResponseFeature接口类型注册到创建FeatureCollection集合上。我们最终利用这个FeatureCollection对象创建出代表上下文的HttpContext,然后将它作为参数调用由所有中间件共同构建的RequestDelegate对象即可。
WebHost
WebHost可以理解为寄宿或者承载Web应用的宿主,应用的启动可以通过启动作为宿主的WebHost来实现。
一个WebHost由一个服务器和一个RequestDelegate构成的,这个RequestDelegate本身是一个委托类型,它由Func<RequestDelegate,RequestDelegate>表示的中间件(多个)转换而来。
到目前为止我们已经知道了由一个服务器和多个中间件构成的管道是如何完整针对请求的监听、接收、处理和最终响应的,接下来来讨论这样的管道是如何被构建出来的。管道是在作为应用宿主的WebHost对象启动的时候被构建出来的,在ASP.NET Core Mini中,我们将表示应用宿主的IWebHost接口简写成如下的形式:只包含一个StartAsync方法用来启动应用程序。
public interface IWebHost { Task StartAsync(); }
由于由WebHost构建的管道由Server和HttpHandler构成,我们在默认实现的WebHost类型中,我们直接提供者两个对象。在实现的StartAsync方法中,我么只需要将后者作为参数调用前者的StartAsync方法将服务器启动就可以了。
public class WebHost : IWebHost { private readonly IServer _server; private readonly RequestDelegate _handler; public WebHost(IServer server, RequestDelegate handler) { _server = server; _handler = handler; } public Task StartAsync() => _server.StartAsync(_handler); }
WebHostBuilder
作为最后一个着重介绍的核心对象,WebHostBuilder的使命非常明确:就是创建作为应用宿主的WebHost。由于在创建WebHost的时候需要提供注册的服务器和由所有注册中间件构建而成的RequestDelegate,所以在对应接口IWebHostBuilder中,我们为它定义了三个核心方法。
public interface IWebHostBuilder { IWebHostBuilder UseServer(IServer server); IWebHostBuilder Configure(Action<IApplicationBuilder> configure); IWebHost Build(); }
除了用来创建WebHost的Build方法之外,我们提供了用来注册服务器的UseServer方法和用来注册中间件的Configure方法。Configure方法提供了一个类型为Action<IApplicationBuilder>的参数,意味着我们针对中间件的注册是利用上面介绍的IApplicationBuilder对象来完成的。
如下所示的WebHostBuilder是针对IWebHostBuilder接口的默认实现,它具有两个字段分别用来保存注册的中间件和调用Configure方法提供的Action<IApplicationBuilder>对象。当Build方法被调用之后,我们创建一个ApplicationBuilder对象,并将它作为参数调用这些Action<IApplicationBuilder>委托,进而将所有中间件全部注册到这个ApplicationBuilder对象上。我们最终调用它的Build方法得到由所有中间件共同构建的RequestDelegate对象,并利用它和注册的服务器构建作为应用宿主的WebHost对象。
public class WebHostBuilder : IWebHostBuilder { private IServer _server; private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>(); public IWebHostBuilder Configure(Action<IApplicationBuilder> configure) { _configures.Add(configure); return this; } public IWebHostBuilder UseServer(IServer server) { _server = server; return this; } public IWebHost Build() { var builder = new ApplicationBuilder(); foreach (var configure in _configures) { configure(builder); } return new WebHost(_server, builder.Build()); } }