gin源码笔记1
前言
主要是通过gin的源码,学习一些golang编程的常见技巧。同时积累标准库的用法。
options模式
一种设置结构体参数的方法,用于设置默认值,但用户可以传入option来修改默认值,具备可扩展性。
- gin初始化时会传入opts
1
2
3
4
5
6
7func New(opts ...OptionFunc) *Engine {
engine := &Engine{
// 默认参数
...
}
return engine.With(opts...)
} - options应用
1
2
3
4
5
6
7func (engine *Engine) With(opts ...OptionFunc) *Engine {
for _, opt := range opts {
opt(engine)
}
return engine
} - 如可以写一个OptionFunc并加载
1
2
3
4
5
6
7func WithForwardedByClientIP() gin.OptionFunc {
return func(engine *gin.Engine) {
engine.ForwardedByClientIP = true
}
}
r := gin.Default(WithForwardedByClientIP())
中间件扩展原理
插入中间件,实际上将中间件对应的函数闭包
HanlerFunc
存到路由切片里1
2
3
4
5
6
7
8
9
10func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
...
return engine
}
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}以默认两个中间件为例,可以看到中间件
HandlerFunc
会操作gin.Context
变量。调用c.Next()
前为中间件前处理,调用后为请求完毕后处理。如日志中间件会记录调用耗时并打印。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
36
37
38
39
40
41
42engine.Use(Logger(), Recovery())
func Logger() HandlerFunc {
return LoggerWithConfig(LoggerConfig{})
}
// LoggerWithConfig instance a Logger middleware with config.
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
...
return func(c *Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
...
param := LogFormatterParams{
Request: c.Request,
isTerm: isTerm,
Keys: c.Keys,
}
// Stop timer
param.TimeStamp = time.Now()
param.Latency = param.TimeStamp.Sub(start)
param.ClientIP = c.ClientIP()
param.Method = c.Request.Method
param.StatusCode = c.Writer.Status()
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
param.Path = path
fmt.Fprint(out, formatter(param))
}
}Next()
函数会执行后面的中间件1
2
3
4
5
6
7func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}假如绑定了一个ping,当一个ping到达gin时,处理过程类似洋葱模型。跟深度优先遍历算法代码基本一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run()
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
// 这里将所有handler汇总,返回最终的HandlersChain
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
1 | func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { |
- 请求到达,取出method树对应的节点挂着的
hanlders
,赋值给Context,然后调用Next()
,开始像洋葱模式一样执行。
1 | // ServeHTTP conforms to the http.Handler interface. |
1 | func (engine *Engine) handleHTTPRequest(c *Context) { |
标准库函数积累
1 | // 测试format是否以 \n 结尾。 |
对象池sync.Pool
sync.Pool 是 Go 语言标准库中的一个并发安全的内存池,主要用于临时对象的存储和复用,从而减少频繁的内存分配和垃圾回收,提高性能。
sync.Pool 主要用于存储短期使用的对象,特别是在高并发场景下,能够有效地减少内存分配的开销。它的设计适合于那些生命周期短暂的对象,比如在请求处理中使用的临时数据结构。
1 | // 分配Context的函数 |