当前位置: 首页 > news >正文

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 mainimport ("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 mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {// 初始化 enginer := 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 boolRedirectFixedPath boolHandleMethodNotAllowed boolForwardedByClientIP boolAppEngine boolUseRawPath boolUnescapePathValues boolRemoveExtraSlash boolRemoteIPHeaders []stringTrustedPlatform stringMaxMultipartMemory int64// UseH2C enable h2c support.UseH2C boolContextWithFallback booldelims           render.DelimssecureJSONPrefix stringHTMLRender       render.HTMLRenderFuncMap          template.FuncMapallNoRoute       HandlersChainallNoMethod      HandlersChainnoRoute          HandlersChainnoMethod         HandlersChain// 对象池,用来复用gin.Contextpool             sync.Pool// 路由树,根据不同的httpmethod,以及不同的路由,拆分http请求及处理函数trees            methodTrees     maxParams        uint16maxSections      uint16trustedProxies   []stringtrustedCIDRs     []*net.IPNet
}type RouterGroup struct {Handlers HandlersChainbasePath stringengine   *Engineroot     bool
}type Pool struct {noCopy noCopylocal     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocallocalSize uintptr        // size of the local arrayvictim     unsafe.Pointer // local from previous cyclevictimSize 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 stringroot   *node
}type methodTrees []methodTreetype node struct {path      stringindices   stringwildChild boolnType     nodeTypepriority  uint32children  []*node // child nodes, at most 1 :param style node at the end of the arrayhandlers  HandlersChainfullPath  string
}type HandlersChain []HandlerFunc
// src/net/http/method.go// http method
const (MethodGet     = "GET"MethodHead    = "HEAD"MethodPost    = "POST"MethodPut     = "PUT"MethodPatch   = "PATCH" // RFC 5789MethodDelete  = "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 = engineengine.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[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 := pathn.priority++// Empty treeif len(n.path) == 0 && len(n.children) == 0 {n.insertChild(path, fullPath, handlers)n.nType = rootreturn}parentFullPathIndex := 0walk: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 edgeif 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 #65n.indices = bytesconv.BytesToString([]byte{n.path[i]})n.path = path[:i]n.handlers = niln.wildChild = falsen.fullPath = fullPath[:parentFullPathIndex+i]}// Make new node a child of this nodeif i < len(path) {path = path[i:]c := path[0]// '/' after paramif 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 existsfor 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 itif c != ':' && c != '*' && n.nType != catchAll {// []byte for proper unicode char conversion, see #65n.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 wildcardn = n.children[len(n.children)-1]n.priority++// Check if the wildcard matchesif len(path) >= len(n.path) && n.path == path[:len(n.path)] &&// Adding a child to a catchAll is not possiblen.nType != catchAll &&// Check for longer wildcard, e.g. :name and :names(len(n.path) >= len(path) || path[len(n.path)] == '/') {continue walk}// Wildcard conflictpathSeg := pathif n.nType != catchAll {pathSeg = strings.SplitN(pathSeg, "/", 2)[0]}prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.pathpanic("'" + 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 nodeif n.handlers != nil {panic("handlers are already registered for path '" + fullPath + "'")}n.handlers = handlersn.fullPath = fullPathreturn}
}

四、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("[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) {// 从对象池获取一个 contextc := engine.pool.Get().(*Context)// 重置或初始化 contextc.writermem.reset(w)c.Request = reqc.reset()// 处理 http 请求engine.handleHTTPRequest(c)// 把 context 放回对象池engine.pool.Put(c)
}func (engine *Engine) handleHTTPRequest(c *Context) {httpMethod := c.Request.MethodrPath := c.Request.URL.Pathunescape := falseif engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {rPath = c.Request.URL.RawPathunescape = engine.UnescapePathValues}if engine.RemoveExtraSlash {rPath = cleanPath(rPath)}// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// Find route in treevalue := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {c.Params = *value.params}if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.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.allNoMethodc.writermem.Header().Set("Allow", strings.Join(allowed, ", "))serveError(c, http.StatusMethodNotAllowed, default405Body)return}}c.handlers = engine.allNoRouteserveError(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框架中采用补偿策略,将挂载的路径越多的子节点越往左排列,以便查询时优先被访问到。

type methodTree struct {method stringroot   *node
}type methodTrees []methodTreetype node struct {path      string// 每个 indice 字符对应一个孩子节点路径的首字母indices   stringwildChild boolnType     nodeType// 后续节点数量priority  uint32// 孩子节点列表children  []*node // child nodes, at most 1 :param style node at the end of the array// 处理函数链handlers  HandlersChainfullPath  string
}type HandlersChain []HandlerFunc

4、注册路由

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 := pathn.priority++// Empty treeif len(n.path) == 0 && len(n.children) == 0 {n.insertChild(path, fullPath, handlers)n.nType = rootreturn}parentFullPathIndex := 0walk: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 edgeif 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 #65n.indices = bytesconv.BytesToString([]byte{n.path[i]})n.path = path[:i]n.handlers = niln.wildChild = falsen.fullPath = fullPath[:parentFullPathIndex+i]}// Make new node a child of this nodeif i < len(path) {path = path[i:]c := path[0]// '/' after paramif 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 existsfor 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 itif c != ':' && c != '*' && n.nType != catchAll {// []byte for proper unicode char conversion, see #65n.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 wildcardn = n.children[len(n.children)-1]n.priority++// Check if the wildcard matchesif len(path) >= len(n.path) && n.path == path[:len(n.path)] &&// Adding a child to a catchAll is not possiblen.nType != catchAll &&// Check for longer wildcard, e.g. :name and :names(len(n.path) >= len(path) || path[len(n.path)] == '/') {continue walk}// Wildcard conflictpathSeg := pathif n.nType != catchAll {pathSeg = strings.SplitN(pathSeg, "/", 2)[0]}prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.pathpanic("'" + 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 nodeif n.handlers != nil {panic("handlers are already registered for path '" + fullPath + "'")}n.handlers = handlersn.fullPath = fullPathreturn}
}

