一 前言

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 中的处理器

模拟上述foreach运行时的流程:

 //传入完成处理器,返回处理器C
处理器 BazMiddleware(完成处理器)
{
return 处理器C
{
//处理器C的处理代码
完成处理器
};
}
//传入处理器C,返回处理器B
处理器 BarMiddleware(处理器C)
{
return 处理器B
{
//处理器B的处理代码
执行处理器C
};
}
//传入处理器B,返回处理器A
处理器 FooMiddleware(处理器B)
{
return 处理器A
{
//处理器A的处理代码
执行处理器B
};
}

上述代码中最终返回处理器A,这样当处理器A执行的时候,会先执行自身的代码,然后执行处理器B,处理器B执行的时候,先执行自身的代码,然后执行处理器C,依次类推。

执行处理器时的伪代码流程:

中间件的方法应该是下面这样的:

RequestDelegate DoMiddleware(RequestDelegate next);

用委托表示中间件:

Func<RequestDelegate, RequestDelegate> middleware;

中间件的管理

要管理中间件,就要提供注册中间件的方法和最终构建出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框架的更多相关文章

  1. 200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]

    2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...

  2. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...

  3. 了解ASP.NET Core框架的本质

    了解ASP.NET Core框架的本质 ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架,并且利用这个 “极简” 的模拟框架阐述了ASP.NET ...

  4. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...

  5. ASP.NET Core框架的本质

    源文章地址:http://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html 1.从Hello World谈起 当我们最开始学习一门 ...

  6. ASP.NET Core框架深度学习(一) Hello World

    对于学习Core的框架,对我帮助最大的一篇文章是Artech的<200行代码,7个对象——让你了解ASP.NET Core框架的本质>,最近我又重新阅读了一遍该文.本系列文章就是结合我的阅 ...

  7. 一个Mini的ASP.NET Core框架的实现

    一.ASP.NET Core Mini 在2019年1月的微软技术(苏州)俱乐部成立大会上,蒋金楠老师(大内老A)分享了一个名为“ASP.NET Core框架揭秘”的课程,他用不到200行的代码实现了 ...

  8. ASP.NET Core 框架源码地址

    ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet  ...

  9. ASP.NET Core 框架本质学习

    本文作为学习过程中的一个记录. 学习文章地址: https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html 一. ASP.N ...

随机推荐

  1. bitmap进行个数统计

    昨天看了编程珠玑第一章的内容, 发现bitmap对于统计某一个范围内的整数个数效率很高, 就自己实现了一下: 这是原始的bitmap, 用于统计0~maxSize是否出现, 也可以用于排序 publi ...

  2. CF 445A(DZY Loves Chessboard-BW填充)

    A. DZY Loves Chessboard time limit per test 1 second memory limit per test 256 megabytes input stand ...

  3. [Phoenix] 三、DML语法

    摘要: 云HBASE上Phoenix支持的DML语法 从一个或者多个表中查询数据.LIMIT(或者FETCH FIRST) 在ORDER BY子句后将转换为top-N查询. 云HBASE上Phoeni ...

  4. (t,p,o) t:p>=o there cannot be more consumer instances in a consumer group than partitions

    https://kafka.apache.org/intro.html Kafka as a Messaging System How does Kafka's notion of streams c ...

  5. DuiLib笔记之CDuiString的bug

    在C/C++中,当使用==比较两个对象时,推荐的风格是将常量置前 例如 if (0 == variable) { ... } 但在DuiLib中,CDuiString存在一个bug:在用==进行比较时 ...

  6. ubuntu搭建mysql

    步骤1 – 安装MySQL 在 Ubuntu 16.04 中,默认情况下,只有最新版本的 MySQL 包含在 APT 软件包存储库中.在撰写本文时,那是 MySQL 5.7 要安装它,只需更新服务器上 ...

  7. hibernate属性配置

    数据库中一个字段的默认值设为0,当用hibernate插入数据时,没有对该字段进行操作,结果该字段居然不是0,而是空.后来google了一下,发现应该在.hbm.xml文件中添加一些参数定义(示例中的 ...

  8. Linux随笔-鸟哥Linux服务器篇学习总结(全)

    作者:Danbo 时间:2015-7-17 在runlevel3启动级别下默认启动网络挂载(autofs)机制,我们可以通过命令将其关闭:chkconfig autofs off 或者 /etc/in ...

  9. php常用加密函数总结

    $var = 123; /** * md5 加密(单项加密.不可逆) * param $var 需要加密的变量(int\float\string\bool\null),资源类型(resource)和复 ...

  10. Vue中的methods、watch、computed

    看到这个标题就知道这篇文章接下来要讲的内容,我们在使用vue的时候methods.watch.computed这三个特性一定经常使用,因为它们是非常的有用,但是没有彻底的理解它们的区别和各自的使用场景 ...