W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
?GoFrame
?提供了優(yōu)雅的中間件請(qǐng)求控制方式,該方式也是主流的?WebServer
?提供的請(qǐng)求流程控制方式,基于中間件設(shè)計(jì)可以為?WebServer
?提供更靈活強(qiáng)大的插件機(jī)制。經(jīng)典的中間件洋蔥模型:
中間件的定義和普通HTTP執(zhí)行方法?HandlerFunc
?一樣,但是可以在?Request
?參數(shù)中使用?Middleware
?屬性對(duì)象來控制請(qǐng)求流程。
我們拿一個(gè)跨域請(qǐng)求的中間件定義來示例說明一下:
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
可以看到在該中間件中執(zhí)行完成跨域請(qǐng)求處理的邏輯后,使用?r.Middleware.Next()
?方法進(jìn)一步執(zhí)行下一個(gè)流程;如果這個(gè)時(shí)候直接退出不調(diào)用?r.Middleware.Next()
?方法的話,將會(huì)退出后續(xù)的執(zhí)行流程(例如可以用于請(qǐng)求的鑒權(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()
// 中間件處理邏輯
}
中間件的注冊(cè)有多種方式,參考接口文檔: https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp
func (s *Server) Use(handlers ...HandlerFunc)
全局中間件是可以獨(dú)立使用的請(qǐng)求攔截方法,通過路由規(guī)則的方式進(jìn)行注冊(cè),綁定到?Server
?上,由于中間件需要執(zhí)行請(qǐng)求攔截操作,因此往往是使用"模糊匹配"或者"命名匹配"規(guī)則。
全局中間件僅對(duì)動(dòng)態(tài)請(qǐng)求攔截有效,無法攔截靜態(tài)文件請(qǐng)求。
func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup
分組路由中注冊(cè)的中間件綁定到當(dāng)前分組路由中的所有的服務(wù)請(qǐng)求上,當(dāng)服務(wù)請(qǐng)求被執(zhí)行前會(huì)調(diào)用到其綁定的中間件方法。 分組路由僅有一個(gè)?Middleware
?的中間件注冊(cè)方法。分組路由中間件與全局中間件不同之處在于,分組路由中間件無法獨(dú)立使用,必須在分組路由注冊(cè)中使用,并且綁定到當(dāng)前分組路由中所有的路由上作為路由方法的一部分。
由于全局中間件也是通過路由規(guī)則執(zhí)行,那么也會(huì)存在執(zhí)行優(yōu)先級(jí):
這里的建議來參考于?gRPC
?的攔截器設(shè)計(jì),沒有過多的路由控制,僅在一個(gè)地方同一個(gè)方法統(tǒng)一注冊(cè)。往往越簡(jiǎn)單,越容易理解,也便于長(zhǎng)期維護(hù)。
分組路由中間件是綁定到分組路由上的服務(wù)方法,不存在路由規(guī)則匹配,因此只會(huì)按照注冊(cè)的先后順序執(zhí)行。
第一個(gè)例子,也是比較常見的功能需求。
我們需要在所有API請(qǐng)求之前增加允許跨域請(qǐng)求的返回?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)
?將跨域中間件通過分組路由的形式注冊(cè)綁定到了?/api.v2
?路由下所有的服務(wù)函數(shù)中。隨后我們可以通過請(qǐng)求 http://127.0.0.1:8199/api.v2/user/list 來查看允許跨域請(qǐng)求的?Header
?信息是否有返回。
我們?cè)诳缬蛘?qǐng)求中間件的基礎(chǔ)之上加上鑒權(quán)中間件。
為了簡(jiǎn)化示例,在該示例中,當(dāng)請(qǐng)求帶有?token
?參數(shù),并且參數(shù)值為?123456
?時(shí)可以通過鑒權(quán),并且允許跨域請(qǐng)求,執(zhí)行請(qǐng)求方法;否則返回?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)中間件。 請(qǐng)求時(shí)將會(huì)按照中間件注冊(cè)的先后順序,先執(zhí)行?MiddlewareCORS
?全局中間件,再執(zhí)行?MiddlewareAuth
?分組中間件。 隨后我們可以通過請(qǐng)求 http://127.0.0.1:8199/api.v2/user/list 和
http://127.0.0.1:8199/api.v2/user/list?token=123456 對(duì)比來查看效果。
使用分組路由中間件可以很方便地添加鑒權(quán)例外,因?yàn)橹挥挟?dāng)前分組路由下注冊(cè)的服務(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)處理。 隨后我們?cè)L問以下URL查看效果:
基于中間件,我們可以在服務(wù)函數(shù)執(zhí)行完成后做一些后置判斷的工作,特別是統(tǒng)一數(shù)據(jù)格式返回、結(jié)果處理、錯(cuò)誤判斷等等。這種需求我們可以使用后置的中間件類型來實(shí)現(xiàn)。我們使用一個(gè)簡(jiǎn)單的例子,用來演示如何使用中間件對(duì)所有的接口請(qǐng)求做后置判斷處理,作為一個(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ù)器居然開小差了,請(qǐng)稍后再試吧!")
}
}
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
|---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|
在該示例中,我們?cè)诤笾弥虚g件中判斷有無系統(tǒng)錯(cuò)誤,如果有則返回固定的提示信息,而不是把敏感的報(bào)錯(cuò)信息展示給用戶。當(dāng)然,在真實(shí)的項(xiàng)目場(chǎ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 查看效果。
我們來更進(jìn)一步完善一下以上示例,我們將請(qǐng)求日志包括狀態(tài)碼輸出到終端。這里我們必須得使用”全局中間件”了,這樣可以攔截處理到所有的服務(wù)請(qǐng)求,甚至?404
?請(qǐng)求。
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()
}
可以看到,我們注冊(cè)了一個(gè)全局的日志處理中間件以及跨域中間件,而鑒權(quán)中間件是注冊(cè)到?/api.v2
?路由下。
執(zhí)行后,我們可以通過請(qǐng)求 http://127.0.0.1:8199/api.v2/user/list 和 http://127.0.0.1:8199/api.v2/user/list?token=123456 對(duì)比來查看效果,并查看終端的日志輸出情況。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: