事件回调注册

事件回调/中间件 - 图1

ghttp.Server提供了事件回调注册功能,类似于其他框架的中间件功能,相比较于中间件,事件回调的特性更加简单。ghttp.Server支持用户对于某一事件进行自定义监听处理,按照pattern方式进行绑定注册(pattern格式与服务注册一致)。支持多个方法对同一事件进行监听,ghttp.Server将会按照路由优先级回调注册顺序进行回调方法调用。
相关方法如下:

  1. func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
  2. func (s *Server) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error

当然域名对象也支持事件回调注册:

  1. func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
  2. func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error

支持的Hook事件列表:

  1. BeforeServe/ghttp.HOOK_BEFORE_SERVE

    在进入/初始化服务对象之前。

  2. AfterServe/ghttp.HOOK_AFTER_SERVE

    在完成服务执行流程之后。

  3. BeforeOutput/ghttp.HOOK_BEFORE_OUTPUT

    向客户端输出返回内容之前。

  4. AfterOutput/ghttp.HOOK_AFTER_OUTPUT

    向客户端输出返回内容之后。

  5. BeforeClose/ghttp.HOOK_BEFORE_CLOSE

    在http请求关闭之前(注意请求关闭是异步处理操作,没有在http执行流程中处理)。

  6. AfterClose/ghttp.HOOK_AFTER_CLOSE

    在http请求关闭之后(注意请求关闭是异步处理操作,没有在http执行流程中处理)。

具体调用时机请参考图例所示。

事件优先级

由于事件的绑定也是使用的路由规则,因此它的优先级和【路由控制】章节介绍的优先级是一样的。

但是事件调用时和服务注册调用时的机制不一样,同一个路由规则下允许绑定多个事件回调方法,该路由下的事件调用会按照优先级进行调用,假如优先级相等的路由规则,将会按照事件注册的顺序进行调用。

使用示例1,基本使用

  1. package main
  2. import (
  3. "gitee.com/johng/gf/g"
  4. "gitee.com/johng/gf/g/os/glog"
  5. "gitee.com/johng/gf/g/net/ghttp"
  6. )
  7. func main() {
  8. // 基本事件回调使用
  9. p := "/:name/info/{uid}"
  10. s := g.Server()
  11. s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
  12. ghttp.HOOK_BEFORE_SERVE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_SERVE) },
  13. ghttp.HOOK_AFTER_SERVE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_SERVE) },
  14. ghttp.HOOK_BEFORE_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_OUTPUT) },
  15. ghttp.HOOK_AFTER_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_OUTPUT) },
  16. ghttp.HOOK_BEFORE_CLOSE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_CLOSE) },
  17. ghttp.HOOK_AFTER_CLOSE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_CLOSE) },
  18. })
  19. s.BindHandler(p, func(r *ghttp.Request) {
  20. r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
  21. })
  22. s.SetPort(8199)
  23. s.Run()
  24. }

当访问http://127.0.0.1:8199/john/info/10000时,运行Web Server进程的终端将会按照事件的执行流程打印出对应的事件名称。

使用示例2,改变业务逻辑

  1. package main
  2. import (
  3. "fmt"
  4. "gitee.com/johng/gf/g"
  5. "gitee.com/johng/gf/g/net/ghttp"
  6. )
  7. func main() {
  8. s := g.Server()
  9. // 多事件回调示例,事件1
  10. pattern1 := "/:name/info"
  11. s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc {
  12. "BeforeServe" : func(r *ghttp.Request) {
  13. r.SetQuery("uid", "1000")
  14. },
  15. })
  16. s.BindHandler(pattern1, func(r *ghttp.Request) {
  17. r.Response.Write("用户:", r.Get("name"), ", uid:", r.GetQueryString("uid"))
  18. })
  19. // 多事件回调示例,事件2
  20. pattern2 := "/{object}/list/{page}.java"
  21. s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc {
  22. "BeforeOutput" : func(r *ghttp.Request){
  23. r.Response.SetBuffer([]byte(
  24. fmt.Sprintf("通过事件修改输出内容, object:%s, page:%s", r.Get("object"), r.GetRouterString("page"))),
  25. )
  26. },
  27. })
  28. s.BindHandler(pattern2, func(r *ghttp.Request) {
  29. r.Response.Write(r.Router.Uri)
  30. })
  31. s.SetPort(8199)
  32. s.Run()
  33. }

