• 【对比学习】koa.js、Gin与asp.net core——中间件


    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 ,重写方法,如启用属性路由,创建自定义授权过滤器,创建自定义身份验证过滤器,模型验证过滤器

  • 相关阅读:
    Ubuntu 14.04 apt-get update失效解决(转)
    linux内核动态调试技术
    Ubuntu18.04开机挂载硬盘
    valgrind memcheck使用方法及效果(转)
    网络测试工具netperf(转)
    开发pc端项目可支持多个窗口登陆
    sql常用语句
    包装类和基础类的区别
    @param
    前台后台的顺序
  • 原文地址:https://www.cnblogs.com/RandyField/p/12258189.html
Copyright © 2020-2023  润新知