Golang中間件設(shè)計(jì)示例詳解
什么是中間件
中間件:將這些非業(yè)務(wù)邏輯代碼抽象出來(lái),封裝好,提供接口給控制器使用
裝飾器模式:將最核心的代碼一層層裝飾,返回的時(shí)候一層層出來(lái)
動(dòng)手設(shè)計(jì)中間件
首先我們從之前的Controller開始,之前寫了一個(gè)可以超時(shí)的controller但是那是寫在了代碼里,我們能不能變成中間件為我們自動(dòng)去判斷超時(shí)呢!
首先在framework/timeout.go
寫下我們的中間件方法:
// 包裝所有注冊(cè)的Controller 然后前置方法加上錯(cuò)誤panic和超時(shí)控制 func TimeOutController(fun ControllerHandler, d time.Duration) ControllerHandler { return func(c *Context) error { finish := make(chan struct{}, 1) panicChan := make(chan interface{}, 1) context, cancel := context.WithTimeout(c, d) defer cancel() c.Request.WithContext(context) go func() { defer func() { if p := recover(); p != nil { panicChan <- p } }() fun(c) finish <- struct{}{} }() select { case p := <-panicChan: log.Println(p) c.ResponseWriter.WriteHeader(500) case <-finish: log.Println("finish") case <-context.Done(): c.SetHasTimeOut() c.ResponseWriter.Write([]byte("time out")) } return nil } }
但是這樣的調(diào)用的話就變成了
core.Get("/user/login", framework.TimeOutController(UserLoginController,time.Second*2))
如果有新的中間件要包裹,那豈不是顯示寫出來(lái)會(huì)很長(zhǎng),一層一層的!而且這個(gè)實(shí)現(xiàn),也只能為一個(gè)controller需要的時(shí)候去包裹一下,只是省了寫多個(gè)超時(shí)的代碼,但還是要自己顯示的調(diào)用!
使用流水線模式
當(dāng)不要嵌套之后,那我們就用一個(gè)數(shù)組將這些中間件都存起來(lái)然后順序自己執(zhí)行,這樣既不用調(diào)用,也不用擔(dān)心嵌套了
需要處理的地方:
- 第一個(gè)是在此次請(qǐng)求處理的入口處,即 Core 的 ServeHttp;
- 第二個(gè)是在每個(gè)中間件的邏輯代碼中,用于調(diào)用下個(gè)中間件。
代碼處理
framework/core.go
修改了Core
中添加了一個(gè)handlers包含中間件的方法
type Core struct { router map[string]*Tree handles []ControllerHandler // 因?yàn)榘虚g件 所以是多個(gè)集合 } // 在請(qǐng)求邏輯處理的函數(shù)中,添加上我們的next循環(huán)處理中間件的方法 func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) { ctx := NewContext(request, writer) handlers := c.FindRouteByRequest(request) if handlers== nil { ctx.Json(404, "router not found ") return } // 先設(shè)置該core添加的中間件方便next去調(diào)用執(zhí)行 ctx.SetHandler(handlers) if err:=ctx.Next();err!=nil{ ctx.Json(500, "server Interval") return } }
上面不僅是加上了這個(gè)結(jié)構(gòu)體成員變量,還要處理一下Get
等處理方法
將接收的函數(shù)ControllerHandler
改為可以接受多個(gè)!,然后處理過程將所有中間件都傳給addroute
中去設(shè)置在最后一個(gè)映射節(jié)點(diǎn)下!**這樣在我們請(qǐng)求對(duì)應(yīng)的路由方法時(shí),就回去執(zhí)行路由最后一個(gè)節(jié)點(diǎn)下的所有中間件方法!**下面是例子Get
方法:同理Post,Put,Delete
// 注冊(cè)Get方法 func (c *Core) Get(pattern string, handler ...ControllerHandler) { str :="" if strings.HasPrefix(pattern,"/"){ strs:=strings.SplitN(pattern,"/",2) str=strs[1] log.Println("去除首字符/",str) } url := strings.ToUpper(str) allHandlers:=append(c.handles,handler...) log.Println("進(jìn)來(lái)了",url) if err := c.router[GET].AddRoute(url, allHandlers); err != nil { log.Fatal("add router error:", err) } }
還要加一個(gè)方法方便我們?cè)谧?cè)函數(shù)的使用去使用中間件!
func (c *Core)Use(middlewares ...ControllerHandler){ c.handles=middlewares }
framework/group.go
其實(shí)和上面的core修改方向差不多,都是加上成員變量中間件集合!
type Group struct { core *Core // perfix string // 自身前綴 handler []ControllerHandler } //IGroup 代表前綴分組 type IGroup interface { Get(string, ...ControllerHandler) Post(string, ...ControllerHandler) Delete(string, ...ControllerHandler) Put(string, ...ControllerHandler) Use(middlewares ...ControllerHandler) } // 獲得中間件集合 func (g Group)getMiddlewares()[]ControllerHandler{ if g.handler==nil{ g.handler=make([]ControllerHandler,0) } return g.handler } // 支持傳入多個(gè)中間件 func (g Group) Get(s string, handler ...ControllerHandler) { url := g.perfix + s allHandlers := append(g.getMiddlewares(), handler...) g.core.Get(url, allHandlers...) }
也添加一個(gè)支持添加組的中間件
func (g *Group)Use(middlewares ...ControllerHandler){ g.handler=middlewares }
framework/node.go
因?yàn)橹С至丝梢詡魅攵鄠€(gè)中間件集合,那么node存放的處理器方法也需要改為切片,那么對(duì)應(yīng)下面有以下改動(dòng)!
// 代表節(jié)點(diǎn) type node struct { isLast bool // 代表這個(gè)節(jié)點(diǎn)是否可以成為最終的路由規(guī)則。 該節(jié)點(diǎn)是否能成為一 segment string // url 中的字符串,代表這個(gè)節(jié)點(diǎn)表示的路由中某個(gè)段的字符串 handler []ControllerHandler // 代表這個(gè)節(jié)點(diǎn)中包含的控制器,用于最終加載調(diào)用 childes []*node // 代表這個(gè)節(jié)點(diǎn)下的子節(jié)點(diǎn) }
將對(duì)應(yīng)的handler之前的代碼修改為支持切片多個(gè)即可!
func (tree *Tree) AddRoute(url string, handler []ControllerHandler) error {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->...}
framework/context.go
下面來(lái)看看context中的Next方法是如何寫的?
type Context struct { Request *http.Request ResponseWriter http.ResponseWriter hasTimeOut bool // 是否超時(shí)標(biāo)記位 writerMux *sync.Mutex // 當(dāng)前請(qǐng)求的handler鏈條 handlers []ControllerHandler index int // 請(qǐng)求調(diào)用到的方法下標(biāo) 每執(zhí)行一個(gè)向后+1 } /* Next() 函數(shù)會(huì)在框架的兩個(gè)地方被調(diào)用: 這里要注意,index 下標(biāo)表示當(dāng)前調(diào)用 Next 要執(zhí)行的控制器序列,它的初始值應(yīng)該為-1,每次調(diào)用都會(huì)自增 1,這樣才能保證第一次調(diào)用的時(shí)候 index 為 0,定位到控制器鏈條的下標(biāo)為 0 的控制器,即第一個(gè)控制器。 在框架文件夾 context.go 的初始化 Context 函數(shù)中,代碼如下: 第一個(gè)是在此次請(qǐng)求處理的入口處,即 Core 的 ServeHttp; 第二個(gè)是在每個(gè)中間件的邏輯代碼中,用于調(diào)用下個(gè)中間件 */ func (ctx *Context) Next() error { ctx.index++ if ctx.index < len(ctx.handlers) { if err := ctx.handlers[ctx.index](ctx); err != nil { return err } } return nil } // 設(shè)置可可執(zhí)行方法 func (ctx *Context)SetHandler(fn []ControllerHandler){ if ctx.handlers==nil{ ctx.handlers=make([]ControllerHandler,0) } ctx.handlers=append(ctx.handlers,fn...) }
中間件例子
新建一個(gè)middlerware文件夾,然后里面創(chuàng)建文件寫下我們的文件夾
比如我們這個(gè)處理錯(cuò)誤的中間件,
// recovery機(jī)制,將協(xié)程中的函數(shù)異常進(jìn)行捕獲 func Recovery() framework.ControllerHandler { // 使用函數(shù)回調(diào) return func(c *framework.Context) error { // 核心在增加這個(gè)recover機(jī)制,捕獲c.Next()出現(xiàn)的panic defer func() { if err := recover(); err != nil { c.Json(500, err) } }() // 使用next執(zhí)行具體的業(yè)務(wù)邏輯 c.Next() return nil } } // 例子1 func Test1()framework.ControllerHandler{ return func(c *framework.Context) error { log.Println("middleware test1") c.Next() log.Println("middleware end test1") return nil } } // 例子2 func Test2()framework.ControllerHandler{ return func(c *framework.Context) error { log.Println("middleware test2") c.Next() log.Println("middleware end test2") return nil } }
實(shí)際使用
可以如同像gin
框架一樣進(jìn)行用use
注冊(cè)進(jìn)我們的中間件!實(shí)戰(zhàn)結(jié)束~
func registerRouter(core *framework.Core) { print(111) // 設(shè)置控制器 core.Use(middleware.Recovery()) core.Get("/foo", FooController) core.Get("/user/login", framework.TimeOutController(UserLoginController,time.Second*2)) core.Use( middleware.Test1(), middleware.Test2(), ) subjectApi := core.Group("/subject") subjectApi.Use(middleware.Test2()) { subjectApi.Get("/list/all", SubjectListController) subjectApi.Post("/add", SubjectListController) subjectApi.Delete("/:id", SubjectListController) subjectApi.Put("/:id", SubjectListController) subjectApi.Get("/:id", SubjectListController) } }
到此這篇關(guān)于Golang中間件設(shè)計(jì)提升HTTP服務(wù)的文章就介紹到這了,更多相關(guān)Golang中間件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中的關(guān)鍵字(defer、:=、go?func())詳細(xì)解讀
這篇文章主要介紹了Golang中的關(guān)鍵字(defer、:=、go?func())詳細(xì)解讀,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04Go標(biāo)準(zhǔn)庫(kù)http與fasthttp服務(wù)端性能對(duì)比場(chǎng)景分析
這篇文章主要介紹了Go標(biāo)準(zhǔn)庫(kù)http與fasthttp服務(wù)端性能比較,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06簡(jiǎn)化Go開發(fā)提高生產(chǎn)力的強(qiáng)大工具及使用詳解
作為?Go?開發(fā)人員,應(yīng)該都知道維持簡(jiǎn)潔高效開發(fā)工作流程的重要性,為了提高工作效率和代碼質(zhì)量,簡(jiǎn)化開發(fā)流程并自動(dòng)執(zhí)行重復(fù)性任務(wù)至關(guān)重要,在本文中,我們將探討一些強(qiáng)大的工具和技術(shù),它們將簡(jiǎn)化?Go?開發(fā)過程,助力您的編碼之旅2023-10-10Go語(yǔ)言HTTPServer開發(fā)的六種方式小結(jié)
Golang的Server開發(fā)顯得非常簡(jiǎn)單,有很多種方式,本文就介紹了Go語(yǔ)言HTTPServer開發(fā)的六種方式,具有一定的參考價(jià)值,感興趣的可以了解一下2021-11-11golang hack插件開發(fā)動(dòng)態(tài)鏈接庫(kù)實(shí)例探究
這篇文章主要為大家介紹了golang hack插件開發(fā)動(dòng)態(tài)鏈接庫(kù)實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Windows10系統(tǒng)下安裝Go環(huán)境詳細(xì)步驟
Go語(yǔ)言是谷歌推出的一款全新的編程語(yǔ)言,可以在不損失應(yīng)用程序性能的情況下極大的降低代碼的復(fù)雜性,這篇文章主要給大家介紹了關(guān)于Windows10系統(tǒng)下安裝Go環(huán)境的詳細(xì)步驟,需要的朋友可以參考下2023-11-11Go語(yǔ)言kube-scheduler深度剖析開發(fā)之scheduler初始化
這篇文章主要介紹了Go語(yǔ)言kube-scheduler深度剖析開發(fā)之scheduler初始化實(shí)現(xiàn)過程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04