拿上一篇用的例子改一下, 看看 Gin 的RouterGroup 与 middleware 是怎么工作的.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.New()
r.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "gin home page")
})
admin := r.Group("/admin")
admin.Use(gin.Logger())
admin.GET("/hello", func(context *gin.Context) {
context.String(http.StatusOK, "admin")
})
r.Run()
}
|
New 函数返回的是一个没有附加任何 middleware 的 gin.Engine 指针. 其中这个 Engine 的嵌套类型 RouterGroup 被赋值为:
1
2
3
4
5
|
RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
}
|
Handlers 是一组 gin.HandlerFunc
,
1
2
3
4
5
|
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
|
basePath
就是这一组路由的路径前缀, 类似 Rails 路由中的 scope 或者基于 scope 的 namespace 加上的路径前缀.
跟 r.GET
, r.POST
一样, 其实 r.Group
也是 gin.Engine
里面的 RouterGroup 嵌套类型的方法. 作用就是构建一个有共通前缀路径的 RouterGroup, 并且这组路由在该前缀路径下使用着特定的一些 middleware.
1
2
3
4
5
6
7
|
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
|
middleware 可以在使用 Group
方法时候作为其第二个参数传入进来, 也可以单独使用 Use
方法.
1
2
|
admin := r.Group("/admin", gin.Logger())
admin.Use(gin.Logger())
|
Use
是直接 append
到 RouterGroup 的 Handlers
最后的, 而 Group
会稍复杂一些调用 group.combineHandlers(handlers)
,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
// ...
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
|
不过, 除了限制 handlers 的总数以外, 没看懂这个 copy 来 copy 去, 跟直接 append 有啥区别 = 。 =
所以, 说了半天, 其实在 Gin 里边 middleware 就是 HandlerFunc
.
比如 admin.Use(gin.Logger())
, Logger 函数的返回值就是 HandlerFunc, 即签名为 func(*Context)
的函数类型
1
2
3
4
5
|
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc {
return LoggerWithConfig(LoggerConfig{})
}
|
同样的, 当我们要自定义一些 middleware 的时候, 不管处理的逻辑怎么着, 最后都是返回一个有且仅有一个 *gin.Context
参数, 且没有返回值的函数类型.
这里先看下如果不是使用 Gin 框架, 只是 net/http
的话, 实现类似 middleware 的功能.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package main
import (
"log"
"net/http"
"time"
)
func main() {
http.Handle("/", customMiddleware(&MyHandler{}))
http.Handle("/ping", customMiddleware(http.HandlerFunc(hello)))
http.ListenAndServe(":8080", nil)
}
func hello(w http.ResponseWriter, r *http.Request) {
log.Println("log hello")
w.Write([]byte("pong"))
}
type MyHandler struct{}
func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("log in MyHandler")
w.Write([]byte("MyHandler ServeHTTP"))
}
func customMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(time.Now())
next.ServeHTTP(w, r)
log.Println("after: next.ServeHTTP(w, r)")
})
}
|
这个输入参数 http.Handler
, 返回值也是 http.Handler
的函数就完成了一个简(mei)单(yong)的 middleware 编写了. 其中输入的参数 http.Handler
则是下一层要执行的操作.
然后, Gin 主页的 README 就有一个自定义 middleware 例子直接拿来了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package main
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// Set example variable
c.Set("example", "12345")
// before request
// after request
latency := time.Since(t)
log.Print(latency)
// access the status we are sending
status := c.Writer.Status()
log.Println(status)
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
|
对比下来, 不管是 net/http 的实现还是 Gin 的实现其实都是
不断地进行函数压栈再出栈
但因为有了 gin.Context 的封装和串联, 所以, 写起来要好看一些.
所以, 最后调用的时候, 关键点在 func (c *Context) Next()
, Context 的 index 默认为 -1,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
path := c.Request.URL.Path
// ...
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
// ...
if handlers != nil {
c.handlers = handlers
c.Params = params
c.Next()
c.writermem.WriteHeaderNow()
return
}
// ...
}
// ...
}
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
|
Related: