go 语言 Gin Web 框架的实现原理探究
Gin 是一个用 Go (Golang) 编写的 Web 框架,性能极优,具有快速、支持中心件、crash处理、json验证、路由组、错误管理、内存渲染、可扩展性等特点。官网地址:https://gin-gonic.com/
源码地址:https://github.com/gin-gonic/gin/tree/v1.10.0
参考视频:gin框架底层技能原理分析_哔哩哔哩_bilibili
一、net/http 及 gin 使用示例
[*]使用 net/http 创建 web 服务
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 服务
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数据结构
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 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
priorityuint32
children[]*node // child nodes, at most 1 :param style node at the end of the array
handlersHandlersChain
fullPathstring
}
type HandlersChain []HandlerFunc // 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
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
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对添加到路由树中
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, handlers)
return mergedHandlers
}
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path == '/', "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,
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})
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
c := path
// '/' after param
if n.nType == param && c == '/' && len(n.children) == 1 {
parentFullPathIndex += len(n.path)
n = n.children
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 {
parentFullPathIndex += len(n.path)
i = n.incrementChildPrio(i)
n = n.children
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
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 == '/') {
continue walk
}
// Wildcard conflict
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(pathSeg, "/", 2)
}
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 方法启动服务。
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint(" 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.method != httpMethod {
continue
}
root := t.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树、字典树或键树,是一种树形数据结构,主要用于高效地存储和查询字符串。
其特点为:
[*]根节点不包含字符,除根节点外每一个节点都只包含一个字符。
[*]从根节点到某一节点,路径上颠末的字符连接起来,为该节点对应的字符串。
[*]每个节点的所有子节点包含的字符都不相同。
https://i-blog.csdnimg.cn/direct/c2826470fef648449126a3d7db1d834f.png
2、压缩前缀树
压缩前缀树是一种高效处理字符串聚集的数据结构,通过归并共享的前缀来节省存储空间并提高检索效率。它在网络路由、编译器符号表和字符串检索等领域有广泛应用。
https://i-blog.csdnimg.cn/direct/1e7e2ad6b760491b9ef493985a6fbff5.png
3、路由树的数据结构
gin路由管理采取压缩前缀树的数据结构,相较于map数据结构,压缩前缀树可用于暗昧匹配,并且在数据量不大的情况下,压缩树的性能并不比map差。gin框架中采取补偿策略,将挂载的路径越多的子节点越往左排列,以便查询时优先被访问到。
type methodTree struct {
method string
root *node
}
type methodTrees []methodTree
type node struct {
path string
// 每个 indice 字符对应一个孩子节点路径的首字母
indices string
wildChild bool
nType nodeType
// 后续节点数量
priorityuint32
// 孩子节点列表
children[]*node // child nodes, at most 1 :param style node at the end of the array
// 处理函数链
handlersHandlersChain
fullPathstring
}
type HandlersChain []HandlerFunc4、注册路由
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path == '/', "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,
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})
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
c := path
// '/' after param
if n.nType == param && c == '/' && len(n.children) == 1 {
parentFullPathIndex += len(n.path)
n = n.children
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 {
parentFullPathIndex += len(n.path)
i = n.incrementChildPrio(i)
n = n.children
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
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 == '/') {
continue walk
}
// Wildcard conflict
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(pathSeg, "/", 2)
}
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、检索路由
type 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
// Try all the non-wildcard children first by matching the indices
idxc := path
for i, c := range []byte(n.indices) {
if c == idxc {
//strings.HasPrefix(n.children.path, ":") == n.wildChild
if n.wildChild {
index := len(*skippedNodes)
*skippedNodes = (*skippedNodes)[:index+1]
(*skippedNodes) = 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
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)
*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
globalParamsCount++
switch n.nType {
case param:
// fix truncate the parameter
// tree_test.goline: 204
// Find param end (either '/' or path end)
end := 0
for end < len(path) && path != '/' {
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) = Param{
Key: n.path,
Value: val,
}
}
// we need to go deeper!
if end < len(path) {
if len(n.children) > 0 {
path = path
n = n.children
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
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) = Param{
Key: n.path,
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)
*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
}
// 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
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
(n.nType == catchAll && n.children.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 == '/' &&
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)
*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数据结构
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 mapany
// 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 中,用以后续复用
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 进行缓存复用
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]