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 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:
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
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:
func Middleware(r *ghttp.Request) {
// Middleware processing logic
r.Middleware.Next()
}
Post-Middleware
Its definition is similar to:
func Middleware(r *ghttp.Request) {
r.Middleware.Next()
// Middleware processing logic
}
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
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.
tip
Global middleware is only effective for dynamic request interception and cannot intercept static file requests.
Group Routing Middleware
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:
- 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;
- 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;
- 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;
tip
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
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/glog"
)
type HelloReq struct {
g.Meta `path:"/hello" method:"get"`
}
type HelloRes struct {
}
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
glog.Debug(ctx, "Inside")
return
}
func RequestHandle1(r *ghttp.Request) {
glog.Debug(r.GetCtx(), "Pre1")
r.Middleware.Next()
}
func RequestHandle2(r *ghttp.Request) {
glog.Debug(r.GetCtx(), "Pre2")
r.Middleware.Next()
}
func RequestHandle3(r *ghttp.Request) {
glog.Debug(r.GetCtx(), "Pre3")
r.Middleware.Next()
}
func RequestHandle4(r *ghttp.Request) {
glog.Debug(r.GetCtx(), "Pre4")
r.Middleware.Next()
}
func ResponseHandle1(r *ghttp.Request) {
r.Middleware.Next()
glog.Debug(r.GetCtx(), "Post1")
}
func ResponseHandle2(r *ghttp.Request) {
r.Middleware.Next()
glog.Debug(r.GetCtx(), "Post2")
}
func ResponseHandle3(r *ghttp.Request) {
r.Middleware.Next()
glog.Debug(r.GetCtx(), "Post3")
}
func ResponseHandle4(r *ghttp.Request) {
r.Middleware.Next()
glog.Debug(r.GetCtx(), "Post4")
}
func main() {
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
// Pre-Middleware
group.Middleware(RequestHandle1)
group.Middleware(RequestHandle2)
// Post-Middleware
group.Middleware(ResponseHandle1)
group.Middleware(ResponseHandle2)
group.Group("/sub", func(group *ghttp.RouterGroup) {
// Pre-Middleware
group.Middleware(RequestHandle3)
group.Middleware(RequestHandle4)
// Post-Middleware
group.Middleware(ResponseHandle3)
group.Middleware(ResponseHandle4)
group.Bind(new(Hello))
})
})
s.Run()
}
Execution result is as follows:
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 inRequestHandle1
, then onlyRequestHandle1
will be executed - Interrupt before the
Next()
call inRequestHandle2
, then onlyRequestHandle1
,RequestHandle2
will be executed - Interrupt before the
Next()
call inRequestHandle3
, thenRequestHandle1
,RequestHandle2
,RequestHandle3
, as well asResponseHandle2
,ResponseHandle1
will be executed - Interrupt before the
Next()
call inRequestHandle4
, thenRequestHandle1
,RequestHandle2
,RequestHandle3
,RequestHandle4
, as well asResponseHandle2
,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