【对比学习】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=404ctx.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?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 }中間件中使用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?pipelineawait?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?pipelineawait?_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 ,重寫方法,如啟用屬性路由,創建自定義授權過濾器,創建自定義身份驗證過濾器,模型驗證過濾器
總結
以上是生活随笔為你收集整理的【对比学习】koa.js、Gin与asp.net core——中间件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多款主流编程语言,哪款开发软件最安全?
- 下一篇: 2020 .NET 开发者峰会顺利在苏州