200行代码实现Mini ASP.NET Core
前言
在学习ASP.NET Core源码过程中,偶然看见蒋金楠老师的ASP.NET Core框架揭秘,不到200行代码实现了ASP.NET Core Mini框架,针对框架本质进行了讲解,受益匪浅,本文结合ASP.NET Core Mini框架讲述ASP.NET Core核心。
微软官网关于ASP.NET Core的概念“ASP.NET Core是一个开源和跨平台的框架,用于构建基于Web的现代互联网连接应用程序,例如Web应用程序,IoT应用程序和移动后端。 ASP.NET Core应用程序可以在.NET Core或完整的.NET Framework上运行。 它的架构旨在为部署到云或在本地运行的应用程序提供优化的开发框架。 它由模块化组件组成,开销最小,因此您可以在构建解决方案时保持灵活性。 您可以在Windows,Mac和Linux上跨平台开发和运行ASP.NET核心应用程序”。可以从定义上看出ASP.NET Core框架具有跨平台、部署灵活、模块化等特点。
ASP.NET Core框架揭秘
ASP.NET Core Mini是200行代码实现的迷你版ASP.NET Core框架,有三大特点“简单”,“真实模拟”,“可执行”来让我们更加容易理解ASP.NET Core。
代码结构:
下图是项目运行页面输出的结果:
本文从以下五个角度讲述:
- Program: 项目入口
- Middleware:中间件
- HttpContext:Http相关
- WebHost:WebHost
- Server:Server相关
Program
using System.Threading.Tasks;
using App.Server;
using App.WebHost; namespace App
{
public static class Program
{
public static async Task Main(string[] args)
{
await CreateWebHostBuilder()
.Build()
.Run();
} private static IWebHostBuilder CreateWebHostBuilder()
{
return new WebHostBuilder()
.UseHttpListener()
.Configure(app => app
.Use(Middleware.Middleware.FooMiddleware)
.Use(Middleware.Middleware.BarMiddleware)
.Use(Middleware.Middleware.BazMiddleware));
}
}
}
可以看到项目的入口是Main方法,它只做了三件事,构造WebHostBuilder,然后Build方法构造WebHost,最后Run方法启动WebHost。我们可以简单的理解WebHostBuilder作用就是为了构造WebHost,他是WebHost的构造器,而WebHost是我们Web应用的宿主。
再看CreateWebHostBuilder的方法具体干了什么。首先创建了WebHostBuilder,然后UseHttpListener配置Server(比如ASP.NET Core中的Kestrel或IIS等等),一般包括地址和端口等,最后注册一系列的中间件。
从Program可以看出整个App运行起来的流程,如下图所示:
Middleware
在看HttpContext之前,我们先来看ASP.NET Core 的Http处理管道是什么样子,上图是官方给出的管道处理图,当我们的服务器接收到一个Http请求,管道进行处理,处理后再进行返回,可以看到,我们的Http请求经过多层中间件处理最后返回。
using System.Threading.Tasks; namespace App.Middleware
{
public delegate Task RequestDelegate(HttpContext.HttpContext context);
}
首先来看RequestDelegate.cs,定义了一个参数类型是HttpContext,返回结果是Task的委托。
为什么会定义这个委托,可以想到一个Http请求会经过多层中间件处理,那么多层中间件处理可以想像成一个HttpHandler,他的参数就是HttpContext,返回结果是Task的委托。
using App.HttpContext; namespace App.Middleware
{
public static class Middleware
{
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");
}
}
Middleware中定义了三个简单的中间件,可以看到,中间件其实就是委托,将HttpContext一层一层进行处理。
Http请求进入管道,第一个中间件处理完,把自身作为结果传输到下一个中间件进行处理,那么参数是RequestDelegate,返回值是RequestDelegate的委托就是中间件,所以中间件其实就是Func<RequestDelegate,RequestDelegate>,简单来说,中间件就是RequestDelegate的加工工厂。
HttpContext
从Middleware了解到,HttpContext是RequestDelegate的参数,是每一个Middleware处理数据的来源。
我们可以这么理解,HttpContext就是我们整个Http请求中的共享资源,所以的中间件共享它,而每个中间件就是对它进行加工处理。
using System;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Threading.Tasks; namespace App.HttpContext
{
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 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 HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>(); public NameValueCollection Headers => _feature.Headers;
public Stream Body => _feature.Body; public int StatusCode
{
get => _feature.StatusCode;
set => _feature.StatusCode = value;
}
} 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);
}
}
}
代码结构可以看出request和reponse构成httpcontext,也反映出httpcontext的职责:Http请求的上下文。
但是,不同的Server和单一的HttpContext之间需要如何适配呢?因为我们可以注册多样的Server,可以是IIS也可以是Kestrel还可以是这里的HttpListenerServer。
所以我们需要定义统一的request和response接口,来适配不同的Server。如下图的IHttpRequestFeature和IHttpResponseFeature。
using System;
using System.Collections.Specialized;
using System.IO; namespace App.HttpContext
{
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; }
}
}
在HttpListenerFeature.cs中实现request和response的接口,实现了适配不同的server。
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net; namespace App.HttpContext
{
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;
}
}
}
至于FeatureCollection.cs,它的作用就是将从httpListenerContext中获取的Http信息存储在FeatureCollection的Dictionary里,更加方便的对HttpRequestFeature和HttpResponseFeature进行操作。
扩展方法Get和Set的作用是方便操作FeatureCollection。
using System;
using System.Collections.Generic; namespace App.HttpContext
{
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;
}
}
}
WebHost
using System;
using System.Collections.Generic;
using App.Server; namespace App.WebHost
{
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());
}
}
}
WebHost是我们App的宿主,通过WebHostBuild构造,代码里定义了三个方法,
- UseServer: 配置server
- Configure: 注册中间件
- Build: 构造WebHost
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using App.Middleware; namespace App.WebHost
{
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;
}
}
}
ApplicationBuilder做了什么,Use方法我们把自定义的中间件放进集合里,而build方法就是构建webhost。首先把中间键集合顺序倒置,然后构造一个StatusCode为404的中间件,其次遍历中间件集合,最后返回构造好的管道。
如果中间件集合为空,我们返回Http 404错误。
至于为什么要Reverse(),是因为我们注册中间件的顺序与我们需要执行的顺序相反。
using System.Threading.Tasks;
using App.Middleware;
using App.Server; namespace App.WebHost
{
public interface IWebHost
{
Task Run();
} public class WebHost : IWebHost
{
private readonly IServer _server;
private readonly RequestDelegate _handler; public WebHost(IServer server, RequestDelegate handler)
{
_server = server;
_handler = handler;
} public Task Run() => _server.RunAsync(_handler);
}
}
WebHost只做了一件事,将我们构造的中间件管道处理器在指定Server运行起来。
Server
我们自定义一个服务器,IServer定义统一接口,HttpListenerServer实现我们自定义的Server
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using App.HttpContext;
using App.Middleware;
using App.WebHost; namespace App.Server
{
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[] {"http://localhost:5000/"};
} public async Task RunAsync(RequestDelegate handler)
{
Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url)); if (!_httpListener.IsListening)
{
_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.HttpContext(features); await handler(httpContext); listenerContext.Response.Close();
}
}
} public static class Extensions
{
public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls)
=> builder.UseServer(new HttpListenerServer(urls));
}
}
使用UseHttpListener扩展方法,指定监听地址,默认为“http://localhost:5000/”。
RunAsync方法是我们WebHost的Run方法,循环中通过调用其GetContextAsync方法实现了针对请求的监听和接收。
总结
看完这篇文章应该对ASP.NET Core有一定对理解,核心就是中间件管道。不过ASP.NET Core源码远远不止这些,每个模块的实现较复杂,还有其他必不可少的模块(依赖注入、日志系统、异常处理等),需要更加深入的学习。我也会记录我的学习记录,最后来一张完整的Http请求管道图。
参考资料 :200行代码,7个对象——让你了解ASP.NET Core框架对本质
代码地址: GitHub
200行代码实现Mini ASP.NET Core的更多相关文章
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...
- 为什么你需要将代码迁移到ASP.NET Core 2.0?
随着 .NET Core 2.0 的发布,.NET 开源跨平台迎来了新的时代.开发者们可以选择使用命令行.个人喜好的文本编辑器.Visual Studio 2017 15.3 和 Visual Stu ...
- 深入研究 Mini ASP.NET Core(迷你 ASP.NET Core),看看 ASP.NET Core 内部到底是如何运行的
前言 几年前,Artech 老师写过一个 Mini MVC,用简单的代码告诉读者 ASP.NET MVC 内部到底是如何运行的.当时我研究完以后,受益匪浅,内心充满了对 Artech 老师的感激,然后 ...
- SpringBoot,用200行代码完成一个一二级分布式缓存
缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂.早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快. 后来应用系统水平扩展,缓存作为一个独立系统存在,如redis,但是每次从缓存 ...
- 200行代码实现简版react🔥
200行代码实现简版react
- 不到 200 行代码,教你如何用 Keras 搭建生成对抗网络(GAN)【转】
本文转载自:https://www.leiphone.com/news/201703/Y5vnDSV9uIJIQzQm.html 生成对抗网络(Generative Adversarial Netwo ...
- 200 行代码实现基于 Paxos 的 KV 存储
前言 写完[paxos 的直观解释]之后,网友都说疗效甚好,但是也会对这篇教程中一些环节提出疑问(有疑问说明真的看懂了 ),例如怎么把只能确定一个值的 paxos 应用到实际场景中. 既然 Talk ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]
2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...
随机推荐
- DBUtils框架的使用(下)
刚才讲了使用QueryRunner插入.修改.更新数据,现在来学习一下使用QueryRunner进行数据库表查询. 通过QueryRunner类的query()方法即可完成数据库表的查询操作,但是在查 ...
- 记:使用vue全家桶 + vux组件库 打包成 dcloud 5+ app 开发过程中遇到的问题
vue-cli 版本:2.9.6 webpack 版本:3.6.0 1. vue-cli 安装好之后,不是自动打开默认浏览器 在 config文件夹 ---> dev选项中,有个 autoO ...
- PythonWeb框架Django:虚拟环境安装(virtualenv)
虚拟环境的用处: 当我们有多个项目要使用不同的第三方类库的时候,就会发生冲突,因为Python的环境内只允许一个版本的第三方类库. 比如说 有A,B两个Web项目,但是A项目的Django的环境为2. ...
- [Revit]开始:编写一个简单外部命令
1 创建项目 以Visual Stidio作为开发工具,测试平台为Revit 2017 打开VS,创建一个C# .NET Framwork类库项目,选择..net框架版本为.NET Framwork ...
- 系统模块 OS
os.system("系统命令") 调用系统命令 os.system("task kill /f /im 系统的进程") 关闭系统进程 os.listdir( ...
- BZOJ2655 Calc - dp 拉格朗日插值法
BZOJ2655 Calc 参考 题意: 给定n,m,mod,问在对mod取模的背景下,从[1,m]中选出n个数相乘可以得到的总和为多少. 思路: 首先可以发现dp方程 ,假定dp[m][n]表示从[ ...
- HDU 1251 统计难题 字典树大水题
今天刚看的字典树, 就RE了一发, 字典树原理还是很简单的, 唯一的问题就是不知道一维够不够用, 就开的贼大, 这真的是容易MLE的东西啊, 赶紧去学优化吧. HDU-1251 统计难题 这道题唯一的 ...
- codeforces 864 E. Fire(背包+思维)
题目链接:http://codeforces.com/contest/864/problem/E 题解:这题一看就很像背包但是这有3维限制也就是说背包取得先后也会对结果有影响.所以可以考虑sort来降 ...
- 移位密码(加密+解密)C++实现
移位密码 加密C=Ek(m)=m+k mod 26 解密m=Dk(m)=c-k mod 26 密钥空间|k|=26=|c|=|m| #include<iostream> #include& ...
- 树状数组求区间和模板 区间可修改 参考题目:牛客小白月赛 I 区间
从前有个东西叫树状数组,它可以轻易实现一些简单的序列操作,比如单点修改,区间求和;区间修改,单点求值等. 但是我们经常需要更高级的操作,比如区间修改区间查询.这时候树状数组就不起作用了,只能选择写一个 ...