GoFrame provides an elegant middleware request control method, which is also the mainstream request flow control method provided by WebServer. The middleware design can provide more flexible and powerful plugin mechanisms for WebServer. The classic middleware onion model:

Middleware - Intro - 图1

Middleware Definition

The definition of middleware is the same as a normal HTTP execution method HandlerFunc, but you can use the Middleware attribute object in the Request parameter to control the request flow.

Let’s take the definition of a middleware for cross-origin requests as an example:

  1. func MiddlewareCORS(r *ghttp.Request) {
  2. r.Response.CORSDefault()
  3. r.Middleware.Next()
  4. }

You can see that in this middleware, after the logic for handling cross-origin requests is completed, the r.Middleware.Next() method is used to further execute the next process; if you exit directly without calling the r.Middleware.Next() method, the subsequent execution process will be exited (this can be used for request authentication processing).

Middleware Types

There are two types of middleware: pre-middleware and post-middleware. Pre-middleware is called before the route service function call, and post-middleware is called after it.

Pre-Middleware

Its definition is similar to:

  1. func Middleware(r *ghttp.Request) {
  2. // Middleware processing logic
  3. r.Middleware.Next()
  4. }

Post-Middleware

Its definition is similar to:

  1. func Middleware(r *ghttp.Request) {
  2. r.Middleware.Next()
  3. // Middleware processing logic
  4. }

Middleware Registration

There are multiple ways to register middleware, refer to the API documentation: https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp

Global Middleware

  1. func (s *Server) Use(handlers ...HandlerFunc)

Global middleware is a request interception method that can be used independently, registered through routing rules, and bound to the Server. Since middleware requires request interception operations, it is often used with “fuzzy matching” or “name matching” rules.

Middleware - Intro - 图2tip

Global middleware is only effective for dynamic request interception and cannot intercept static file requests.

Group Routing Middleware

  1. func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup

Middleware registered in group routing is bound to all service requests in the current group routing. The bound middleware methods are called before the service request is executed. Group routing has only one middleware registration method Middleware. Group routing middleware is different from global middleware in that group routing middleware cannot be used independently, it must be used in group routing registration and bound to all routes in the current group routing as part of the routing method.

Execution Priority

Global Middleware

Since global middleware is also executed through routing rules, there will be an execution priority:

  1. First, since global middleware is based on fuzzy route matching, when the same route matches multiple middleware, it will be executed according to the depth-first rule of the route, for more details please refer to the routing chapter;
  2. Secondly, under the same route rule, execution is done in the order that middleware is registered, and the middleware registration method also supports registering multiple middleware simultaneously in the order;
  3. Finally, to avoid priority confusion and for subsequent management, it is recommended to register all middleware in one place in sequential order to control execution priority;

Middleware - Intro - 图3tip

The recommendation here is referenced from the design of interceptors in gRPC, where there is not much route control, and registration is done uniformly in one place with the same method. The simpler it is, the easier it is to understand, and it also facilitates long-term maintenance.

Group Routing Middleware

Group routing middleware is bound to service methods on the group routing, with no routing rule matching, so execution is done only in the order of registration. Refer to the following example or the execution result of the following code.

Expand source code

  1. package main
  2. import (
  3. "context"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. "github.com/gogf/gf/v2/os/glog"
  7. )
  8. type HelloReq struct {
  9. g.Meta `path:"/hello" method:"get"`
  10. }
  11. type HelloRes struct {
  12. }
  13. type Hello struct{}
  14. func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
  15. glog.Debug(ctx, "Inside")
  16. return
  17. }
  18. func RequestHandle1(r *ghttp.Request) {
  19. glog.Debug(r.GetCtx(), "Pre1")
  20. r.Middleware.Next()
  21. }
  22. func RequestHandle2(r *ghttp.Request) {
  23. glog.Debug(r.GetCtx(), "Pre2")
  24. r.Middleware.Next()
  25. }
  26. func RequestHandle3(r *ghttp.Request) {
  27. glog.Debug(r.GetCtx(), "Pre3")
  28. r.Middleware.Next()
  29. }
  30. func RequestHandle4(r *ghttp.Request) {
  31. glog.Debug(r.GetCtx(), "Pre4")
  32. r.Middleware.Next()
  33. }
  34. func ResponseHandle1(r *ghttp.Request) {
  35. r.Middleware.Next()
  36. glog.Debug(r.GetCtx(), "Post1")
  37. }
  38. func ResponseHandle2(r *ghttp.Request) {
  39. r.Middleware.Next()
  40. glog.Debug(r.GetCtx(), "Post2")
  41. }
  42. func ResponseHandle3(r *ghttp.Request) {
  43. r.Middleware.Next()
  44. glog.Debug(r.GetCtx(), "Post3")
  45. }
  46. func ResponseHandle4(r *ghttp.Request) {
  47. r.Middleware.Next()
  48. glog.Debug(r.GetCtx(), "Post4")
  49. }
  50. func main() {
  51. s := g.Server()
  52. s.Use(ghttp.MiddlewareHandlerResponse)
  53. s.Group("/", func(group *ghttp.RouterGroup) {
  54. // Pre-Middleware
  55. group.Middleware(RequestHandle1)
  56. group.Middleware(RequestHandle2)
  57. // Post-Middleware
  58. group.Middleware(ResponseHandle1)
  59. group.Middleware(ResponseHandle2)
  60. group.Group("/sub", func(group *ghttp.RouterGroup) {
  61. // Pre-Middleware
  62. group.Middleware(RequestHandle3)
  63. group.Middleware(RequestHandle4)
  64. // Post-Middleware
  65. group.Middleware(ResponseHandle3)
  66. group.Middleware(ResponseHandle4)
  67. group.Bind(new(Hello))
  68. })
  69. })
  70. s.Run()
  71. }

Execution result is as follows:

Middleware - Intro - 图4

Execution Interruption

In the group routing middleware, we can interrupt the current request before the Next() call of the pre-middleware with return. After the interruption, all subsequent pre-middleware, post-middleware of the same level and sub-level, and request handling methods will not be executed.

In the code above that demonstrates the priority of group routing middleware:

  • Interrupt before the Next() call in RequestHandle1, then only RequestHandle1 will be executed
  • Interrupt before the Next() call in RequestHandle2, then only RequestHandle1, RequestHandle2 will be executed
  • Interrupt before the Next() call in RequestHandle3, then RequestHandle1, RequestHandle2, RequestHandle3, as well as ResponseHandle2, ResponseHandle1 will be executed
  • Interrupt before the Next() call in RequestHandle4, then RequestHandle1, RequestHandle2, RequestHandle3, RequestHandle4, as well as ResponseHandle2, ResponseHandle1 will be executed

None of these interrupt cases will execute the request processing method.

Apart from the common method of using return to terminate the subsequent processing flow in the middleware, the framework also provides Exit related methods to forcibly interrupt the execution flow at the code execution location. For specific details, refer to the chapter: Response - Exit