林中两路分,一路人迹稀。我独选此路,境遇乃相异。

0%

ASP.NET Core框架原理


蒋金楠,曾在微软技术(苏州)俱乐部成立大会上分享一个“迷你版”的ASP.NET Core框架,阐述了ASP.NET Core最核心、最本质的原理。本文对这个ASP.NET Core框架进行整理和学习。
地址:https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html

1. ASP.NET Core介绍

ASP.NET Core 是一个新的开源和跨平台的框架,用于构建如 Web 应用、物联网(IoT)应用和移动后端应用等连接到互联网的基于云的现代应用程序。ASP.NET Core 应用可运行于 .NET Core 和完整的 .NET Framework 之上。 构建它的目的是为那些部署在云端或者内部运行(on-premises)的应用提供一个优化的开发框架。它由最小开销的模块化的组件构成,因此在构建你的解决方案的同时可以保持灵活性。你可以在 WindowsMacLinux 上跨平台的开发和运行你的 ASP.NET Core 应用。 ASP.NET Core 开源在 GitHub 上。

2. ASP.NET Core Mini

GitHub地址:https://github.com/penseestroller/ASPDNC

2.1 简单示例

我们在控制台或Winform程序中通常使用HttpListener来托管Http服务,ASP.NET Core本质上也是控制台的程序。下面我们来设计一个简单的Http服务:

Program.cs:

1
2
3
4
5
6
7
8
9
10
11
12
class Program {
static async Task Main(string[] args) {
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add("http://localhost:8000/");
httpListener.Start();
while (true) {
var context = await httpListener.GetContextAsync();
await context.Response.OutputStream.WriteAsync(Encoding.UTF8.GetBytes("Hello World!"));
context.Response.Close();
}
}
}

在浏览器中访问:http://localhost:8000/,会显示 Hello World!

2.2 架构设计

ASP.NET Core具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求。
我们可以设计一个管道:Pipeline = Server + HttpHandler
Http的处理流程是:对请求(Request)的监听、接收,然后对请求信息进行处理,处理完成后,形成响应(Response),这个处理过程由服务器(Server)来完成。Server可以将一个请求进行多节点的处理,然后形成一个最终的响应,各节点之间都是按照请求+处理+响应的模式处理,形成一条管道。这些请求处理节点我们称之为“中间件(Middleware)”,每个中间件都是具有独立的功能。
pipeline2

2.3 核心对象

2.3.1 HttpContext

请求处理管道由一个服务器和多个中间件构建,面向传输层的服务器负责请求的监听、接收和最终的响应,当它接收到客户端发送的请求后,需要将它分发给后续中间件进行处理。对于某个中间件来说,当我们完成了自身的请求处理任务之后,在大部分情况下也需要将请求分发给后续的中间件。请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。
pipeline
如图所示,当服务器接收到请求之后,会创建一个通过HttpContext的上下文对象,所有中间件都是对这个上下文进行处理的。
我们定义上下文对象(Context),他封装了Http请求(Request)和响应(Response)

HttpContext.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HttpContext {
public HttpRequest Request { get; set; }
public HttpResponse Response { get; set; }
}

public class HttpRequest {
public Uri Url { get; set;}
public NameValueCollection Headers { get; set; }
public Stream Body { get; set;}
}

public class HttpResponse {
public NameValueCollection Headers { get; set; }
public Stream Body { get; set; }
public int StatusCode { get; set; }
}
2.3.2 Server

服务器(Server)用来执行处理器的对象。他有一个启动方法,传入处理器并执行处理。

1
2
3
public interface IServer{
Task StartAsync(RequestDelegate handler);
}

这里涉及到一个问题,就是HttpContextServer之间的适配,ASP.NET Core 适用于多平台,也就是说会有不同的服务器,需要去处理HttpContext,我们怎么才能做到这个HttpContext适用所有的Http服务器呢?“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决” ,可以设计一套抽象的接口,来反映HttpContext
server
如上图所示,我们定义统一的RequestResponse接口,来适配不同的Server
我们可以定义一系列的Feature接口来实现RequestResponse,那么具体的服务器只需要实现这些Feature接口就可以了。

