新聞中心
?GOFrame?提供了優(yōu)雅的中間件請求控制方式,該方式也是主流的?WebServer?提供的請求流程控制方式,基于中間件設(shè)計(jì)可以為?WebServer?提供更靈活強(qiáng)大的插件機(jī)制。經(jīng)典的中間件洋蔥模型:

站在用戶的角度思考問題,與客戶深入溝通,找到望江網(wǎng)站設(shè)計(jì)與望江網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、主機(jī)域名、虛擬主機(jī)、企業(yè)郵箱。業(yè)務(wù)覆蓋望江地區(qū)。
中間件定義
中間件的定義和普通HTTP執(zhí)行方法?HandlerFunc?一樣,但是可以在?Request?參數(shù)中使用?Middleware?屬性對象來控制請求流程。
我們拿一個(gè)跨域請求的中間件定義來示例說明一下:
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
可以看到在該中間件中執(zhí)行完成跨域請求處理的邏輯后,使用?r.Middleware.Next()?方法進(jìn)一步執(zhí)行下一個(gè)流程;如果這個(gè)時(shí)候直接退出不調(diào)用?r.Middleware.Next()?方法的話,將會(huì)退出后續(xù)的執(zhí)行流程(例如可以用于請求的鑒權(quán)處理)。
中間件類型
中間件的類型分為兩種:前置中間件和后置中間件。前置即在路由服務(wù)函數(shù)調(diào)用之前調(diào)用,后置即在其后調(diào)用。
前置中間件
其定義類似于:
func Middleware(r *ghttp.Request) {
// 中間件處理邏輯
r.Middleware.Next()
}
后置中間件
其定義類似于:
func Middleware(r *ghttp.Request) {
r.Middleware.Next()
// 中間件處理邏輯
}
中間件注冊
中間件的注冊有多種方式,參考接口文檔: https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp
全局中間件
func (s *Server) Use(handlers ...HandlerFunc)
全局中間件是可以獨(dú)立使用的請求攔截方法,通過路由規(guī)則的方式進(jìn)行注冊,綁定到?Server?上,由于中間件需要執(zhí)行請求攔截操作,因此往往是使用"模糊匹配"或者"命名匹配"規(guī)則。
全局中間件僅對動(dòng)態(tài)請求攔截有效,無法攔截靜態(tài)文件請求。
分組路由中間件
func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup
分組路由中注冊的中間件綁定到當(dāng)前分組路由中的所有的服務(wù)請求上,當(dāng)服務(wù)請求被執(zhí)行前會(huì)調(diào)用到其綁定的中間件方法。 分組路由僅有一個(gè)?Middleware?的中間件注冊方法。分組路由中間件與全局中間件不同之處在于,分組路由中間件無法獨(dú)立使用,必須在分組路由注冊中使用,并且綁定到當(dāng)前分組路由中所有的路由上作為路由方法的一部分。
執(zhí)行優(yōu)先級
全局中間件
由于全局中間件也是通過路由規(guī)則執(zhí)行,那么也會(huì)存在執(zhí)行優(yōu)先級:
- 首先,由于全局中間件是基于模糊路由匹配,因此當(dāng)同一個(gè)路由匹配到多個(gè)中間件時(shí),會(huì)按照路由的深度優(yōu)先規(guī)則執(zhí)行,具體請查看路由章節(jié);
- 其次,同一個(gè)路由規(guī)則下,會(huì)按照中間件的注冊先后順序執(zhí)行,中間件的注冊方法也支持同時(shí)按照先后順序注冊多個(gè)中間件;
- 最后,為避免優(yōu)先級混淆和后續(xù)管理,建議將所有中間件放到同一個(gè)地方進(jìn)行先后順序注冊來控制執(zhí)行優(yōu)先級;
這里的建議來參考于?gRPC?的攔截器設(shè)計(jì),沒有過多的路由控制,僅在一個(gè)地方同一個(gè)方法統(tǒng)一注冊。往往越簡單,越容易理解,也便于長期維護(hù)。
分組路由中間件
分組路由中間件是綁定到分組路由上的服務(wù)方法,不存在路由規(guī)則匹配,因此只會(huì)按照注冊的先后順序執(zhí)行。
中間件示例
示例1,允許跨域請求
第一個(gè)例子,也是比較常見的功能需求。
我們需要在所有API請求之前增加允許跨域請求的返回?Header?信息,該功能可以通過中間件實(shí)現(xiàn):
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS)
group.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Writeln("list")
})
})
s.SetPort(8199)
s.Run()
}
執(zhí)行后,終端打印出路由表信息如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|-------------------|-------------------|---------------------|
default | default | :8199 | ALL | /api.v2/user/list | main.main.func1.1 | main.MiddlewareCORS
|---------|---------|---------|--------|-------------------|-------------------|---------------------|
這里我們使用?group.Middleware(MiddlewareCORS)?將跨域中間件通過分組路由的形式注冊綁定到了?/api.v2?路由下所有的服務(wù)函數(shù)中。隨后我們可以通過請求 http://127.0.0.1:8199/api.v2/user/list 來查看允許跨域請求的?Header?信息是否有返回。
示例2,請求鑒權(quán)處理
我們在跨域請求中間件的基礎(chǔ)之上加上鑒權(quán)中間件。
為了簡化示例,在該示例中,當(dāng)請求帶有?token?參數(shù),并且參數(shù)值為?123456?時(shí)可以通過鑒權(quán),并且允許跨域請求,執(zhí)行請求方法;否則返回?403 Forbidden?狀態(tài)碼。
package main
import (
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token.String() == "123456" {
r.Response.Writeln("auth")
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.Writeln("cors")
r.Response.CORSDefault()
r.Middleware.Next()
}
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS, MiddlewareAuth)
group.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Writeln("list")
})
})
s.SetPort(8199)
s.Run()
}
執(zhí)行后,終端打印出路由表信息如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|-------------------|-------------------|-----------------------------------------|
default | default | :8199 | ALL | /api.v2/user/list | main.main.func1.1 | main.MiddlewareCORS,main.MiddlewareAuth
|---------|---------|---------|--------|-------------------|-------------------|-----------------------------------------|
可以看到,我們的服務(wù)方法綁定了兩個(gè)中間件,跨域中間件和而鑒權(quán)中間件。 請求時(shí)將會(huì)按照中間件注冊的先后順序,先執(zhí)行?MiddlewareCORS?全局中間件,再執(zhí)行?MiddlewareAuth?分組中間件。 隨后我們可以通過請求 http://127.0.0.1:8199/api.v2/user/list 和 http://127.0.0.1:8199/api.v2/user/list?token=123456 對比來查看效果。
示例3,鑒權(quán)例外處理
使用分組路由中間件可以很方便地添加鑒權(quán)例外,因?yàn)橹挥挟?dāng)前分組路由下注冊的服務(wù)方法才會(huì)綁定并執(zhí)行鑒權(quán)中間件,否則并不會(huì)執(zhí)行到鑒權(quán)中間件。
package main
import (
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token.String() == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func main() {
s := g.Server()
s.Group("/admin", func(group *ghttp.RouterGroup) {
group.ALL("/login", func(r *ghttp.Request) {
r.Response.Writeln("login")
})
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth)
group.ALL("/dashboard", func(r *ghttp.Request) {
r.Response.Writeln("dashboard")
})
})
})
s.SetPort(8199)
s.Run()
}
執(zhí)行后,終端打印出路由表信息如下:
SERVER | ADDRESS | DOMAIN | METHOD | P | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|---|------------------|---------------------|---------------------|
default | :8199 | default | ALL | 2 | /admin/dashboard | main.main.func1.2.1 | main.MiddlewareAuth
|---------|---------|---------|--------|---|------------------|---------------------|---------------------|
default | :8199 | default | ALL | 2 | /admin/login | main.main.func1.1 |
|---------|---------|---------|--------|---|------------------|---------------------|---------------------|
可以看到,只有?/admin/dashboard?路由的服務(wù)方法綁定了鑒權(quán)中間件?main.MiddlewareAuth?,而?/admin/login?路由的服務(wù)方法并沒有添加鑒權(quán)處理。 隨后我們訪問以下URL查看效果:
- http://127.0.0.1:8199/admin/login
- http://127.0.0.1:8199/admin/dashboard
- http://127.0.0.1:8199/admin/dashboard?token=123456
示例4,統(tǒng)一的錯(cuò)誤處理
基于中間件,我們可以在服務(wù)函數(shù)執(zhí)行完成后做一些后置判斷的工作,特別是統(tǒng)一數(shù)據(jù)格式返回、結(jié)果處理、錯(cuò)誤判斷等等。這種需求我們可以使用后置的中間件類型來實(shí)現(xiàn)。我們使用一個(gè)簡單的例子,用來演示如何使用中間件對所有的接口請求做后置判斷處理,作為一個(gè)拋磚引玉作用。
package main
import (
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token.String() == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.Status >= http.StatusInternalServerError {
r.Response.ClearBuffer()
r.Response.Writeln("哎喲我去,服務(wù)器居然開小差了,請稍后再試吧!")
}
}
func main() {
s := g.Server()
s.Use(MiddlewareCORS)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth, MiddlewareErrorHandler)
group.ALL("/user/list", func(r *ghttp.Request) {
panic("db error: sql is xxxxxxx")
})
})
s.SetPort(8199)
s.Run()
}
執(zhí)行后,終端打印出路由表信息如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|
default | default | :8199 | ALL | /* | main.MiddlewareCORS | GLOBAL MIDDLEWARE
|---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|
default | default | :8199 | ALL | /api.v2/user/list | main.main.func1.1 | main.MiddlewareAuth,main.MiddlewareErrorHandler
|---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|
在該示例中,我們在后置中間件中判斷有無系統(tǒng)錯(cuò)誤,如果有則返回固定的提示信息,而不是把敏感的報(bào)錯(cuò)信息展示給用戶。當(dāng)然,在真實(shí)的項(xiàng)目場景中,往往還有是需要解析返回緩沖區(qū)的數(shù)據(jù),例如?JSON?數(shù)據(jù),根據(jù)當(dāng)前的執(zhí)行結(jié)果進(jìn)行封裝返回固定的數(shù)據(jù)格式等等。
執(zhí)行該示例后,訪問 http://127.0.0.1:8199/api.v2/user/list?token=123456 查看效果。
示例5,自定義日志處理
我們來更進(jìn)一步完善一下以上示例,我們將請求日志包括狀態(tài)碼輸出到終端。這里我們必須得使用”全局中間件”了,這樣可以攔截處理到所有的服務(wù)請求,甚至?404?請求。
package main
import (
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token.String() == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func MiddlewareLog(r *ghttp.Request) {
r.Middleware.Next()
errStr := ""
if err := r.GetError(); err != nil {
errStr = err.Error()
}
g.Log().Println(r.Response.Status, r.URL.Path, errStr)
}
func main() {
s := g.Server()
s.SetConfigWithMap(g.Map{
"AccessLogEnabled": false,
"ErrorLogEnabled": false,
})
s.Use(MiddlewareLog, MiddlewareCORS)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth)
group.ALL("/user/list", func(r *ghttp.Request) {
panic("??!我出錯(cuò)了!")
})
})
s.SetPort(8199)
s.Run()
}
可以看到,我們注冊了一個(gè)全局的日志處理中間件以及跨域中間件,而鑒權(quán)中間件是注冊到?/api.v2?路由下。
執(zhí)行后,我們可以通過請求 http://127.0.0.1:8199/api.v2/user/list 和 http://127.0.0.1:8199/api.v2/user/list?token=123456 對比來查看效果,并查看終端的日志輸出情況。
當(dāng)前題目:創(chuàng)新互聯(lián)GoFrame教程:GoFrame路由管理-中間件/攔截器
文章來源:http://www.5511xx.com/article/dhssese.html


咨詢
建站咨詢
