【对比学习】koa.js、Gin与asp.net core——中间件
web框架中間件對(duì)比
編程語言都有所不同,各個(gè)語言解決同一類問題而設(shè)計(jì)的框架,確有共通之處,畢竟是解決同一類問題,面臨的挑戰(zhàn)大致相同,比如身份驗(yàn)證,api授權(quán)等等,鄙人對(duì)node.js,golang,.net core有所涉獵,對(duì)各自的web框架進(jìn)行學(xué)習(xí)的過程中發(fā)現(xiàn)了確實(shí)有相似之處。下面即對(duì)node.js的koa、golang的gin與.net core的asp.net core三種不同的web后端框架的中間件做一個(gè)分析對(duì)比
Node-Koa.js
應(yīng)用級(jí)中間件
//如果不寫next,就不會(huì)向下匹配--匹配任何一個(gè)路由 app.use(async(ctx,next)=>{console.log(new?Date())await?next(); })路由級(jí)中間件
?router.get('/news',async(ctx,next)=>{console.log("this?is?news")await?next();})錯(cuò)誤處理中間件
app.use(async(ctx,next)=>{//應(yīng)用級(jí)中間件?都需要執(zhí)行/*1.執(zhí)行若干代碼*/next();//2.執(zhí)行next()?匹配其他路由//4.再執(zhí)行if(ctx.status==404){ctx.status=404ctx.body="這是一個(gè)404"}else{console.log(ctx.url)} })//3.匹配下面的路由router.get('/news',async(ctx)=>{console.log("this?is?news")ctx.body="這是一個(gè)新聞頁面"})第三方中間件
靜態(tài)資源中間件為例:靜態(tài)資源地址沒有路由匹配,盲目引入靜態(tài)資源,會(huì)報(bào)404.
//安裝 npm?install?koa-static?--save//使用 //引入 const?static=require('koa-static') //使用 app.use(static('static'))?//去static文件目錄中將中找文件,如果能找到對(duì)應(yīng)的文件,找不到就next()app.use(static(__dirname+'/static'))app.use(static(__dirname+'/public'))中間件執(zhí)行順序
洋蔥執(zhí)行:從上到下依次執(zhí)行,匹配路由響應(yīng),再返回至中間件進(jìn)行執(zhí)行中間件,【先從外向內(nèi),然后再從內(nèi)向外】
Golang-Gin
鉤子(Hook)函數(shù),中間件函數(shù)
定義中間件
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()//調(diào)用后續(xù)的處理函數(shù)//c.Abort()//阻止調(diào)用后續(xù)的處理函數(shù)fmt.Println("m1?out...") }注冊(cè)中間件
全局注冊(cè)-某個(gè)路由單獨(dú)注冊(cè)-路由組注冊(cè)
package?mainimport("github.com/gin-gonic/gin" )func?main(){r:=gin.Default()r.GET("/index",func(c?*gin.Context){//...})//某個(gè)路由單獨(dú)注冊(cè)--也可以取名為路由級(jí)注冊(cè)中間件r.GET("/test1",m1,func(c?*gin.Context){//...})//路由組注冊(cè)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()//調(diào)用后續(xù)的處理函數(shù)//c.Abort()//阻止調(diào)用后續(xù)的處理函數(shù)//return?連下方的fmt.Println都不執(zhí)行了,立即返回fmt.Println("m1?out...") }r.Use(m1)//全局注冊(cè)//多個(gè)中間件注冊(cè) r.Use(m1,m2)中間件執(zhí)行順序
與koa中間件執(zhí)行順序一致
中間件通常寫法-閉包
func?authMiddleware(doCheck?bool)?gin.HandlerFunc{//連接數(shù)據(jù)庫//或準(zhǔn)備工作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 }中間件中使用goroutine
當(dāng)在中間件或handler中啟動(dòng)新的goroutine時(shí),不能使用原始的上下文(c *gin.Context) 必須使用其只讀副本c.Copy(),否則會(huì)出現(xiàn)線程安全問題。
.Net Core-Asp.net core
創(chuàng)建中間件管道
使用IApplicationBuilder 創(chuàng)建中間件管道
//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.");});} }//這個(gè)Use是不是跟koa的應(yīng)用級(jí)中間件很像創(chuàng)建中間件管道分支
Map 擴(kuò)展用作約定來創(chuàng)建管道分支。Map 基于給定請(qǐng)求路徑的匹配項(xiàng)來創(chuàng)建請(qǐng)求管道分支。如果請(qǐng)求路徑以給定路徑開頭,則執(zhí)行分支。koa和gin中路由匹配就是map這種,當(dāng)不使用內(nèi)置的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>");});}//請(qǐng)求會(huì)匹配?map1...map2...沒匹配到路由的統(tǒng)統(tǒng)會(huì)執(zhí)行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 基于給定謂詞的結(jié)果創(chuàng)建請(qǐng)求管道分支。Func<HttpContext, bool>?類型的任何謂詞均可用于將請(qǐng)求映射到管道的新分支。?在以下示例中,謂詞用于檢測查詢字符串變量 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 也是基于給定謂詞的結(jié)果創(chuàng)建請(qǐng)求管道分支。?與 MapWhen 不同的是,如果這個(gè)分支發(fā)生短路或包含終端中間件,則會(huì)重新加入主管道: 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.");});} }內(nèi)置中間件
public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env) {if?(env.IsDevelopment()){//開發(fā)人員異常頁中間件?報(bào)告應(yīng)用運(yùn)行時(shí)錯(cuò)誤app.UseDeveloperExceptionPage();//數(shù)據(jù)庫錯(cuò)誤頁中間件報(bào)告數(shù)據(jù)庫運(yùn)行時(shí)錯(cuò)誤app.UseDatabaseErrorPage();}else{//異常處理程序中間件app.UseExceptionHandler("/Error");//http嚴(yán)格傳輸安全協(xié)議中間件app.UseHsts();}//HTTPS重定向中間件app.UseHttpsRedirection();//靜態(tài)文件中間件app.UseStaticFiles();//Cookie策略中間件app.UseCookiePolicy();//路由中間件app.UseRouting();//身份驗(yàn)證中間件app.UseAuthentication();//授權(quán)中間件app.UseAuthorization();//會(huì)話中間件-如果使用session,就需要把cookie策略中間件先使用了,再引入session中間件,再引入mvc中間件,畢竟session是依賴cookie實(shí)現(xiàn)的app.UseSession();//終結(jié)點(diǎn)路由中間件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?pipelineawait?next();});app.Run(async?(context)?=>{await?context.Response.WriteAsync($"Hello?world");});}中間件類+中間件擴(kuò)展方法+UseXX
在Startup.Configure直接編碼,當(dāng)定義多個(gè)中間件,代碼難免變得臃腫,不利于維護(hù),看看內(nèi)置的中間件,app.UseAuthentication();多簡潔,查看asp.net core源碼,內(nèi)置的中間件都是一個(gè)中間件類xxMiddleware.cs 一個(gè)擴(kuò)展方法 xxMiddlewareExtensions.cs ?然后在Startup.Configure 中使用擴(kuò)展方法調(diào)用Usexx()
using?Microsoft.AspNetCore.Http; using?System.Globalization; using?System.Threading.Tasks;namespace?Culture {public?class?RequestTestMiddleware{private?readonly?RequestDelegate?_next;//具有類型為?RequestDelegate?的參數(shù)的公共構(gòu)造函數(shù)public?RequestTestMiddleware(RequestDelegate?next){_next?=?next;}//名為 Invoke 或 InvokeAsync 的公共方法。?此方法必須://返回 Task。//接受類型 HttpContext 的第一個(gè)參數(shù)。public?async?Task?InvokeAsync(HttpContext?context){//做一些操作//?Call?the?next?delegate/middleware?in?the?pipelineawait?_next(context);}} }//中間件擴(kuò)展方法 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>();}} }//調(diào)用中間件 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
對(duì)于asp.net core的中間件與koa.js,gin中間件,實(shí)現(xiàn)形式略有不同,但是終極目標(biāo)只有一個(gè),就是AOP,面向切面編程,減少代碼量,不至于在某一個(gè)路由匹配的方法中去編寫同樣的代碼。在asp.net core之前,還是asp.net的時(shí)候,也有類似的AOP實(shí)現(xiàn),去繼承各種FilterAttribute ,重寫方法,如啟用屬性路由,創(chuàng)建自定義授權(quán)過濾器,創(chuàng)建自定義身份驗(yàn)證過濾器,模型驗(yàn)證過濾器
總結(jié)
以上是生活随笔為你收集整理的【对比学习】koa.js、Gin与asp.net core——中间件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多款主流编程语言,哪款开发软件最安全?
- 下一篇: 2020年终回顾:时间会回答成长,成长会