HOOK Callbacks - 图1

ghttp.Server provides an event callback registration function, similar to the middleware function in other frameworks, but event callbacks are simpler.

ghttp.Server supports custom event listening and handling, binding and registering based on the pattern method (the pattern format is consistent with route registration). It supports multiple methods to listen to the same event, and ghttp.Server will call the callback methods according to the routing priority and callback registration order. The earlier the HOOK callback function is registered for the same event, the higher its priority. The related methods are as follows:

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

Of course, domain objects also support event callback registration:

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

Supported Hook event list:

  1. ghttp.HookBeforeServe

This event occurs before entering/initializing the service object and is most commonly used, especially for handling permission control and cross-origin requests.

  1. ghttp.HookAfterServe

After completing the service execution process.

  1. ghttp.HookBeforeOutput

Before outputting content to the client.

  1. ghttp.HookAfterOutput

After outputting content to the client.

Please refer to the example for specific invocation timing.

Callback Priority

Since event binding is also based on routing rules, its priority is the same as described in the Router - Route Patterns chapter.

However, the mechanism for event invocation differs from that of route registration invocation. It allows multiple event callback methods under the same routing rule, and events under this route will be invoked in order of priority. If the routing rules have the same priority, they will be invoked in the order of event registration.

About Global Callback

We often use a binding like /* for HOOK routes to achieve global callback handling, which is feasible. However, HOOK executions have the lowest priority; the more specific the route registration, the higher the priority, and the more vague the route, the lower the priority. /* belongs to the most vague route.

To reduce coupling between different modules, routes are often not registered in the same place. For instance, a HOOK registered for a user module (/user/*) will be invoked first before the global HOOK. Relying solely on registration order to control priority becomes difficult to manage when there are many modules and routes.

Business Function Invocation Order

It is recommended that multiple processing functions (such as: A, B, C) for the same business (same business module) be placed in the same HOOK callback function for handling. Manage the invocation order of business processing functions (function invocation order: A-B-C) within the registered callback function.

Although registering multiple identical HOOK callback functions can also fulfill the requirement without any functional issues, from a design perspective, cohesion is reduced, making it inconvenient to manage business functions.

ExitHook Method

When the route matches multiple HOOK methods, by default, HOOK methods are executed according to the priority of route matching. When calling the Request.ExitHook method within a HOOK method, subsequent HOOK methods will not be executed, which acts similar to HOOK function overriding.

API Authentication Control

A common application of event callback registration is to control authentication/permissions for the called API. This requires binding the ghttp.HookBeforeServe event, where all matched API requests are processed before service execution (e.g., binding /* event callback route). If authentication fails, call r.ExitAll() to exit subsequent service execution (including subsequent event callback execution).

Furthermore, executing r.Redirect* in the event callback function for permission verification without calling r.ExitAll() to exit the business execution often results in http multiple response writeheader calls error messages. This is because the r.Redirect* method writes the Location header in the header, and subsequent business service APIs often write the Content-Type/Content-Length headers, causing a conflict.

Middleware vs. Event Callback

Middleware (Middleware) and Event Callback (HOOK) are two major process control features in the GoFrame framework. Both can be used to control request processes and support binding specific routing rules. However, they are quite different.

  1. Firstly, Middleware focuses on application-level process control, while Event Callback focuses on service-level process control; Middleware’s scope is limited to applications, while Event Callback is more powerful, at the Server level, and can handle static file request callbacks.
  2. Secondly, middleware design adopts an “onion” design model; Event Callback adopts a hook trigger design for specific events.
  3. Lastly, middleware is relatively more flexible and is often recommended for process control; Event Callback is simpler but less flexible.

Request.URL vs. Request.Router

Request.Router is the matched route object containing route registration information, which is typically not used by developers. Request.URL is the underlying URL object from the standard library http.Request, containing the request URL address information, especially Request.URL.Path representing the requested URI address.

Therefore, if used in a service callback function, Request.Router has a value because it will call the service callback method only when the route is matched. However, in an event callback function, this object may be nil (indicating no matched service callback function route). Especially when using event callback for request API authentication, use the Request.URL object to obtain the request URL information instead of Request.Router.

Static File Events

HOOK Callbacks - 图2tip

If you are only providing API API services (including front static file service proxies like nginx), which do not involve static file services, you can ignore this section.

Note that event callbacks can also match static file accesses that meet routing rules (Static File Service feature is disabled by default in the gf framework, and we can manually enable it using WebServer related configuration. See Configuration for details).

For example, if we register a /* global match event callback route, static file accesses like /static/js/index.js or /upload/images/thumb.jpg will also be matched and processed in the registered event callback function.

We can use Request.IsFileRequest() in the event callback function to determine whether the request is a static file request. If the business logic does not require static file event callbacks, ignore it in the event callback function to selectively process it.

Event Callback Examples

Example 1: Basic Usage

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. "github.com/gogf/gf/v2/os/glog"
  6. )
  7. func main() {
  8. // Basic event callback usage
  9. p := "/:name/info/{uid}"
  10. s := g.Server()
  11. s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
  12. ghttp.HookBeforeServe: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeServe) },
  13. ghttp.HookAfterServe: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterServe) },
  14. ghttp.HookBeforeOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeOutput) },
  15. ghttp.HookAfterOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterOutput) },
  16. })
  17. s.BindHandler(p, func(r *ghttp.Request) {
  18. r.Response.Write("User:", r.Get("name"), ", uid:", r.Get("uid"))
  19. })
  20. s.SetPort(8199)
  21. s.Run()
  22. }

When accessing http://127.0.0.1:8199/john/info/10000, the terminal running the WebServer process will print out the corresponding event names according to the execution sequence of the events.

Example 2: Registering the Same Event

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. // Priority HOOK
  7. func beforeServeHook1(r *ghttp.Request) {
  8. r.SetParam("name", "GoFrame")
  9. r.Response.Writeln("set name")
  10. }
  11. // Subsequent HOOK
  12. func beforeServeHook2(r *ghttp.Request) {
  13. r.SetParam("site", "https://goframe.org")
  14. r.Response.Writeln("set site")
  15. }
  16. // Allow registering multiple callback functions for the same route and event, invoked in the order of registration priority.
  17. // To easily compare priority in the route table, here the HOOK callback functions are separately defined as two functions.
  18. func main() {
  19. s := g.Server()
  20. s.BindHandler("/", func(r *ghttp.Request) {
  21. r.Response.Writeln(r.Get("name"))
  22. r.Response.Writeln(r.Get("site"))
  23. })
  24. s.BindHookHandler("/", ghttp.HookBeforeServe, beforeServeHook1)
  25. s.BindHookHandler("/", ghttp.HookBeforeServe, beforeServeHook2)
  26. s.SetPort(8199)
  27. s.Run()
  28. }

After execution, the terminal outputs routing table information as follows:

  1. SERVER | ADDRESS | DOMAIN | METHOD | P | ROUTE | HANDLER | MIDDLEWARE
  2. |---------|---------|---------|--------|---|-------|-----------------------|-------------------|
  3. default | :8199 | default | ALL | 1 | / | main.main.func1 |
  4. |---------|---------|---------|--------|---|-------|-----------------------|-------------------|
  5. default | :8199 | default | ALL | 2 | / | main.beforeServeHook1 | HOOK_BEFORE_SERVE
  6. |---------|---------|---------|--------|---|-------|-----------------------|-------------------|
  7. default | :8199 | default | ALL | 1 | / | main.beforeServeHook2 | HOOK_BEFORE_SERVE
  8. |---------|---------|---------|--------|---|-------|-----------------------|-------------------|

After manually accessing http://127.0.0.1:8199/, the page output is:

  1. set name
  2. set site
  3. GoFrame
  4. https://goframe.org

Example 3: Changing Business Logic

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. func main() {
  8. s := g.Server()
  9. // Multi-event callback example, event 1
  10. pattern1 := "/:name/info"
  11. s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc{
  12. ghttp.HookBeforeServe: func(r *ghttp.Request) {
  13. r.SetParam("uid", 1000)
  14. },
  15. })
  16. s.BindHandler(pattern1, func(r *ghttp.Request) {
  17. r.Response.Write("User:", r.Get("name"), ", uid:", r.Get("uid"))
  18. })
  19. // Multi-event callback example, event 2
  20. pattern2 := "/{object}/list/{page}.java"
  21. s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{
  22. ghttp.HookBeforeOutput: func(r *ghttp.Request) {
  23. r.Response.SetBuffer([]byte(
  24. fmt.Sprintf("Changed output content through event, 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. }

Event 1 sets the GET parameter when accessing the route /:\name/info; Event 2 changes the output result when the access path matches the route /{object}/list/{page}.java. After execution, access the following URLs to see the effect:

Example 4: Callback Execution Priority

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func main() {
  7. s := g.Server()
  8. s.BindHandler("/priority/show", func(r *ghttp.Request) {
  9. r.Response.Writeln("priority service")
  10. })
  11. s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc{
  12. ghttp.HookBeforeServe: func(r *ghttp.Request) {
  13. r.Response.Writeln("/priority/:name")
  14. },
  15. })
  16. s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc{
  17. ghttp.HookBeforeServe: func(r *ghttp.Request) {
  18. r.Response.Writeln("/priority/*any")
  19. },
  20. })
  21. s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc{
  22. ghttp.HookBeforeServe: func(r *ghttp.Request) {
  23. r.Response.Writeln("/priority/show")
  24. },
  25. })
  26. s.SetPort(8199)
  27. s.Run()
  28. }

In this example, we registered event callbacks for 3 route rules, all of which can match the route registered address /priority/show, allowing us to access this address to see the order of route execution.

After execution, when we access http://127.0.0.1:8199/priority/show, the page outputs the following information:

  1. /priority/show
  2. /priority/:name
  3. /priority/*any
  4. priority service

Example 5: Allowing Cross-Origin Requests

In the chapters Middleware - Intro and CORS, examples of cross-origin handling have also been introduced. In most cases, we use middleware to achieve cross-origin request handling.

Both HOOK and middleware can implement cross-origin request handling. Here, we’ll use HOOK to achieve simple cross-origin processing. First, let’s look at a simple API example:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func Order(r *ghttp.Request) {
  7. r.Response.Write("GET")
  8. }
  9. func main() {
  10. s := g.Server()
  11. s.Group("/api.v1", func(group *ghttp.RouterGroup) {
  12. group.GET("/order", Order)
  13. })
  14. s.SetPort(8199)
  15. s.Run()
  16. }

The API address is http://localhost:8199/api.v1/order, and this API is not allowed for cross-origin. Open a different domain name, such as the Baidu homepage (conveniently using jQuery for debugging), and press F12 to open the developer panel, and execute the following AJAX request in console:

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

The result is:

HOOK Callbacks - 图3

A cross-origin error is returned. Next, let’s modify the test code as follows:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func Order(r *ghttp.Request) {
  7. r.Response.Write("GET")
  8. }
  9. func main() {
  10. s := g.Server()
  11. s.Group("/api.v1", func(group *ghttp.RouterGroup) {
  12. group.Hook("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) {
  13. r.Response.CORSDefault()
  14. })
  15. group.GET("/order", Order)
  16. })
  17. s.SetPort(8199)
  18. s.Run()
  19. }

We added a bound event ghttp.HookBeforeServe for the route /api.v1/*any. This event will be called before all service executions. In this event’s callback method, we allow cross-origin requests by calling the CORSDefault method with default cross-origin settings. The bound event route rule uses a vague match rule, indicating that all API addresses starting with /api.v1 allow cross-origin requests.

Return to the Baidu homepage and execute the AJAX request again; this time, it is successful:

HOOK Callbacks - 图4