通过事件1设置了访问/:name/info路由规则时的GET参数;通过事件2,改变了当访问的路径匹配路由/{object}/list/{page}.java时的输出结果。

使用示例3,事件回调注册优先级

  1. package main
  2. import (
  3. "gitee.com/johng/gf/g"
  4. "gitee.com/johng/gf/g/net/ghttp"
  5. "gitee.com/johng/gf/g/os/glog"
  6. )
  7. func main() {
  8. s := g.Server()
  9. s.BindHandler("/priority/show", func(r *ghttp.Request) {
  10. r.Response.Write("priority test")
  11. })
  12. s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
  13. "BeforeServe" : func(r *ghttp.Request) {
  14. glog.Println(r.Router.Uri)
  15. },
  16. })
  17. s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
  18. "BeforeServe" : func(r *ghttp.Request) {
  19. glog.Println(r.Router.Uri)
  20. },
  21. })
  22. s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
  23. "BeforeServe" : func(r *ghttp.Request) {
  24. glog.Println(r.Router.Uri)
  25. },
  26. })
  27. s.SetPort(8199)
  28. s.Run()
  29. }

在这个示例中,我们往注册了3个路由规则的事件回调,并且都匹配服务注册的地址/priority/show,这样我们便可以通过访问这个地址来看看路由执行的顺序是怎么样的。

执行后我们访问http://127.0.0.1:8199/priority/show,随后我们可以在服务端的终端上看到以下输出信息:

  1. 2018-08-03 15:16:25.971 27391: http server started listening on [:8199]
  2. 2018-08-03 15:16:28.385 /priority/show
  3. 2018-08-03 15:16:28.385 /priority/:name
  4. 2018-08-03 15:16:28.385 /priority/*any

使用示例4,使用事件回调处理跨域请求

首先我们来看一个简单的REST接口示例:

  1. package main
  2. import (
  3. "gitee.com/johng/gf/g"
  4. "gitee.com/johng/gf/g/frame/gmvc"
  5. "gitee.com/johng/gf/g/net/ghttp"
  6. )
  7. type Order struct {
  8. gmvc.Controller
  9. }
  10. func (o *Order) Get() {
  11. o.Response.Write("GET")
  12. }
  13. func main() {
  14. s := g.Server()
  15. s.BindControllerRest("/api.v1/{.struct}", new(Order))
  16. s.SetPort(8199)
  17. s.Run()
  18. }

接口地址是http://localhost/api.v1/order,当然这个接口是不允许跨域的。我们打开一个不同的域名,例如:百度首页,然后按F12打开开发者面板,在console下执行以下AJAX请求:

  1. $.get("http://localhost:8199/api.v1/order", function(result){
  2. console.log(result)
  3. });

结果如下:
事件回调/中间件 - 图2
返回了不允许跨域的错误,接着我们修改一下测试代码,如下:

  1. package main
  2. import (
  3. "gitee.com/johng/gf/g"
  4. "gitee.com/johng/gf/g/frame/gmvc"
  5. "gitee.com/johng/gf/g/net/ghttp"
  6. )
  7. type Order struct {
  8. gmvc.Controller
  9. }
  10. func (o *Order) Get() {
  11. o.Response.Write("GET")
  12. }
  13. func main() {
  14. s := g.Server()
  15. s.BindHookHandlerByMap("/api.v1/*any", map[string]ghttp.HandlerFunc {
  16. "BeforeServe" : func(r *ghttp.Request) {
  17. r.Response.SetAllowCrossDomainRequest("*", "PUT,GET,POST,DELETE,OPTIONS")
  18. },
  19. })
  20. s.BindControllerRest("/api.v1/{.struct}", new(Order))
  21. s.SetPort(8199)
  22. s.Run()
  23. }

我们增加了针对于路由/api.v1/*any的绑定事件BeforeServe,该事件将会在所有服务执行之前调用,该事件的回调方法中,我们通过调用SetAllowCrossDomainRequest方法设置运行跨域请求。该绑定的事件路由规则使用了模糊匹配规则,表示所有/api.v1开头的接口地址都允许跨域请求。

返回刚才的百度首页,再次执行请求AJAX请求,这次便成功了:
事件回调/中间件 - 图3