本文共 9764 字,大约阅读时间需要 32 分钟。
编程语言都有所不同,各个语言解决同一类问题而设计的框架,确有共通之处,毕竟是解决同一类问题,面临的挑战大致相同,比如身份验证,api授权等等,鄙人对node.js,golang,.net core有所涉猎,对各自的web框架进行学习的过程中发现了确实有相似之处。下面即对node.js的koa、golang的gin与.net core的asp.net core三种不同的web后端框架的中间件做一个分析对比
//如果不写next,就不会向下匹配--匹配任何一个路由app.use(async(ctx,next)=>{ console.log(new Date()) await next();})
router.get('/news',async(ctx,next)=>{ console.log("this is news") await next(); })
app.use(async(ctx,next)=>{ //应用级中间件 都需要执行 /* 1.执行若干代码 */ next();//2.执行next() 匹配其他路由 //4.再执行 if(ctx.status==404){ ctx.status=404 ctx.body="这是一个404" }else{ console.log(ctx.url) }})//3.匹配下面的路由 router.get('/news',async(ctx)=>{ console.log("this is news") ctx.body="这是一个新闻页面" })
静态资源中间件为例:静态资源地址没有路由匹配,盲目引入静态资源,会报404.
//安装npm install koa-static --save//使用//引入const static=require('koa-static')//使用app.use(static('static')) //去static文件目录中将中找文件,如果能找到对应的文件,找不到就next()app.use(static(__dirname+'/static'))app.use(static(__dirname+'/public'))
洋葱执行:从上到下依次执行,匹配路由响应,再返回至中间件进行执行中间件,【先从外向内,然后再从内向外】
钩子(Hook)函数,中间件函数
package mainimport( "github.com/gin-gonic/gin")func main(){ r:=gin.Default() r.GET("/index",func(c *gin.Context){ //... }) r.Run()}func m1(c *gin.Context){ fmt.Println("中间件m1") c.Next()//调用后续的处理函数 //c.Abort()//阻止调用后续的处理函数 fmt.Println("m1 out...")}
package mainimport( "github.com/gin-gonic/gin")func main(){ r:=gin.Default() r.GET("/index",func(c *gin.Context){ //... }) //某个路由单独注册--也可以取名为路由级注册中间件 r.GET("/test1",m1,func(c *gin.Context){ //... }) //路由组注册 xxGroup:=r.Group("/xx",m1) { xxGroup.GET("/index",func(c *gin.Context){ //... }) } xx2Group:=r.Group("/xx2") xx2Group.Use(m1) { xxGroup.GET("/index",func(c *gin.Context){ //... }) } r.Run() r.GET("/index",m1)}func m1(c *gin.Context){ fmt.Println("中间件m1") c.Next()//调用后续的处理函数 //c.Abort()//阻止调用后续的处理函数 //return 连下方的fmt.Println都不执行了,立即返回 fmt.Println("m1 out...")}r.Use(m1)//全局注册//多个中间件注册r.Use(m1,m2)
与koa中间件执行顺序一致
func authMiddleware(doCheck bool) gin.HandlerFunc{ //连接数据库 //或准备工作 return func(c *gin.Context){ //是否登录判断 //if是登录用户 //c.Next() //else //c.Abort() }}
func m1(c *gin.Context){ fmt.Println("m1 in ...") start := time.Now() c.Next() cost:=time.Since(start) fmt.Printf("cost:%v\n",cost) fmt.Println("m1 out...")}func m2(c *gin.Context){ fmt.Println("m2 in...") //中间件存值 c.Set("name","carfield") fmt.Println("m2 out...") //其他中间件取值 // c.Get // c.MustGet}
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文
(c *gin.Context)
必须使用其只读副本c.Copy()
,否则会出现线程安全问题。
使用IApplicationBuilder 创建中间件管道
//Runpublic class Startup{ public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); }}//Use - Runpublic class Startup{ public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesn't write to the Response. await next.Invoke(); // Do logging or other work that doesn't write to the Response. }); app.Run(async context => { await context.Response.WriteAsync("Hello from 2nd delegate."); }); }}//这个Use是不是跟koa的应用级中间件很像
Map 扩展用作约定来创建管道分支。
Map
基于给定请求路径的匹配项来创建请求管道分支。如果请求路径以给定路径开头,则执行分支。koa和gin中路由匹配就是map这种,当不使用内置的mvc模板路由,我姑且称它为自定义路由
public class Startup{ private static void HandleMapTest1(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); } private static void HandleMapTest2(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 2"); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate."); }); } //请求会匹配 map1...map2...没匹配到路由的统统会执行app.Run}//像golang的gin一样,map也支持嵌套app.Map("/level1", level1App => { level1App.Map("/level2a", level2AApp => { // "/level1/level2a" processing }); level1App.Map("/level2b", level2BApp => { // "/level1/level2b" processing });});public class Startup{ private static void HandleMultiSeg(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map multiple segments."); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1/seg1", HandleMultiSeg); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate."); }); }}//MapWhen 基于给定谓词的结果创建请求管道分支。Func
类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量 branch 是否存在:public class Startup{ private static void HandleBranch(IApplicationBuilder app) { app.Run(async context => { var branchVer = context.Request.Query["branch"]; await context.Response.WriteAsync($"Branch used = {branchVer}"); }); } public void Configure(IApplicationBuilder app) { app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate. "); }); }}//UseWhen 也是基于给定谓词的结果创建请求管道分支。 与 MapWhen 不同的是,如果这个分支发生短路或包含终端中间件,则会重新加入主管道:public class Startup{ private readonly ILogger
_logger; public Startup(ILogger logger) { _logger = logger; } private void HandleBranchAndRejoin(IApplicationBuilder app) { app.Use(async (context, next) => { var branchVer = context.Request.Query["branch"]; _logger.LogInformation("Branch used = {branchVer}", branchVer); // Do work that doesn't write to the Response. await next(); // Do other work that doesn't write to the Response. }); } public void Configure(IApplicationBuilder app) { app.UseWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranchAndRejoin); app.Run(async context => { await context.Response.WriteAsync("Hello from main pipeline."); }); }}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ if (env.IsDevelopment()) { //开发人员异常页中间件 报告应用运行时错误 app.UseDeveloperExceptionPage(); //数据库错误页中间件报告数据库运行时错误 app.UseDatabaseErrorPage(); } else { //异常处理程序中间件 app.UseExceptionHandler("/Error"); //http严格传输安全协议中间件 app.UseHsts(); } //HTTPS重定向中间件 app.UseHttpsRedirection(); //静态文件中间件 app.UseStaticFiles(); //Cookie策略中间件 app.UseCookiePolicy(); //路由中间件 app.UseRouting(); //身份验证中间件 app.UseAuthentication(); //授权中间件 app.UseAuthorization(); //会话中间件-如果使用session,就需要把cookie策略中间件先使用了,再引入session中间件,再引入mvc中间件,毕竟session是依赖cookie实现的 app.UseSession(); //终结点路由中间件 app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); });}
Configure
中直接写//在Startup.Configure直接编码public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { //做一些操作 // Call the next delegate/middleware in the pipeline await next(); }); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello world"); }); }
在
Startup.Configure
直接编码,当定义多个中间件,代码难免变得臃肿,不利于维护,看看内置的中间件,app.UseAuthentication();
多简洁,查看asp.net core源码,内置的中间件都是一个中间件类xxMiddleware.cs
一个扩展方法xxMiddlewareExtensions.cs
然后在Startup.Configure
中使用扩展方法调用Usexx()
using Microsoft.AspNetCore.Http;using System.Globalization;using System.Threading.Tasks;namespace Culture{ public class RequestTestMiddleware { private readonly RequestDelegate _next; //具有类型为 RequestDelegate 的参数的公共构造函数 public RequestTestMiddleware(RequestDelegate next) { _next = next; } //名为 Invoke 或 InvokeAsync 的公共方法。 此方法必须: //返回 Task。 //接受类型 HttpContext 的第一个参数。 public async Task InvokeAsync(HttpContext context) { //做一些操作 // Call the next delegate/middleware in the pipeline await _next(context); } }}//中间件扩展方法using Microsoft.AspNetCore.Builder;namespace Culture{ public static class RequestTestMiddlewareExtensions { public static IApplicationBuilder UseRequestTest( this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMiddleware(); } }}//调用中间件public class Startup{ public void Configure(IApplicationBuilder app) { app.UseRequestTest(); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); }}
对于asp.net core的中间件与koa.js,gin中间件,实现形式略有不同,但是终极目标只有一个,就是AOP,面向切面编程,减少代码量,不至于在某一个路由匹配的方法中去编写同样的代码。在asp.net core之前,还是asp.net的时候,也有类似的AOP实现,去继承各种FilterAttribute ,重写方法,如启用属性路由,创建自定义授权过滤器,创建自定义身份验证过滤器,模型验证过滤器
转载地址:http://kwudi.baihongyu.com/