Features.cs:

1
2
3
4
5
6
7
8
9
10
public interface IRequestFeature {
Uri Url { get; }
NameValueCollection Headers { get; }
Stream Body { get; }
}
public interface IResponseFeature {
int StatusCode { get; set; }
NameValueCollection Headers { get; }
Stream Body { get; }
}

FeatureCollection.cs:

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

修改HttpContext.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class HttpContext {
public HttpRequest Request { get; }
public HttpResponse Response { get; }

public HttpContext(IFeatureCollection features) {
Request = new HttpRequest(features);
Response = new HttpResponse(features);
}
}

public class HttpRequest {
private readonly IRequestFeature _feature;
public Uri Url => _feature.Url;
public NameValueCollection Headers => _feature.Headers;
public Stream Body => _feature.Body;
public HttpRequest(IFeatureCollection features) => _feature = features.Get<IRequestFeature>();
}

public class HttpResponse {
private readonly IResponseFeature _feature;
public NameValueCollection Header => _feature.Headers;
public Stream Body => _feature.Body;
public int StatusCode { get => _feature.StatusCode; set => _feature.StatusCode = value; }
public HttpResponse(IFeatureCollection features) => _feature = features.Get<IResponseFeature>();
}

public static partial class Extensions {
public static Task WriteAsync(this HttpResponse response, string contents) {
var buffer = Encoding.UTF8.GetBytes(contents);
return response.Body.WriteAsync(buffer, 0, buffer.Length);
}
}

接下来,我们来实现这个服务器,使用HttpListener来托管服务,HttpListenerFeature.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HttpListenerFeature : IRequestFeature, IResponseFeature {
private readonly HttpListenerContext _context;

Uri IRequestFeature.Url => _context.Request.Url;
NameValueCollection IRequestFeature.Headers => _context.Request.Headers;
NameValueCollection IResponseFeature.Headers => _context.Response.Headers;
Stream IRequestFeature.Body => _context.Request.InputStream;
Stream IResponseFeature.Body => _context.Response.OutputStream;
public int StatusCode {
get { return _context.Response.StatusCode; }
set { _context.Response.StatusCode = value; }
}

public HttpListenerFeature(HttpListenerContext context) => _context = context;
}

HttpListenerServer.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HttpListenerServer : IServer {
private readonly HttpListener _listener;
private readonly string[] _urls;

public HttpListenerServer(params string[] urls) {
_listener = new HttpListener();
_urls = urls.Any() ? urls : new string[] { "http://localhost:8000/" };
}

public async Task StartAsync(RequestDelegate handler) {
Array.ForEach(_urls, url => _listener.Prefixes.Add(url));
_listener.Start();
Console.WriteLine("Server started and is listening on: {0}", string.Join(';', _urls));
while (true) {
var listenerContext = await _listener.GetContextAsync();
var feature = new HttpListenerFeature(listenerContext);
var features = new FeatureCollection()
.Set<IRequestFeature>(feature)
.Set<IResponseFeature>(feature);
var httpContext = new HttpContext(features);
await handler(httpContext);
listenerContext.Response.Close();
}
}
}

修改下程序入口Main方法,测试运行:

1
2
3
4
5
6
7
static async Task Main(string[] args) {   
IServer server = new HttpListenerServer();
async Task fooBar(HttpContext httpContext) {
await httpContext.Response.WriteAsync("FooBar");
}
await server.StartAsync(fooBar);
}

运行结果:
main1

2.3 中间件(Middleware)

2.3.1 Handler

HttpHandler:Http处理器,处理请求(Request)返回响应(Response)。可以考虑有这么一个处理方法:

1
Task Handle(HttpContext context);

如何来表示这个Handler?我们需要同步或者异步的处理请求和响应,那个考虑使用Task对象,这样我们设计一个返回Task的委托,来灵活的实现这个处理器:
常规思维,直接定义一个HttpHandler接口,再定义一套实现:

1
2
3
public interface IHttpHandler{
Task Handle(HttpContext context);
}

或者采用更灵活的方式,采用委托类型,实现相同效果:

1
public delegate Task RequestDelegate(HttpContext context);
2.3.2 Middleware

中间件是被用到管道(Pipeline)上来处理请求(Request)和响应(Response)的,他将多个中间件连接,当前中间件在完成了自身的请求处理任务之后,需要将请求分发给后续中间件进行处理。也就是说,中间件的输入和输出都可以用Handler对象来表示,所以我们定义中间件为:

1
RequestDelegate Middleware(RequestDelegate next);

1
Func<RequestDelegate, RequestDelegate>

我们可以设计一系列这样的中间件,按顺序执行这些处理程序。

2.3.3 ApplicationBuilder

接下来的问题是,我们要如何管理这些中间件,并且如何使用这些中间件呢?使用建造者模式来设计这个管理器,定义ApplicationBuilder,那么应该构建一个Application。对于这个Application,可以这样理解为
Pipeline = Server + HttpHandler
用来处理请求的HttpHandler就承载了当前应用的所有职责,那么HttpHandler就等于Application,由于HttpHandler通过RequestDelegate表示,那么由ApplicationBuilder构建的Application就是一个RequestDelegate对象。由于表示HttpHandlerRequestDelegate是由注册的中间件来构建的,所以ApplicationBuilder还具有注册中间件的功能。

ApplicationBuilder.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IApplicationBuilder {
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}

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;
}
}

修改Program类,增加定义三个中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static RequestDelegate FooMiddleware(RequestDelegate next)
=> async context => {
await context.Response.WriteAsync("Foo=>");
await next(context);
};

public static RequestDelegate BarMiddleware(RequestDelegate next)
=> async context => {
await context.Response.WriteAsync("Bar=>");
await next(context);
};

public static RequestDelegate BazMiddleware(RequestDelegate next)
=> context => context.Response.WriteAsync("Baz");

修改下程序入口Main方法,测试运行:

1
2
3
4
5
6
7
8
9
static async Task Main(string[] args) {   
IServer server = new HttpListenerServer();
var application = new ApplicationBuilder()
.Use(FooMiddleware)
.Use(BarMiddleware)
.Use(BazMiddleware)
.Build();
await server.StartAsync(application);
}

运行结果:
main2

2.3.4 WebHost

管理服务器及后续的管道构建,需要一个应用宿主WebHost,管道在WebHost对象启动的时候就被构建出来了。WebHost只做了一件事,将我们构造的中间件管道处理器在指定Server运行起来。
WebHost.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IWebHost {
Task 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);
}

要构建WebHost,需要知道用哪个服务器,和配置了哪些中间件,WebHostBuilder就是创建作为应用宿主的WebHost
WebHostBuilder.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface IWebHostBuilder {
IWebHostBuilder UseServer(IServer server);
IWebHostBuilder Configure(Action<IApplicationBuilder> configure);
IWebHost Build();
}

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());
}
}

IWebHostBuilder加一个扩展方法,用来使用HttpListenerServer 服务器,修改HttpListenerServer.cs,增加:

1
2
3
4
public static partial class Extensions {
public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls)
=> builder.UseServer(new HttpListenerServer(urls));
}

最后修改Main启动方法:

1
2
3
4
5
6
7
8
9
10
static async Task Main(string[] args) {   
await new WebHostBuilder()
.UseHttpListener()
.Configure(app => app
.Use(FooMiddleware)
.Use(BarMiddleware)
.Use(BazMiddleware))
.Build()
.StartAsync();
}

运行结果:
main2

注:在ASP.NET Core 3.0后,舍弃了WebHost的构建,采用泛型主机(Generic Hosting)来构建宿主。

-------------本文结束 感谢您的阅读-------------
觉得好,点这里 ^_^