5、检索路由

type nodeValue struct {handlers HandlersChainparams   *Paramstsr      boolfullPath string
}type skippedNode struct {path        stringnode        *nodeparamsCount int16
}func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {var globalParamsCount int16walk: // Outer loop for walking the treefor {prefix := n.pathif len(path) > len(prefix) {if path[:len(prefix)] == prefix {path = path[len(prefix):]// Try all the non-wildcard children first by matching the indicesidxc := path[0]for i, c := range []byte(n.indices) {if c == idxc {//  strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChildif 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 skippedNodeif path != "/" {for length := len(*skippedNodes); length > 0; length-- {skippedNode := (*skippedNodes)[length-1]*skippedNodes = (*skippedNodes)[:length-1]if strings.HasSuffix(skippedNode.path, path) {path = skippedNode.pathn = skippedNode.nodeif value.params != nil {*value.params = (*value.params)[:skippedNode.paramsCount]}globalParamsCount = skippedNode.paramsCountcontinue 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 != nilreturn value}// Handle wildcard child, which is always at the end of the arrayn = 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 := 0for end < len(path) && path[end] != '/' {end++}// Save param valueif params != nil {// Preallocate capacity if necessaryif 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 capacityi := 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'tvalue.tsr = len(path) == end+1return value}if value.handlers = n.handlers; value.handlers != nil {value.fullPath = n.fullPathreturn value}if len(n.children) == 1 {// No handle found. Check if a handle for this path + a// trailing slash exists for TSR recommendationn = n.children[0]value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")}return valuecase catchAll:// Save param valueif params != nil {// Preallocate capacity if necessaryif 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 capacityi := len(*value.params)*value.params = (*value.params)[:i+1]val := pathif unescape {if v, err := url.QueryUnescape(path); err == nil {val = v}}(*value.params)[i] = Param{Key:   n.path[2:],Value: val,}}value.handlers = n.handlersvalue.fullPath = n.fullPathreturn valuedefault: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 skippedNodeif 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.pathn = skippedNode.nodeif value.params != nil {*value.params = (*value.params)[:skippedNode.paramsCount]}globalParamsCount = skippedNode.paramsCountcontinue 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.fullPathreturn 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 slashif path == "/" && n.wildChild && n.nType != root {value.tsr = truereturn value}if path == "/" && n.nType == static {value.tsr = truereturn value}// No handle found. Check if a handle for this path + a// trailing slash exists for trailing slash recommendationfor 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 pathvalue.tsr = path == "/" ||(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&path == prefix[:len(prefix)-1] && n.handlers != nil)// roll back to last valid skippedNodeif !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.pathn = skippedNode.nodeif value.params != nil {*value.params = (*value.params)[:skippedNode.paramsCount]}globalParamsCount = skippedNode.paramsCountcontinue walk}}}return value}
}

六、gin.Context数据结构

type Context struct {writermem responseWriterRequest   *http.RequestWriter    ResponseWriterParams   Paramshandlers HandlersChainindex    int8fullPath stringengine       *Engineparams       *ParamsskippedNodes *[]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 中,用以后续复用
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {// 从对象池获取一个 contextc := engine.pool.Get().(*Context)// 重置或初始化 contextc.writermem.reset(w)c.Request = reqc.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 进行缓存复用


http://www.mrgr.cn/news/56592.html

相关文章:

  • lua while循环
  • 【设计模式】深入理解Python中的桥接模式(Bridge Pattern)
  • Eclipse Java 构建路径
  • 【C++干货篇】——类和对象的魅力(四)
  • JavaSE之多态
  • 算法Day-7
  • Java | Leetcode Java题解之第501题二叉搜索树中的众数
  • 有什么好点子帮助更好的学习英语吗?
  • MySQL-事物隔离级别
  • C++ —— 实现一个日期类
  • 使用Mock库进行依赖注入的实用指南
  • TinyC编译器5—词法分析
  • git 下载慢
  • input标签v-model属性失效
  • 信发软件之展示excel文档——未来之窗行业应用跨平台架构
  • 图像处理学习笔记-20241021
  • Ubuntu配置FTP
  • eCAP超声波测距-ePWM电机调速
  • 影刀RPA实战:网页爬虫之我爱听评书
  • 数据结构 - 树,三探之代码实现
  • 如何看待AI技术的应用前景?
  • AI处理图片和视频的网址
  • 帝国CMS – AutoTitlePic 自动生成文章标题图片插件
  • ARL 灯塔 | ARL 灯塔 — 字典替换
  • 路径参数和post请求方式在请求资源时如何选择------各自的优势和使用场景比较
  • 基于深度学习的声纹识别