W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
?ghttp.Server
?提供了事件回調(diào)注冊功能,類似于其他框架的中間件功能,相比較于中間件,事件回調(diào)的特性更加簡單。
?ghttp.Server
?支持用戶對于某一事件進(jìn)行自定義監(jiān)聽處理,按照?pattern
?方式進(jìn)行綁定注冊(?pattern
?格式與路由注冊一致)。支持多個方法對同一事件進(jìn)行監(jiān)聽,?ghttp.Server
?將會按照路由優(yōu)先級及回調(diào)注冊順序進(jìn)行回調(diào)方法調(diào)用。同一事件時先注冊的?HOOK
?回調(diào)函數(shù)優(yōu)先級越高。 相關(guān)方法如下:
func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
func (s *Server) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error
當(dāng)然域名對象也支持事件回調(diào)注冊:
func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error
支持的?Hook
?事件列表:
ghttp.HookBeforeServe
?在進(jìn)入/初始化服務(wù)對象之前,該事件是最常用的事件,特別是針對于權(quán)限控制、跨域請求等處理。
ghttp.HookAfterServe
?在完成服務(wù)執(zhí)行流程之后。
ghttp.HookBeforeOutput
?向客戶端輸出返回內(nèi)容之前。
ghttp.HookAfterOutput
?向客戶端輸出返回內(nèi)容之后。
具體調(diào)用時機(jī)請參考圖例所示。
由于事件的綁定也是使用的路由規(guī)則,因此它的優(yōu)先級和 路由管理-路由規(guī)則 章節(jié)介紹的優(yōu)先級是一樣的。
但是事件調(diào)用時和路由注冊調(diào)用時的機(jī)制不一樣,同一個路由規(guī)則下允許綁定多個事件回調(diào)方法,該路由下的事件調(diào)用會按照優(yōu)先級進(jìn)行調(diào)用,假如優(yōu)先級相等的路由規(guī)則,將會按照事件注冊的順序進(jìn)行調(diào)用。
我們往往使用綁定?/*
?這樣的?HOOK
?路由來實現(xiàn)全局的回調(diào)處理,這樣是可以的。但是?HOOK
?執(zhí)行的優(yōu)先是最低的,路由注冊的越精確,優(yōu)先級越高,越模糊的路由優(yōu)先級越低,?/*
?就屬于最模糊的路由。
為降低不同的模塊耦合性,所有的路由往往不是在同一個地方進(jìn)行注冊。例如用戶模塊注冊的?HOOK
?(?/user/*
?),它將會被優(yōu)先調(diào)用隨后才可能是全局的?HOOK
?;如果僅僅依靠注冊順序來控制優(yōu)先級,在模塊多路由多的時候優(yōu)先級便很難管理。
建議 相同的業(yè)務(wù)(同一業(yè)務(wù)模塊) 的多個處理函數(shù)(例如: A、B、C)放到同一個HOOK回調(diào)函數(shù)中進(jìn)行處理,在注冊的回調(diào)函數(shù)中自行管理業(yè)務(wù)處理函數(shù)的調(diào)用順序(函數(shù)調(diào)用順序: A-B-C)。
雖然同樣的需求,注冊多個相同?HOOK
?的回調(diào)函數(shù)也可以實現(xiàn),功能上不會有問題,但從設(shè)計的角度來講,內(nèi)聚性降低了,不便于業(yè)務(wù)函數(shù)管理。
當(dāng)路由匹配到多個?HOOK
?方法時,默認(rèn)是按照路由匹配優(yōu)先級順序執(zhí)行?HOOK
?方法。當(dāng)在?HOOK
?方法中調(diào)用?Request.ExitHook
?方法后,后續(xù)的?HOOK
?方法將不會被繼續(xù)執(zhí)行,作用類似?HOOK
?方法覆蓋。
事件回調(diào)注冊比較常見的應(yīng)用是在對調(diào)用的接口進(jìn)行鑒權(quán)控制/權(quán)限控制。該操作需要綁定?ghttp.HookBeforeServe
?事件,在該事件中會對所有匹配的接口請求(例如綁定?/*
?事件回調(diào)路由)服務(wù)執(zhí)行前進(jìn)行回調(diào)處理。當(dāng)鑒權(quán)不通過時,需要調(diào)用?r.ExitAll()
?方法退出后續(xù)的服務(wù)執(zhí)行(包括后續(xù)的事件回調(diào)執(zhí)行)。
此外,在權(quán)限校驗的事件回調(diào)函數(shù)中執(zhí)行?r.Redirect*
?方法,又沒有調(diào)用?r.ExitAll()
?方法退出業(yè)務(wù)執(zhí)行,往往會產(chǎn)生?http multiple response writeheader calls
?錯誤提示。因為?r.Redirect*
?方法會往返回的?header
?中寫入?Location
?頭;而隨后的業(yè)務(wù)服務(wù)接口往往會往?header
?寫入?Content-Type/Content-Length
?頭,這兩者有沖突造成的。
中間件(?Middleware
?)與事件回調(diào)(?HOOK
?)是?GF
?框架的兩大流程控制特性,兩者都可用于控制請求流程,并且也都支持綁定特定的路由規(guī)則。但兩者區(qū)別也是非常明顯的。
Server
?級別,并可處理靜態(tài)文件的請求回調(diào)。
?Request.Router
?是匹配到的路由對象,包含路由注冊信息,一般來說開發(fā)者不會用到。 ?Request.URL
?是底層請求的?URL
?對象(繼承自標(biāo)準(zhǔn)庫?http.Request
?),包含請求的?URL
?地址信息,特別是?Request.URL.Path
?表示請求的?URI
?地址。
因此,假如在服務(wù)回調(diào)函數(shù)中使用的話,?Request.Router
?是有值的,因為只有匹配到了路由才會調(diào)用服務(wù)回調(diào)方法。但是在事件回調(diào)函數(shù)中,該對象可能為?nil
?(表示沒有匹配到服務(wù)回調(diào)函數(shù)路由)。特別是在使用事件回調(diào)對請求接口鑒權(quán)的時候,應(yīng)當(dāng)使用?Request.URL
?對象獲取請求的?URL
?信息,而不是?Request.Router
?。
如果僅僅是提供?API
?接口服務(wù)(包括前置靜態(tài)文件服務(wù)代理如?nginx
?),不涉及到靜態(tài)文件服務(wù),那么可以忽略該小節(jié)。
需要注意的是,事件回調(diào)同樣能夠匹配到符合路由規(guī)則的靜態(tài)文件訪問(靜態(tài)文件特性在?gf
?框架中是默認(rèn)開啟的,我們可以使用?WebServer
?相關(guān)配置來手動關(guān)閉。
例如,我們注冊了一個?/*
?的全局匹配事件回調(diào)路由,那么?/static/js/index.js
?或者?/upload/images/thumb.jpg
?等等靜態(tài)文件訪問也會被匹配到,會進(jìn)入到注冊的事件回調(diào)函數(shù)中進(jìn)行處理。
我們可以在事件回調(diào)函數(shù)中使用?Request.IsFileRequest()
?方法來判斷該請求是否是靜態(tài)文件請求,如果業(yè)務(wù)邏輯不需要靜態(tài)文件的請求事件回調(diào),那么在事件回調(diào)函數(shù)中直接忽略即可,以便進(jìn)行選擇性地處理。
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/glog"
)
func main() {
// 基本事件回調(diào)使用
p := "/:name/info/{uid}"
s := g.Server()
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeServe) },
ghttp.HookAfterServe: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterServe) },
ghttp.HookBeforeOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeOutput) },
ghttp.HookAfterOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterOutput) },
})
s.BindHandler(p, func(r *ghttp.Request) {
r.Response.Write("用戶:", r.Get("name"), ", uid:", r.Get("uid"))
})
s.SetPort(8199)
s.Run()
}
當(dāng)訪問 http://127.0.0.1:8199/john/info/10000 時,運行?WebServer
?進(jìn)程的終端將會按照事件的執(zhí)行流程打印出對應(yīng)的事件名稱。
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// 優(yōu)先調(diào)用的HOOK
func beforeServeHook1(r *ghttp.Request) {
r.SetParam("name", "GoFrame")
r.Response.Writeln("set name")
}
// 隨后調(diào)用的HOOK
func beforeServeHook2(r *ghttp.Request) {
r.SetParam("site", "https://goframe.org")
r.Response.Writeln("set site")
}
// 允許對同一個路由同一個事件注冊多個回調(diào)函數(shù),按照注冊順序進(jìn)行優(yōu)先級調(diào)用。
// 為便于在路由表中對比查看優(yōu)先級,這里講HOOK回調(diào)函數(shù)單獨定義為了兩個函數(shù)。
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("name"))
r.Response.Writeln(r.Get("site"))
})
s.BindHookHandler("/", ghttp.HookBeforeServe, beforeServeHook1)
s.BindHookHandler("/", ghttp.HookBeforeServe, beforeServeHook2)
s.SetPort(8199)
s.Run()
}
執(zhí)行后,終端輸出的路由表信息如下:
SERVER | ADDRESS | DOMAIN | METHOD | P | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|---|-------|-----------------------|-------------------|
default | :8199 | default | ALL | 1 | / | main.main.func1 |
|---------|---------|---------|--------|---|-------|-----------------------|-------------------|
default | :8199 | default | ALL | 2 | / | main.beforeServeHook1 | HOOK_BEFORE_SERVE
|---------|---------|---------|--------|---|-------|-----------------------|-------------------|
default | :8199 | default | ALL | 1 | / | main.beforeServeHook2 | HOOK_BEFORE_SERVE
|---------|---------|---------|--------|---|-------|-----------------------|-------------------|
手動訪問 http://127.0.0.1:8199/ 后,頁面輸出內(nèi)容為:
set name
set site
GoFrame
https://goframe.org
package main
import (
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
s := g.Server()
// 多事件回調(diào)示例,事件1
pattern1 := "/:name/info"
s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.SetParam("uid", 1000)
},
})
s.BindHandler(pattern1, func(r *ghttp.Request) {
r.Response.Write("用戶:", r.Get("name"), ", uid:", r.Get("uid"))
})
// 多事件回調(diào)示例,事件2
pattern2 := "/{object}/list/{page}.java"
s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{
ghttp.HookBeforeOutput: func(r *ghttp.Request) {
r.Response.SetBuffer([]byte(
fmt.Sprintf("通過事件修改輸出內(nèi)容, object:%s, page:%s", r.Get("object"), r.GetRouterString("page"))),
)
},
})
s.BindHandler(pattern2, func(r *ghttp.Request) {
r.Response.Write(r.Router.Uri)
})
s.SetPort(8199)
s.Run()
}
通過事件1設(shè)置了訪問?/:name/info
?路由規(guī)則時的?GET
?參數(shù);通過事件2,改變了當(dāng)訪問的路徑匹配路由?/{object}/list/{page}.java
?時的輸出結(jié)果。執(zhí)行之后,訪問以下?URL
?查看效果:
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Writeln("priority service")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Writeln("/priority/:name")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Writeln("/priority/*any")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Writeln("/priority/show")
},
})
s.SetPort(8199)
s.Run()
}
在這個示例中,我們往注冊了3個路由規(guī)則的事件回調(diào),并且都能夠匹配到路由注冊的地址?/priority/show
?,這樣我們便可以通過訪問這個地址來看看路由執(zhí)行的順序是怎么樣的。
執(zhí)行后我們訪問 http://127.0.0.1:8199/priority/show ,隨后我們看到頁面輸出以下信息:
/priority/show
/priority/:name
/priority/*any
priority service
?HOOK
?和中間件都能實現(xiàn)跨域請求處理,我們這里使用?HOOK
?來實現(xiàn)簡單的跨域處理。 首先我們來看一個簡單的接口示例:
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func Order(r *ghttp.Request) {
r.Response.Write("GET")
}
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.GET("/order", Order)
})
s.SetPort(8199)
s.Run()
}
接口地址是 http://localhost:8199/api.v1/order ,當(dāng)然這個接口是不允許跨域的。我們打開一個不同的域名,例如:百度首頁(正好用了?jQuery
?,方便調(diào)試),然后按?F12
?打開開發(fā)者面板,在?console
?下執(zhí)行以下?AJAX
?請求:
$.get("http://localhost:8199/api.v1/order", function(result){
console.log(result)
});
結(jié)果如下:
返回了不允許跨域的錯誤,接著我們修改一下測試代碼,如下:
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func Order(r *ghttp.Request) {
r.Response.Write("GET")
}
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.Hook("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.CORSDefault()
})
group.GET("/order", Order)
})
s.SetPort(8199)
s.Run()
}
我們增加了針對于路由?/api.v1/*any
?的綁定事件?ghttp.HookBeforeServe
?,該事件將會在所有服務(wù)執(zhí)行之前調(diào)用,該事件的回調(diào)方法中,我們通過調(diào)用?CORSDefault
?方法使用默認(rèn)的跨域設(shè)置允許跨域請求。該綁定的事件路由規(guī)則使用了模糊匹配規(guī)則,表示所有?/api.v1
?開頭的接口地址都允許跨域請求。
返回剛才的百度首頁,再次執(zhí)行請求?AJAX
?請求,這次便成功了:
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: