【源码解读】Gin 框架 (一)
写在前面
我们今天就从下面这几行简单的代码中,探讨gin框架的底层实现
gin的底层是基于net/http
包实现的,所以很多gin底层源码中涉及到了很多net/http
的相关方法。
本文全部基于gin@v1.8.0
进行讲解
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200,"pong")
})
_ = r.Run(":3000")
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
1. Run 函数底层实现
- gin/gin.go 文件
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
(1) address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
(2) err = http.ListenAndServe(address, engine.Handler())
return
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
这段代码还是比较容易看懂的。
address := resolveAddress(addr)
将传入的addr进行判断,返回正确的端口。- 调用
http.ListenAndServe
对这个端口进行监听,并将框架的信息引擎传进入。
然后让我们来看看这个ListenAndServe
的具体实现
- net/http/server.go 文件
这个链接是基于TCP网络进行监听连接的,并且request和response都通过这个handler进行传递。
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
- 1
- 2
- 3
- 4
然后我们来看一下这个Handler对象
是如何实现处理请求和响应的
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
- 1
- 2
- 3
这个Handler实现一个ServerHTTP的接口,来处理Response和Request,既然gin的Engine能和net/http包的Handler进行一个无缝连接,那么我们可以看看在这个gin包中,这个Engine
是如何实现Handler()
方法的。
接着我们来看一下这个gin的引擎对象 *Engine
实现的ServerHTTP
方法
- gin/gin.go 563行
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
// 创建上下文对象,注意这里是gin封装的Context,并不是go原生的Context!
// 这里用到了sync.Pool来进行内存的复用,防止频繁创建上下文,而导致性能的下降
c.writermem.reset(w)
c.Request = req
// 对请求进行赋值,并将这个req请求放到context的Request上下文中。
c.reset()
engine.handleHTTPRequest(c) // 处理请求
engine.pool.Put(c) // 对上下文对象进行回收
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
那这个Engine的handleHTTPRequest()
方法究竟是怎么处理请求的呢?
- gin/gin.go 585行
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method // 获取请求的方法
rPath := c.Request.URL.Path // 获取URL请求地址
{...对请求地址进行判断处理}
t := engine.trees // 获取压缩前缀树数组,每个请求方法都有一颗radix树。
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
// 找到当前请求方式对应的radix树
continue
}
root := t[i].root // 得到树的根节点
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
// 根据请求路径获取匹配的redix树节点
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
// 如果这个路由处理器数组不为空,逐个调用处理器处理请求,响应给客户端
c.handlers = value.handlers
c.fullPath = value.fullPath
// 调用第一个处理器处理请求
c.Next()
c.writermem.WriteHeaderNow()
return
}
// {...请求后续处理,比如没有该方法之类的处理}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
- 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
那你可能会对这个c.Next()
感到疑惑,这个是如何对请求进行处理的呢?
- gin/context.go 170 行
func (c *Context) Next() {
c.index++
// 指向要执行的中间件,初始值为-1,对这个index进行自增操作
for c.index < int8(len(c.handlers)) {
// 遍历所有的处理器,一次调用他们来处理请求
c.handlers[c.index](c)
// 使用中间件处理请求,中间件可以改变c.index的值
c.index++
// 然后再进行自增
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
c.handlers
中是可以处理所有的路径请求,因为已经遍历完所有的c.index
了,所以调用这个Next()
就可以处理所有的命令。
然后我们看看这个Next()
方法下面的另外两个方法IsAborted()
和Abort()
。
- IsAborted() 判断是否已经终止处理器调用
func (c *Context) IsAborted() bool {
return c.index >= abortIndex
}
- 1
- 2
- 3
- Abort() 终止处理器调用
func (c *Context) Abort() {
c.index = abortIndex
}
- 1
- 2
- 3
2. Engine 引擎对象初始化
我们一般会有两种形式的对象初始化,一个是 gin.New()
另一个是 gin.Default()
- gin/gin.go 209行
Default()
其实就是New()
之后新加了两个中间件而已
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
- 1
- 2
- 3
- 4
- 5
- 6
我们来重点看一下New()的实现。
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{ // 初始化Engine对象
RouterGroup: RouterGroup{ // 初始化路由组对象
Handlers: nil,
basePath: "/",
root: true, // 设置该路由器组为根节点
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
// 为true,如果只有/hello的路由存在,会将请求/hello/ 请求重定向到/hello , GET 响应到301, 其他响应到307。
RedirectFixedPath: false,
// 如果找不到路由,尝试修复请求路径。例如 /HELLO 和 /../../HEllo 可以重定向到/hello。
HandleMethodNotAllowed: false,
// 是否对不允许的方法,做对应的响应;开启后,入股用POST方法请求[GET /user] ,请求将由[GET /user]处理
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
// 提供给http.Request的ParseMultipartForm方法调用的“maxMerory”参数的值。默认是32MB
trees: make(methodTrees, 0, 9),
// 创建容量为9的redix树切片,对应9种请求方法。
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() any {
// 设置 sync.Pool 新建上下文对象函数
return engine.allocateContext()
}
return engine
}
- 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
3. Router路由
3.1 Group路由器组
r.Group()
可以帮助我们更快归纳某种请求。
- gin/routergroup.go 文件
创建路由组,仅是返回路由组对象,路由组的本质就是一个模板,使用路由组添加路由,省去用户填写相同路径前缀和中间件的步骤
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
// 返回一个路由组对象
return &RouterGroup{
// 新路由器组继承父路由器组的所有处理器
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
// 将绝对路径计算成相对路径
engine: group.engine,
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
combineHandlers()
方法
type HandlersChain []HandlerFunc
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
// 将原来的处理器长度加上放入当前需要追加的处理器长度
assert1(finalSize < int(abortIndex), "too many handlers")
// 如果超过了63中间件,这个路由是无法进行一个添加的,太多中间件要处理了。
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
// 把旧的中间件都拷贝到新创建的切片中
copy(mergedHandlers[len(group.Handlers):], handlers)
// 把新的也追加到这个新的创建的切片中
return mergedHandlers
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
// 根据绝对路径进行拼接成相对路径
}
- 1
- 2
- 3
- 4
3.2 GET 路由
路由是怎么进行注册的呢?我们通过GET方法来了解一下是怎么处理的
- gin/routergroup.go
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
- 1
- 2
- 3
然后我们来看看这个handle()
方法
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
// 根据相对路径,计算绝对路径
handlers = group.combineHandlers(handlers)
// 合并处理器(实际上就是将handlers追加到原有的处理器组切片中,作为该路径的处理链)
group.engine.addRoute(httpMethod, absolutePath, handlers)
// 添加路由,涉及radix树添加节点方法。
return group.returnObj()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4. Context 上下文
注意一点:这个上下文是Gin构建的,与Go原生的Context是不一样的
type Context struct {
writermem responseWriter
Request *http.Request // 请求对象
Writer ResponseWriter // 响应对象
Params Params // 路由参数 /user/:id 这个id
handlers HandlersChain // 中间件数组
index int8 // 当前执行中间件的下标
fullPath string // 请求的完整路径
engine *Engine
params *Params
skippedNodes *[]skippedNode
mu sync.RWMutex // 保证Keys map的线程安全
Keys map[string]any // 对每一个请求进行处理存储
Errors errorMsgs // 存储错误的列表
Accepted []string
queryCache url.Values // 存放url请求参数
formCache url.Values // 存放form参数
sameSite http.SameSite
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- Context创建
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
c.sameSite = 0
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- Context 传递过程进行拷贝
如果上下文在携程之间进行传递,那么必须要使用拷贝,传递副本。
因为context在处理完一个请求之后,就变成nil了,所以为了其他使用这个context的不报错,所以采用的是拷贝,防止被回收。拷贝是不会被回收的
func (c *Context) Copy() *Context {
cp := Context{
writermem: c.writermem,
Request: c.Request,
Params: c.Params,
engine: c.engine,
}
cp.writermem.ResponseWriter = nil
cp.Writer = &cp.writermem
cp.index = abortIndex
cp.handlers = nil
cp.Keys = map[string]any{}
for k, v := range c.Keys {
cp.Keys[k] = v
}
paramCopy := make([]Param, len(cp.Params))
copy(paramCopy, cp.Params)
cp.Params = paramCopy
return &cp
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
5. 思考
- 如果上下文对象的创建,可以用sync.Pool 来复用内存。
- 如果上下文需要被并发使用,需要使用上下文副本。
文章来源: blog.csdn.net,作者:小生凡一,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_45304503/article/details/125090876
- 点赞
- 收藏
- 关注作者
评论(0)