Golang中間件設(shè)計(jì)示例詳解
什么是中間件
中間件:將這些非業(yè)務(wù)邏輯代碼抽象出來,封裝好,提供接口給控制器使用
裝飾器模式:將最核心的代碼一層層裝飾,返回的時(shí)候一層層出來
動(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))如果有新的中間件要包裹,那豈不是顯示寫出來會(huì)很長,一層一層的!而且這個(gè)實(shí)現(xiàn),也只能為一個(gè)controller需要的時(shí)候去包裹一下,只是省了寫多個(gè)超時(shí)的代碼,但還是要自己顯示的調(diào)用!
使用流水線模式
當(dāng)不要嵌套之后,那我們就用一個(gè)數(shù)組將這些中間件都存起來然后順序自己執(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)來了",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
下面來看看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)文章
go內(nèi)存緩存BigCache封裝Entry源碼解讀
這篇文章主要為大家介紹了go內(nèi)存緩存BigCache封裝Entry源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Golang基礎(chǔ)教程之字符串string實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于Golang基礎(chǔ)教程之字符串string的相關(guān)資料,需要的朋友可以參考下2022-07-07
Golang?中的?strconv?包常用函數(shù)及用法詳解
strconv是Golang中一個(gè)非常常用的包,主要用于字符串和基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換,這篇文章主要介紹了Golang中的strconv包,需要的朋友可以參考下2023-06-06
Go語言Swagger實(shí)現(xiàn)為項(xiàng)目生成 API 文檔
Swagger 是一個(gè)基于 OpenAPI 規(guī)范設(shè)計(jì)的工具,用于為 RESTful API 生成交互式文檔,下面小編就來介紹一下如何在 Go 項(xiàng)目中集成 Swagger,特別是結(jié)合 Gin 框架生成 API 文檔2025-03-03
Golang跨平臺(tái)GUI框架Fyne的使用教程詳解
Go 官方?jīng)]有提供標(biāo)準(zhǔn)的 GUI 框架,在 Go 實(shí)現(xiàn)的幾個(gè) GUI 庫中,Fyne 算是最出色的,它有著簡潔的API、支持跨平臺(tái)能力,且高度可擴(kuò)展,下面我們就來看看它的具體使用吧2024-03-03
Golang中空的切片轉(zhuǎn)化成 JSON 后變?yōu)?nbsp;null 問題的解決方案
在 Golang 中,經(jīng)常需要將其他類型(例如 slice、map、struct 等類型)的數(shù)據(jù)轉(zhuǎn)化為 JSON 格式,有時(shí)候轉(zhuǎn)化的結(jié)果并不是預(yù)期中的,例如將一個(gè)空的切片轉(zhuǎn)化為 JSON 時(shí),會(huì)變成"null",所以本文將給大家介紹一下解決方法,需要的朋友可以參考下2023-09-09
Go語言開發(fā)編程規(guī)范命令風(fēng)格代碼格式
這篇文章主要為大家介紹了Go語言開發(fā)編程規(guī)范命令風(fēng)格代碼格式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Go語言實(shí)現(xiàn)定時(shí)器的原理及使用詳解
這篇文章主要為大家詳細(xì)介紹了Go語言實(shí)現(xiàn)定時(shí)器的兩種方法:一次性定時(shí)器(Timer)和周期性定時(shí)器(Ticker),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12

