欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一步步完成“迷你版” 的ASP.NET Core框架

程序员文章站 2023-10-17 09:06:00
一 前言 Artech 分享了 "200行代码,7个对象——让你了解ASP.NET Core框架的本质" 。 用一个极简的模拟框架阐述了ASP.NET Core框架最为核心的部分。 这里一步步来完成这个迷你框架。 二 先来一段简单的代码 这段代码非常简单,启动服务器并监听本地5000端口和处理请求。 ......

一 前言

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

运行结果如下:
一步步完成“迷你版” 的ASP.NET Core框架

至此,完成了服务器和处理器的抽象。
接下来单看处理器,所有的处理逻辑都集合在一个方法中,理想的方式是有多个处理器进行处理,比如处理器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); 
        }

运行结果如下:
一步步完成“迷你版” 的ASP.NET Core框架

六 管理服务器和处理器

为了管理服务器和处理器之间的关系 抽象出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();              

        }

运行结果
一步步完成“迷你版” 的ASP.NET Core框架

最后,期待artech 新书。