web框架中间件对比
编程语言都有所不同,各个语言解决同一类问题而设计的框架,确有共通之处,毕竟是解决同一类问题,面临的挑战大致相同,比如身份验证,api授权等等,鄙人对node.js,golang,.net core有所涉猎,对各自的web框架进行学习的过程中发现了确实有相似之处。下面即对node.js的koa、golang的gin与.net core的asp.net core三种不同的web后端框架的中间件做一个分析对比
Node-Koa.js
应用级中间件
//如果不写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'))
中间件执行顺序
洋葱执行:从上到下依次执行,匹配路由响应,再返回至中间件进行执行中间件,【先从外向内,然后再从内向外】
Golang-Gin
钩子(Hook)函数,中间件函数
定义中间件
package main import( "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 main
import(
"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
",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
}
中间件中使用goroutine
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文
(c *gin.Context)
必须使用其只读副本c.Copy()
,否则会出现线程安全问题。
.Net Core-Asp.net core
创建中间件管道
使用IApplicationBuilder 创建中间件管道
//Run public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); } } //Use - Run public 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. <p>");
});
}
//请求会匹配 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<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量 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. <p>");
});
}
}
//UseWhen 也是基于给定谓词的结果创建请求管道分支。 与 MapWhen 不同的是,如果这个分支发生短路或包含终端中间件,则会重新加入主管道:
public class Startup
{
private readonly ILogger<Startup> _logger;
public Startup(ILogger<Startup> 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");
});
}
中间件类+中间件扩展方法+UseXX
在
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<RequestTestMiddleware>();
}
}
}
//调用中间件
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestTest();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
.Net -Asp.Net
对于asp.net core的中间件与koa.js,gin中间件,实现形式略有不同,但是终极目标只有一个,就是AOP,面向切面编程,减少代码量,不至于在某一个路由匹配的方法中去编写同样的代码。在asp.net core之前,还是asp.net的时候,也有类似的AOP实现,去继承各种FilterAttribute ,重写方法,如启用属性路由,创建自定义授权过滤器,创建自定义身份验证过滤器,模型验证过滤器