掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流
?GOFrame?提供了優(yōu)雅的中間件請求控制方式,該方式也是主流的?WebServer?提供的請求流程控制方式,基于中間件設計可以為?WebServer?提供更靈活強大的插件機制。經(jīng)典的中間件洋蔥模型:

站在用戶的角度思考問題,與客戶深入溝通,找到望江網(wǎng)站設計與望江網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設計與互聯(lián)網(wǎng)技術結合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站設計、網(wǎng)站建設、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、主機域名、虛擬主機、企業(yè)郵箱。業(yè)務覆蓋望江地區(qū)。
中間件的定義和普通HTTP執(zhí)行方法?HandlerFunc?一樣,但是可以在?Request?參數(shù)中使用?Middleware?屬性對象來控制請求流程。
我們拿一個跨域請求的中間件定義來示例說明一下:
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
可以看到在該中間件中執(zhí)行完成跨域請求處理的邏輯后,使用?r.Middleware.Next()?方法進一步執(zhí)行下一個流程;如果這個時候直接退出不調用?r.Middleware.Next()?方法的話,將會退出后續(xù)的執(zhí)行流程(例如可以用于請求的鑒權處理)。
中間件的類型分為兩種:前置中間件和后置中間件。前置即在路由服務函數(shù)調用之前調用,后置即在其后調用。
其定義類似于:
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)
全局中間件是可以獨立使用的請求攔截方法,通過路由規(guī)則的方式進行注冊,綁定到?Server?上,由于中間件需要執(zhí)行請求攔截操作,因此往往是使用"模糊匹配"或者"命名匹配"規(guī)則。
全局中間件僅對動態(tài)請求攔截有效,無法攔截靜態(tài)文件請求。
func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup
分組路由中注冊的中間件綁定到當前分組路由中的所有的服務請求上,當服務請求被執(zhí)行前會調用到其綁定的中間件方法。 分組路由僅有一個?Middleware?的中間件注冊方法。分組路由中間件與全局中間件不同之處在于,分組路由中間件無法獨立使用,必須在分組路由注冊中使用,并且綁定到當前分組路由中所有的路由上作為路由方法的一部分。
由于全局中間件也是通過路由規(guī)則執(zhí)行,那么也會存在執(zhí)行優(yōu)先級:
這里的建議來參考于?gRPC?的攔截器設計,沒有過多的路由控制,僅在一個地方同一個方法統(tǒng)一注冊。往往越簡單,越容易理解,也便于長期維護。
分組路由中間件是綁定到分組路由上的服務方法,不存在路由規(guī)則匹配,因此只會按照注冊的先后順序執(zhí)行。
第一個例子,也是比較常見的功能需求。
我們需要在所有API請求之前增加允許跨域請求的返回?Header?信息,該功能可以通過中間件實現(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?路由下所有的服務函數(shù)中。隨后我們可以通過請求 http://127.0.0.1:8199/api.v2/user/list 來查看允許跨域請求的?Header?信息是否有返回。
我們在跨域請求中間件的基礎之上加上鑒權中間件。
為了簡化示例,在該示例中,當請求帶有?token?參數(shù),并且參數(shù)值為?123456?時可以通過鑒權,并且允許跨域請求,執(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
|---------|---------|---------|--------|-------------------|-------------------|-----------------------------------------|
可以看到,我們的服務方法綁定了兩個中間件,跨域中間件和而鑒權中間件。 請求時將會按照中間件注冊的先后順序,先執(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 對比來查看效果。
使用分組路由中間件可以很方便地添加鑒權例外,因為只有當前分組路由下注冊的服務方法才會綁定并執(zhí)行鑒權中間件,否則并不會執(zhí)行到鑒權中間件。
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?路由的服務方法綁定了鑒權中間件?main.MiddlewareAuth?,而?/admin/login?路由的服務方法并沒有添加鑒權處理。 隨后我們訪問以下URL查看效果:
基于中間件,我們可以在服務函數(shù)執(zhí)行完成后做一些后置判斷的工作,特別是統(tǒng)一數(shù)據(jù)格式返回、結果處理、錯誤判斷等等。這種需求我們可以使用后置的中間件類型來實現(xià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 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("哎喲我去,服務器居然開小差了,請稍后再試吧!")
}
}
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)錯誤,如果有則返回固定的提示信息,而不是把敏感的報錯信息展示給用戶。當然,在真實的項目場景中,往往還有是需要解析返回緩沖區(qū)的數(shù)據(jù),例如?JSON?數(shù)據(jù),根據(jù)當前的執(zhí)行結果進行封裝返回固定的數(shù)據(jù)格式等等。
執(zhí)行該示例后,訪問 http://127.0.0.1:8199/api.v2/user/list?token=123456 查看效果。
我們來更進一步完善一下以上示例,我們將請求日志包括狀態(tài)碼輸出到終端。這里我們必須得使用”全局中間件”了,這樣可以攔截處理到所有的服務請求,甚至?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("啊!我出錯了!")
})
})
s.SetPort(8199)
s.Run()
}
可以看到,我們注冊了一個全局的日志處理中間件以及跨域中間件,而鑒權中間件是注冊到?/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 對比來查看效果,并查看終端的日志輸出情況。

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流