Gin 是一个用 Go (Golang) 编写的 Web 框架,性能极优,具有快速、支持中间件、crash处理、json验证、路由组、错误管理、内存渲染、可扩展性等特点。
源码地址:https://github.com/gin-gonic/gin/tree/v1.10.0
参考视频:gin框架底层技术原理剖析_哔哩哔哩_bilibili
一、net/http 及 gin 使用示例
- 使用 net/http 创建 web 服务
Go
package main
import (
"net/http"
)
func main() {
// 使用 net/http 创建 web 服务
// 注册路由
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
})
// 启动监听
http.ListenAndServe(":8080", nil)
}
- 使用 gin 创建 web 服务
Go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 初始化 engine
r := gin.Default()
// 注册路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
// 启动服务
r.Run()
}
二、gin.Engine数据结构
Go
type Engine struct {
RouterGroup // 路由组
RedirectTrailingSlash bool
RedirectFixedPath bool
HandleMethodNotAllowed bool
ForwardedByClientIP bool
AppEngine bool
UseRawPath bool
UnescapePathValues bool
RemoveExtraSlash bool
RemoteIPHeaders []string
TrustedPlatform string
MaxMultipartMemory int64
// UseH2C enable h2c support.
UseH2C bool
ContextWithFallback bool
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
// 对象池,用来复用gin.Context
pool sync.Pool
// 路由树,根据不同的httpmethod,以及不同的路由,拆分http请求及处理函数
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
localSize uintptr // size of the local array
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims array
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() any
}
type methodTree struct {
method string
root *node
}
type methodTrees []methodTree
type node struct {
path string
indices string
wildChild bool
nType nodeType
priority uint32
children []*node // child nodes, at most 1 :param style node at the end of the array
handlers HandlersChain
fullPath string
}
type HandlersChain []HandlerFunc
Go
// src/net/http/method.go
// http method
const (
MethodGet = "GET"
MethodHead = "HEAD"
MethodPost = "POST"
MethodPut = "PUT"
MethodPatch = "PATCH" // RFC 5789
MethodDelete = "DELETE"
MethodConnect = "CONNECT"
MethodOptions = "OPTIONS"
MethodTrace = "TRACE"
)
三、gin 注册路由流程
- 创建engine
Go
func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}
func New(opts ...OptionFunc) *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
// 9 棵路由压缩前缀树,对应 http 的 9 种方法
trees: make(methodTrees, 0, 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 {
return engine.allocateContext(engine.maxParams)
}
return engine.With(opts...)
}
- 注册MiddleWare
Go
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
- 注册handler
以 http post 方法为例,注册 handler 方法调用顺序为 RouterGroup.POST -> RouterGroup.handle:
- 拼接出待注册方法的完整路径 absolutePath
- 拼接出待注册方法的完整函数处理链 handlers
- 以 absolutePath 和 handlers 组成kv对添加到路由树中
Go
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPost, 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()
}
func joinPaths(absolutePath, relativePath string) string {
if relativePath == "" {
return absolutePath
}
finalPath := path.Join(absolutePath, relativePath)
if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
return finalPath + "/"
}
return finalPath
}
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
}
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers)
root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
func (n *node) addRoute(path string, handlers HandlersChain) {
fullPath := path
n.priority++
// Empty tree
if len(n.path) == 0 && len(n.children) == 0 {
n.insertChild(path, fullPath, handlers)
n.nType = root
return
}
parentFullPathIndex := 0
walk:
for {
// Find the longest common prefix.
// This also implies that the common prefix contains no ':' or '*'
// since the existing key can't contain those chars.
i := longestCommonPrefix(path, n.path)
// Split edge
if i < len(n.path) {
child := node{
path: n.path[i:],
wildChild: n.wildChild,
nType: static,
indices: n.indices,
children: n.children,
handlers: n.handlers,
priority: n.priority - 1,
fullPath: n.fullPath,
}
n.children = []*node{&child}
// []byte for proper unicode char conversion, see #65
n.indices = bytesconv.BytesToString([]byte{n.path[i]})
n.path = path[:i]
n.handlers = nil
n.wildChild = false
n.fullPath = fullPath[:parentFullPathIndex+i]
}
// Make new node a child of this node
if i < len(path) {
path = path[i:]
c := path[0]
// '/' after param
if n.nType == param && c == '/' && len(n.children) == 1 {
parentFullPathIndex += len(n.path)
n = n.children[0]
n.priority++
continue walk
}
// Check if a child with the next path byte exists
for i, max := 0, len(n.indices); i < max; i++ {
if c == n.indices[i] {
parentFullPathIndex += len(n.path)
i = n.incrementChildPrio(i)
n = n.children[i]
continue walk
}
}
// Otherwise insert it
if c != ':' && c != '*' && n.nType != catchAll {
// []byte for proper unicode char conversion, see #65
n.indices += bytesconv.BytesToString([]byte{c})
child := &node{
fullPath: fullPath,
}
n.addChild(child)
n.incrementChildPrio(len(n.indices) - 1)
n = child
} else if n.wildChild {
// inserting a wildcard node, need to check if it conflicts with the existing wildcard
n = n.children[len(n.children)-1]
n.priority++
// Check if the wildcard matches
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
// Adding a child to a catchAll is not possible
n.nType != catchAll &&
// Check for longer wildcard, e.g. :name and :names
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
continue walk
}
// Wildcard conflict
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")
}
n.insertChild(path, fullPath, handlers)
return
}
// Otherwise add handle to current node
if n.handlers != nil {
panic("handlers are already registered for path '" + fullPath + "'")
}
n.handlers = handlers
n.fullPath = fullPath
return
}
}
四、gin 服务启动流程
调用 *Engine.Run() 方法,底层会把 gin.Engine 本身作为 net/http 包下的 handler 接口的实现类,并调用 http.ListenAndServe 方法启动服务。
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.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从对象池获取一个 context
c := engine.pool.Get().(*Context)
// 重置或初始化 context
c.writermem.reset(w)
c.Request = req
c.reset()
// 处理 http 请求
engine.handleHTTPRequest(c)
// 把 context 放回对象池
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
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
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
if engine.HandleMethodNotAllowed {
// According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
// containing a list of the target resource's currently supported methods.
allowed := make([]string, 0, len(t)-1)
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
allowed = append(allowed, tree.method)
}
}
if len(allowed) > 0 {
c.handlers = engine.allNoMethod
c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
Engine.handleHTTPRequest 处理 http 请求的流程:
- 根据 http method 取得对应的 methodTree
- 根据 path 从 methodTree 中取得对应的 handlers 链
- 将 handlers 链注入到 gin.context 中, 通过 context.Next() 方法遍历获取 handler
五、路由树原理
1、前缀树
又称Trie树、字典树或键树,是一种树形数据结构,主要用于高效地存储和查询字符串。
其特点为:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
2、压缩前缀树
压缩前缀树是一种高效处理字符串集合的数据结构,通过合并共享的前缀来节省存储空间并提高检索效率。它在网络路由、编译器符号表和字符串检索等领域有广泛应用。
3、路由树的数据结构
gin路由管理采用压缩前缀树的数据结构,相较于map数据结构,压缩前缀树可用于模糊匹配,并且在数据量不大的情况下,压缩树的性能并不比map差。gin框架中采用补偿策略,将挂载的路径越多的子节点越往左排列,以便查询时优先被访问到。
Gotype methodTree struct { method string root *node } type methodTrees []methodTree type node struct { path string // 每个 indice 字符对应一个孩子节点路径的首字母 indices string wildChild bool nType nodeType // 后续节点数量 priority uint32 // 孩子节点列表 children []*node // child nodes, at most 1 :param style node at the end of the array // 处理函数链 handlers HandlersChain fullPath string } type HandlersChain []HandlerFunc
4、注册路由
Gofunc (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) root := engine.trees.get(method) if root == nil { root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) if paramsCount := countParams(path); paramsCount > engine.maxParams { engine.maxParams = paramsCount } if sectionsCount := countSections(path); sectionsCount > engine.maxSections { engine.maxSections = sectionsCount } } func (n *node) addRoute(path string, handlers HandlersChain) { fullPath := path n.priority++ // Empty tree if len(n.path) == 0 && len(n.children) == 0 { n.insertChild(path, fullPath, handlers) n.nType = root return } parentFullPathIndex := 0 walk: for { // Find the longest common prefix. // This also implies that the common prefix contains no ':' or '*' // since the existing key can't contain those chars. i := longestCommonPrefix(path, n.path) // Split edge if i < len(n.path) { child := node{ path: n.path[i:], wildChild: n.wildChild, nType: static, indices: n.indices, children: n.children, handlers: n.handlers, priority: n.priority - 1, fullPath: n.fullPath, } n.children = []*node{&child} // []byte for proper unicode char conversion, see #65 n.indices = bytesconv.BytesToString([]byte{n.path[i]}) n.path = path[:i] n.handlers = nil n.wildChild = false n.fullPath = fullPath[:parentFullPathIndex+i] } // Make new node a child of this node if i < len(path) { path = path[i:] c := path[0] // '/' after param if n.nType == param && c == '/' && len(n.children) == 1 { parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ continue walk } // Check if a child with the next path byte exists for i, max := 0, len(n.indices); i < max; i++ { if c == n.indices[i] { parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) n = n.children[i] continue walk } } // Otherwise insert it if c != ':' && c != '*' && n.nType != catchAll { // []byte for proper unicode char conversion, see #65 n.indices += bytesconv.BytesToString([]byte{c}) child := &node{ fullPath: fullPath, } n.addChild(child) n.incrementChildPrio(len(n.indices) - 1) n = child } else if n.wildChild { // inserting a wildcard node, need to check if it conflicts with the existing wildcard n = n.children[len(n.children)-1] n.priority++ // Check if the wildcard matches if len(path) >= len(n.path) && n.path == path[:len(n.path)] && // Adding a child to a catchAll is not possible n.nType != catchAll && // Check for longer wildcard, e.g. :name and :names (len(n.path) >= len(path) || path[len(n.path)] == '/') { continue walk } // Wildcard conflict pathSeg := path if n.nType != catchAll { pathSeg = strings.SplitN(pathSeg, "/", 2)[0] } prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path panic("'" + pathSeg + "' in new path '" + fullPath + "' conflicts with existing wildcard '" + n.path + "' in existing prefix '" + prefix + "'") } n.insertChild(path, fullPath, handlers) return } // Otherwise add handle to current node if n.handlers != nil { panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers n.fullPath = fullPath return } }
5、检索路由
Gotype nodeValue struct { handlers HandlersChain params *Params tsr bool fullPath string } type skippedNode struct { path string node *node paramsCount int16 } func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) { var globalParamsCount int16 walk: // Outer loop for walking the tree for { prefix := n.path if len(path) > len(prefix) { if path[:len(prefix)] == prefix { path = path[len(prefix):] // Try all the non-wildcard children first by matching the indices idxc := path[0] for i, c := range []byte(n.indices) { if c == idxc { // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild if n.wildChild { index := len(*skippedNodes) *skippedNodes = (*skippedNodes)[:index+1] (*skippedNodes)[index] = skippedNode{ path: prefix + path, node: &node{ path: n.path, wildChild: n.wildChild, nType: n.nType, priority: n.priority, children: n.children, handlers: n.handlers, fullPath: n.fullPath, }, paramsCount: globalParamsCount, } } n = n.children[i] continue walk } } if !n.wildChild { // If the path at the end of the loop is not equal to '/' and the current node has no child nodes // the current node needs to roll back to last valid skippedNode if path != "/" { for length := len(*skippedNodes); length > 0; length-- { skippedNode := (*skippedNodes)[length-1] *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node if value.params != nil { *value.params = (*value.params)[:skippedNode.paramsCount] } globalParamsCount = skippedNode.paramsCount continue walk } } } // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. value.tsr = path == "/" && n.handlers != nil return value } // Handle wildcard child, which is always at the end of the array n = n.children[len(n.children)-1] globalParamsCount++ switch n.nType { case param: // fix truncate the parameter // tree_test.go line: 204 // Find param end (either '/' or path end) end := 0 for end < len(path) && path[end] != '/' { end++ } // Save param value if params != nil { // Preallocate capacity if necessary if cap(*params) < int(globalParamsCount) { newParams := make(Params, len(*params), globalParamsCount) copy(newParams, *params) *params = newParams } if value.params == nil { value.params = params } // Expand slice within preallocated capacity i := len(*value.params) *value.params = (*value.params)[:i+1] val := path[:end] if unescape { if v, err := url.QueryUnescape(val); err == nil { val = v } } (*value.params)[i] = Param{ Key: n.path[1:], Value: val, } } // we need to go deeper! if end < len(path) { if len(n.children) > 0 { path = path[end:] n = n.children[0] continue walk } // ... but we can't value.tsr = len(path) == end+1 return value } if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath return value } if len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/") } return value case catchAll: // Save param value if params != nil { // Preallocate capacity if necessary if cap(*params) < int(globalParamsCount) { newParams := make(Params, len(*params), globalParamsCount) copy(newParams, *params) *params = newParams } if value.params == nil { value.params = params } // Expand slice within preallocated capacity i := len(*value.params) *value.params = (*value.params)[:i+1] val := path if unescape { if v, err := url.QueryUnescape(path); err == nil { val = v } } (*value.params)[i] = Param{ Key: n.path[2:], Value: val, } } value.handlers = n.handlers value.fullPath = n.fullPath return value default: panic("invalid node type") } } } if path == prefix { // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node // the current node needs to roll back to last valid skippedNode if n.handlers == nil && path != "/" { for length := len(*skippedNodes); length > 0; length-- { skippedNode := (*skippedNodes)[length-1] *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node if value.params != nil { *value.params = (*value.params)[:skippedNode.paramsCount] } globalParamsCount = skippedNode.paramsCount continue walk } } // n = latestNode.children[len(latestNode.children)-1] } // We should have reached the node containing the handle. // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath return value } // If there is no handle for this route, but this route has a // wildcard child, there must be a handle for this path with an // additional trailing slash if path == "/" && n.wildChild && n.nType != root { value.tsr = true return value } if path == "/" && n.nType == static { value.tsr = true return value } // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation for i, c := range []byte(n.indices) { if c == '/' { n = n.children[i] value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) return value } } return value } // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path value.tsr = path == "/" || (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && path == prefix[:len(prefix)-1] && n.handlers != nil) // roll back to last valid skippedNode if !value.tsr && path != "/" { for length := len(*skippedNodes); length > 0; length-- { skippedNode := (*skippedNodes)[length-1] *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node if value.params != nil { *value.params = (*value.params)[:skippedNode.paramsCount] } globalParamsCount = skippedNode.paramsCount continue walk } } } return value } }
六、gin.Context数据结构
Go
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
params *Params
skippedNodes *[]skippedNode
// This mutex protects Keys map.
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]any
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// queryCache caches the query result from c.Request.URL.Query().
queryCache url.Values
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests.
sameSite http.SameSite
}
gin.Context 作为处理 http 请求的通用数据结构,不可避免地会被频繁创建和销毁。为了缓解GC 压力,gin 中采用对象池sync.Pool进行Context 的缓存复用,处理流程如下:
- http 请求到达时,从 pool 中获取 Context,倘若池子已空,通过 pool.New 方法构造新的 Context补上空缺
- http 请求处理完成后,将Context 放回 pool 中,用以后续复用
Go
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从对象池获取一个 context
c := engine.pool.Get().(*Context)
// 重置或初始化 context
c.writermem.reset(w)
c.Request = req
c.reset()
// 处理 http 请求
engine.handleHTTPRequest(c)
// 把 context 放回对象池
engine.pool.Put(c)
}
七、总结
- gin 将 Engine 作为 http.Handler 的实现类进行注入,从而融入 Golang net/http 标准库的框架之内
- gin 中基于 handler 链的方式实现中间件和处理函数的协调使用
- gin 中基于压缩前缀树的方式作为路由树的数据结构,对应于9种 http 方法共有 9 棵树
- gin 中基于 gin.Context 作为一次 http 请求贯穿整条 handler chain 的核心数据结构
- gin.Context 是一种会被频繁创建销毁的资源对象,因此使用对象池 sync.Pool 进行缓存复用