一步步完成“迷你版” 的ASP.NET Core框架
一 前言
artech 分享了 200行代码,7个对象——让你了解asp.net core框架的本质 。 用一个极简的模拟框架阐述了asp.net core框架最为核心的部分。
这里一步步来完成这个迷你框架。
二 先来一段简单的代码
这段代码非常简单,启动服务器并监听本地5000端口和处理请求。
static async task main(string[] args) { httplistener httplistener = new httplistener(); httplistener.prefixes.add("http://localhost:5000/"); httplistener.start(); while (true) { var context = await httplistener.getcontextasync(); await context.response.outputstream.writeasync(encoding.utf8.getbytes("hello world")); context.response.close(); } }
现在要分离服务器(server) 和 请求处理(handle),那么一个简单设计架构就出来了 :
pipeline =server + httphandler
三 处理器的抽象
处理器要从请求(request)中获取数据,和定制响应(response)的数据。
可以想到我们的处理器的处理方法应该是这样的:
task handle(/*httprequest httpresponse*/);
它可以处理请求和响应,由于处理可以是同步或者异步的,所以返回task。
很容易想到要封装http请求和响应,封装成一个上下文(context) 供处理器使用(这样的好处,处理器需要的其他数据也可以封装在这里,统一使用),所以要开始封装httpcontext。
封装httpcontext
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; } } public class httpcontext { public httprequest request { get; set; } public httpresponse response { get; set; } }
要支持不同的服务器,则不同的服务器都要提供httpcontext,这样有了新的难题:服务器和httpcontext之间的适配 。
现阶段的httpcontext包含httprequest和httpresponse,请求和响应的数据都是要服务器(server)提供的。
可以定义接口,让不同的服务器提供实现接口的实例:
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之间的适配,定义一个功能的集合,通过类型可以找到服务器提供的实例
public interface ifeaturecollection:idictionary<type,object> { } public static partial class extensions { public static t get<t>(this ifeaturecollection features) { return features.trygetvalue(typeof(t), out var value) ? (t)value : default; } public static ifeaturecollection set<t>(this ifeaturecollection features,t feature) { features[typeof(t)] = feature; return features; } } public class featurecollection : dictionary<type, object>, ifeaturecollection { }
接下来修改httpcontext,完成适配
public class httpcontext { public httpcontext(ifeaturecollection features) { request = new httprequest(features); response = new httpresponse(features); } public httprequest request { get; set; } public httpresponse response { get; set; } } public class httprequest { private readonly ihttprequestfeature _httprequestfeature; public httprequest(ifeaturecollection features) { _httprequestfeature = features.get<ihttprequestfeature>(); } public uri url => _httprequestfeature.url; public namevaluecollection headers => _httprequestfeature.headers; public stream body => _httprequestfeature.body; } public class httpresponse { private readonly ihttpresponsefeature _httpresponsefeature; public httpresponse(ifeaturecollection features) { _httpresponsefeature = features.get<ihttpresponsefeature>(); } public int statuscode { get => _httpresponsefeature.statuscode; set => _httpresponsefeature.statuscode = value; } public namevaluecollection headers => _httpresponsefeature.headers; public stream body => _httpresponsefeature.body; } public static partial class extensions { public static task writeasync(this httpresponse response,string content) { var buffer = encoding.utf8.getbytes(content); return response.body.writeasync(buffer, 0, buffer.length); } }
定义处理器
封装好了httpcontext,终于可以回过头来看看处理器。
处理器的处理方法现在应该是这样:
task handle(httpcontext context);
接下来就是怎么定义这个处理器了。
起码有两种方式:
1、定义一个接口:
public interface ihttphandler { task handle(httpcontext context); }
2、定义一个委托类型
public delegate task requestdelegate(httpcontext context);
两种方式,本质上没啥区别,委托代码方式更灵活,不用实现一个接口,还符合鸭子模型。
处理器就选用委托类型。
定义了处理器,接下来看看服务器
四 服务器的抽象
服务器应该有一个开始方法,传入处理器,并执行。
服务器抽象如下:
public interface iserver { task startasync(requestdelegate handler); }
定义一个httplistener的服务器来实现iserver,由于httplistener的服务器需要提供httpcontext所需的数据,所以先定义httplistenerfeature
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; } }
定义httplistener服务器
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(); console.writeline($"服务器{typeof(httplistenerserver).name} 开启,开始监听:{string.join(";", _urls)}"); while (true) { var listtenercontext = await _httplistener.getcontextasync(); var feature = new httplistenerfeature(listtenercontext); var features = new featurecollection() .set<ihttprequestfeature>(feature) .set<ihttpresponsefeature>(feature); var httpcontext = new httpcontext(features); await handler(httpcontext); listtenercontext.response.close(); } } }
修改main方法运行测试
static async task main(string[] args) { iserver server = new httplistenerserver(); async task foobar(httpcontext httpcontext) { await httpcontext.response.writeasync("foobar"); } await server.startasync(foobar); }
运行结果如下:
至此,完成了服务器和处理器的抽象。
接下来单看处理器,所有的处理逻辑都集合在一个方法中,理想的方式是有多个处理器进行处理,比如处理器a处理完,则接着b处理器进行处理……
那么就要管理多个处理器之间的连接方式。
五 中间件
中间件的定义
假设有三个处理器a,b,c
框架要实现:a处理器开始处理,a处理完成之后,b处理器开始处理,b处理完成之后,c处理器开始处理。
引入中间件来完成处理器的连接。
中间件的要实现的功能很简单:
- 传入下一个要执行的处理器;
- 在中间件中的处理器里,记住下一个要执行的处理器;
- 返回中间件中的处理器,供其他中间件使用。
所以中间件应该是这样的:
//伪代码 处理器 middleware(传入下一个要执行的处理器) { return 处理器 { //处理器的逻辑 下一个要执行的处理器在这里执行 } }
举个例子,现在有三个中间件foomiddleware,barmiddleware,bazmiddleware,分别对应的处理器为a,b,c
要保证 处理器的处理顺序为 a->b->c
则先要执行 最后一个bazmiddleware,传入“完成处理器” 返回 处理器c
然后把处理器c 传入 barmiddleware ,返回处理器b,依次类推。
//伪代码 var middlewares=new []{foomiddleware,barmiddleware,bazmiddleware}; middlewares.reverse(); var next=完成的处理器; foreach(var middleware in middlewares) { next= middleware(next); } //最后的next,就是最终要传入iserver 中的处理器
模拟运行时的伪代码:
//传入完成处理器,返回处理器c 处理器 bazmiddleware(完成处理器) { return 处理器c { //处理器c的处理代码 完成处理器 }; } //传入处理器c,返回处理器b 处理器 barmiddleware(处理器c) { return 处理器b { //处理器b的处理代码 执行处理器c }; } //传入处理器b,返回处理器a 处理器 foomiddleware(处理器b) { return 处理器a { //处理器a的处理代码 执行处理器b }; }
这样当处理器a执行的时候,会先执行自身的代码,然后执行处理器b,处理器b执行的时候,先执行自身的代码,然后执行处理器c,依次类推。
所以,中间件的方法应该是下面这样的:
requestdelegate domiddleware(requestdelegate next);
中间件的管理
要管理中间件,就要提供注册中间件的方法和最终构建出requestdelegate的方法。
定义注册中间件和构建处理器的接口: iapplicationbuilder
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 iapplicationbuilder use(func<requestdelegate, requestdelegate> middleware) { _middlewares.add(middleware); return this; } public requestdelegate build() { _middlewares.reverse(); requestdelegate next = context => { context.response.statuscode = 404; return task.completedtask; }; foreach (var middleware in _middlewares) { next = middleware(next); } return next; } }
定义中间件测试
在program 类里定义三个中间件:
static requestdelegate foomiddleware(requestdelegate next) { return async context => { await context.response.writeasync("foo=>"); await next(context); }; } static requestdelegate barmiddleware(requestdelegate next) { return async context => { await context.response.writeasync("bar=>"); await next(context); }; } static requestdelegate bazmiddleware(requestdelegate next) { return async context => { await context.response.writeasync("baz=>"); await next(context); }; }
修改main方法测试运行
static async task main(string[] args) { iserver server = new httplistenerserver(); var handler = new applicationbuilder() .use(foomiddleware) .use(barmiddleware) .use(bazmiddleware) .build(); await server.startasync(handler); }
运行结果如下:
六 管理服务器和处理器
为了管理服务器和处理器之间的关系 抽象出web宿主
如下:
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() { return _server.startasync(_handler); } }
main方法可以改一下测试
static async task main(string[] args) { iserver server = new httplistenerserver(); var handler = new applicationbuilder() .use(foomiddleware) .use(barmiddleware) .use(bazmiddleware) .build(); iwebhost webhost = new webhost(server, handler); await webhost.startasync(); }
要构建webhost,需要知道用哪个服务器,和配置了哪些中间件,最后可以构建出webhost
代码如下:
public interface iwebhostbuilder { iwebhostbuilder useserver(iserver server); iwebhostbuilder configure(action<iapplicationbuilder> configure); iwebhost build(); } public class webhostbuilder : iwebhostbuilder { private readonly list<action<iapplicationbuilder>> _configures = new list<action<iapplicationbuilder>>(); private iserver _server; public iwebhost build() { //所有的中间件都注册在builder上 var builder = new applicationbuilder(); foreach (var config in _configures) { config(builder); } return new webhost(_server, builder.build()); } public iwebhostbuilder configure(action<iapplicationbuilder> configure) { _configures.add(configure); return this; } public iwebhostbuilder useserver(iserver server) { _server = server; return this; } }
给iwebhostbuilder加一个扩展方法,用来使用httplistenerserver 服务器
public static partial class extensions { public static iwebhostbuilder usehttplistener(this iwebhostbuilder builder, params string[] urls) { return builder.useserver(new httplistenerserver(urls)); } }
修改mian方法
static async task main(string[] args) { await new webhostbuilder() .usehttplistener() .configure(app=> app.use(foomiddleware) .use(barmiddleware) .use(bazmiddleware)) .build() .startasync(); }
完成。
七 添加一个usemiddleware 扩展 玩玩
public static iapplicationbuilder usemiddleware(this iapplicationbuilder application, type type) { //省略实现 } public static iapplicationbuilder usemiddleware<t>(this iapplicationbuilder application) where t : class { return application.usemiddleware(typeof(t)); }
添加一个中间件
public class quxmiddleware { private readonly requestdelegate _next; public quxmiddleware(requestdelegate next) { _next = next; } public async task invokeasync(httpcontext context) { await context.response.writeasync("qux=>"); await _next(context); } } public static partial class extensions { public static iapplicationbuilder usequx(this iapplicationbuilder builder) { return builder.usemiddleware<quxmiddleware>(); } }
使用中间件
class program { static async task main(string[] args) { await new webhostbuilder() .usehttplistener() .configure(app=> app.use(foomiddleware) .use(barmiddleware) .use(bazmiddleware) .usequx()) .build() .startasync(); }
运行结果
最后,期待artech 新书。
推荐阅读
-
ASP.NET Core应用中与第三方IoC/DI框架的整合
-
ASP.NET Core应用中与第三方IoC/DI框架的整合
-
一步步完成“迷你版” 的ASP.NET Core框架
-
【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持
-
在ASP.NET Core 2.0上操作MongoDB就是能这么的简便酷爽(自动完成分库分表)
-
基于ASP.Net Core开发的一套通用后台框架
-
详解ASP.NET Core 中的框架级依赖注入
-
ASP.NET Core 依赖注入框架的使用
-
一步步完成“迷你版” 的ASP.NET Core框架
-
在ASP.NET Core 2.0上操作MongoDB就是能这么的简便酷爽(自动完成分库分表)