新聞中心
?ghttp.Server?提供了事件回調(diào)注冊功能,類似于其他框架的中間件功能,相比較于中間件,事件回調(diào)的特性更加簡單。

十多年的宜陽網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。營銷型網(wǎng)站建設(shè)的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整宜陽建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“宜陽網(wǎng)站設(shè)計”,“宜陽網(wǎng)站推廣”以來,每個客戶項目都認(rèn)真落實(shí)執(zhí)行。
?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ī)請參考圖例所示。
回調(diào)優(yōu)先級
由于事件的綁定也是使用的路由規(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)用。
關(guān)于全局回調(diào)
我們往往使用綁定?/*?這樣的?HOOK?路由來實(shí)現(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ù)函數(shù)調(diào)用順序
建議 相同的業(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ù)也可以實(shí)現(xiàn),功能上不會有問題,但從設(shè)計的角度來講,內(nèi)聚性降低了,不便于業(yè)務(wù)函數(shù)管理。
ExitHook方法
當(dāng)路由匹配到多個?HOOK?方法時,默認(rèn)是按照路由匹配優(yōu)先級順序執(zhí)行?HOOK?方法。當(dāng)在?HOOK?方法中調(diào)用?Request.ExitHook?方法后,后續(xù)的?HOOK?方法將不會被繼續(xù)執(zhí)行,作用類似?HOOK?方法覆蓋。
接口鑒權(quán)控制
事件回調(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)限校驗(yàn)的事件回調(diào)函數(shù)中執(zhí)行?r.Redirect*?方法,又沒有調(diào)用?r.ExitAll()?方法退出業(yè)務(wù)執(zhí)行,往往會產(chǎn)生?http multiple response writeheader calls?錯誤提示。因?yàn)?r.Redirect*?方法會往返回的?header?中寫入?Location?頭;而隨后的業(yè)務(wù)服務(wù)接口往往會往?header?寫入?Content-Type/Content-Length?頭,這兩者有沖突造成的。
中間件與事件回調(diào)
中間件(?Middleware?)與事件回調(diào)(?HOOK?)是?GF?框架的兩大流程控制特性,兩者都可用于控制請求流程,并且也都支持綁定特定的路由規(guī)則。但兩者區(qū)別也是非常明顯的。
- 首先,中間件側(cè)重于應(yīng)用級的流程控制,而事件回調(diào)側(cè)重于服務(wù)級流程控制;也就是說中間件的作用域僅限于應(yīng)用,而事件回調(diào)的“權(quán)限”更強(qiáng)大,屬于?
Server?級別,并可處理靜態(tài)文件的請求回調(diào)。 - 其次,中間件設(shè)計采用了“洋蔥”設(shè)計模型;而事件回調(diào)采用的是特定事件的鉤子觸發(fā)設(shè)計。
- 最后,中間件相對來說靈活性更高,也是比較推薦的流程控制方式;而事件回調(diào)比較簡單,但靈活性較差。
Request.URL與Request.Router
?Request.Router?是匹配到的路由對象,包含路由注冊信息,一般來說開發(fā)者不會用到。 ?Request.URL?是底層請求的?URL?對象(繼承自標(biāo)準(zhǔn)庫?http.Request?),包含請求的?URL?地址信息,特別是?Request.URL.Path?表示請求的?URI?地址。
因此,假如在服務(wù)回調(diào)函數(shù)中使用的話,?Request.Router?是有值的,因?yàn)橹挥衅ヅ涞搅寺酚刹艜{(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?。
靜態(tài)文件事件
如果僅僅是提供?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)行選擇性地處理。
事件回調(diào)示例
示例1,基本使用
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 時,運(yùn)行?WebServer?進(jìn)程的終端將會按照事件的執(zhí)行流程打印出對應(yīng)的事件名稱。
示例2,相同事件注冊
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ù)單獨(dú)定義為了兩個函數(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示例3,改變業(yè)務(wù)邏輯
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?查看效果:
- http://127.0.0.1:8199/user/info
- http://127.0.0.1:8199/user/list/1.java
- http://127.0.0.1:8199/user/list/2.java
示例4,回調(diào)執(zhí)行優(yōu)先級
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示例5,允許跨域請求
?HOOK?和中間件都能實(shí)現(xiàn)跨域請求處理,我們這里使用?HOOK?來實(shí)現(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?請求,這次便成功了:
網(wǎng)站標(biāo)題:創(chuàng)新互聯(lián)GoFrame教程:GoFrame高級特性-HOOK事件回調(diào)
分享URL:http://m.5511xx.com/article/dpceocc.html


咨詢
建站咨詢
