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.mzph.cn/pingmian/57278.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell重定向输入输出

我的后端学习大纲 我的Linux学习大纲 重定向介绍 标准输入介绍 从键盘读取用户输入的数据&#xff0c;然后再把数据拿到Shell程序中使用&#xff1b; 标准输出介绍 Shell程序产生的数据&#xff0c;这些数据一般都是呈现到显示器上供用户浏览查看; 默认输入输出文件 每个…

前OpenAI首席技术官为新AI初创公司筹资;我国发布首个应用临床眼科大模型 “伏羲慧眼”|AI日报

文章推荐 2024人工智能报告.zip &#xff5c;一文迅速了解今年的AI界都发生了什么&#xff1f; 今日热点 据报道&#xff0c;前OpenAI首席技术官Mira Murati正在为一家新的AI初创公司筹集资金 据路透社报道&#xff0c;上个月宣布离职的OpenAI首席技术官Mira Murati正在为一…

栈和队列(一)

栈和队列的定义和特点 栈和队列是一种特殊的线性表&#xff0c;只能在表的端点进行操作 栈的定义和特点 这就是栈的结构&#xff0c;是一个特殊的线性表&#xff0c;只能在栈顶&#xff08;或者说是表尾&#xff09;进行操作。其中top为栈顶&#xff0c;base为栈底 栈s的存储…

华为:高级ACL 特定ip访问特定ip命令

网络拓扑图&#xff1a; 网络环境&#xff1a; 全网互通即可 1.创建一个名为test的高级ACL acl name test advance 2.添加规则 ##拒绝所有ip访问 rule 10 deny ip source any destination 192.168.1.10 0.0.0.0 只允许特定ip访问特定ip rule 5 permit ip source 192.168.2.10…

【Vulnhub靶场】Kioptrix Level 5

目标 本地IP&#xff1a;192.168.118.128 目标IP&#xff1a;192.168.118.0/24 信息收集 nmap探测存活主机&#xff0c;扫全端口&#xff0c;扫服务 首先探测到目标ip为&#xff1a;192.168.118.136 nmap -sP 192.168.118.0/24nmap -p- 192.168.118.136nmap -sV -A 192.168.…

BurpSuite渗透工具的简单使用

BurpSuite渗透工具 用Burp Suite修改请求 step1&#xff1a; 安装Burp Suite。官网链接&#xff1a;Burp Suite官网 step2&#xff1a; 设置代理 step3&#xff1a; 如果要拦截https请求&#xff0c;还需要在客户端安装证书 step4&#xff1a; 拦截到请求可以在Proxy ->…

【嵌入式实时操作系统开发】智能家居入门4(FreeRTOS、MQTT服务器、MQTT协议、STM32、微信小程序)

前面已经发了智能家居入门的1、2、3了&#xff0c;在实际开发中一般都会使用到实时操作系统&#xff0c;这里就以FreeRTOS为例子&#xff0c;使用标准库。记录由裸机转到实时操作系统所遇到的问题以及总体流程。相较于裸机&#xff0c;系统实时性强了很多&#xff0c;小程序下发…

opencv环境配置-适配b站阿童木的opencv教程

首先&#xff0c;opencv作为一个库文件&#xff0c;目的是为了让更多人不需要学习底层像素操作就能上手视觉技术&#xff0c;所以他适配很多环境&#xff0c;目前电脑端我知道的就可以适配C语言 C Python MCU端就是openmv跟他最类似&#xff0c;还有个k210 canmv 阿童木教的…

Unity 山水树木

本章节内容 1. Unity对3D游戏物体的简单操作&#xff1b; 2. 构建山水树木的场景 1. Unity 简易操作 1.1 新建3D游戏场景 1. 打开Unity Hub&#xff0c;点击 New Project &#xff08;新建项目&#xff09;按键&#xff0c;选择第二项 3D(Built-In Render Pipeline)&#xf…

Linux之实战命令41:lshw应用实例(七十五)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

虚拟滚动是怎么做性能优化的?

前言 一个简单的情景模拟&#xff08;千万别被带入&#xff09;&#xff1a; A&#xff1a; 假设现在有 10 万条数据&#xff0c;你作为前端该怎么优化这种大数据的列表&#xff1f; B&#xff1a; 针对大数据列表一般不会依次性加载&#xff0c;会采用上拉加载、分页加载等…

如何用数据字典提升数据质量和决策效率?

在前面的文章中我们谈到过数据字典的概念&#xff0c;本文将继续探讨如何用数据字典提升数据质量和决策效率。 一、数据字典 数据字典&#xff1a;一种对数据的定义和描述的集合&#xff0c;它包含了数据的名称、类型、长度、取值范围、业务含义、数据来源等详细信息。 数据字…

Java中的一些名词概念

**函数式接口:** 概念&#xff1a;一个接口中的抽象方法只有一个&#xff0c;那么这个接口就是一个函数式接口。形参: 形参变量是**功能函数里的变量**&#xff0c;只有<u>在被调用的时候才分配内存单元</u>&#xff0c;<u>调用结束后立即释放</u>。…

AUTOSAR_EXP_ARAComAPI的6章笔记(2)

☞返回总目录 相关总结&#xff1a;AutoSar AP CM实例说明符的使用方法总结 6.2 实例说明符的使用方法 一、InstanceSpecifier 的概念 InstanceSpecifier 是在 [3] 中定义的一个核心概念&#xff0c;它由符合特定模型元素绝对路径的模型元素 shortName 组成&#xff0c;表现…

【10月最新】植物大战僵尸杂交版即将新增【植物】内容介绍预告(附最新版本下载链接)

新增植物 玉米旋转机 玉米旋转机是一种支持性植物&#xff0c;每4秒可散射6颗油炸玉米或黄油&#xff08;概率20%&#xff09;&#xff0c;油炸玉米经过火炬可变为爆米花&#xff0c;造成范围爆炸伤害。其价格为325&#xff0c;并在每种植一颗后&#xff0c;价格增加50。玉米旋…

Imagic: Text-Based Real Image Editing with Diffusion Models

https://openaccess.thecvf.com/content/CVPR2023/papers/Kawar_Imagic_Text-Based_Real_Image_Editing_With_Diffusion_Models_CVPR_2023_paper.pdfhttps://imagic-editing.github.io/ 问题引入 针对的是text based image editing问题&#xff0c;可以解决non rigid edit&am…

进程的了解

目录 一、进程控制块抽象&#xff08;PCB Process Control Block&#xff09; 1.pid&#xff1a;进程的身份标识 2.内存指针&#xff1a; 3.文件描述符表 进程的调度&#xff08;额外知识&#xff0c;不是进程属性&#xff09;&#xff1a; 4.进程的状态&#xff1a; 5.…

学习threejs,通过THREE.Raycaster给模型绑定点击事件

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.Raycaster光线投射概…

Go语言基础学习(Go安装配置、基础语法)

一、简介及安装教程 1、为什么学习Go&#xff1f; 简单好记的关键词和语法&#xff1b;更高的效率&#xff1b;生态强大&#xff1b;语法检查严格&#xff0c;安全性高&#xff1b;严格的依赖管理&#xff0c; go mod 命令&#xff1b;强大的编译检查、严格的编码规范和完整的…

图神经网络

定义&#xff1a; 对图上所有的属性&#xff0c;包括顶点、边、全局、上下文进行的一个可以优化的变换&#xff0c;该变换可以保存住图的对称信息&#xff08;将顶点进行另外一个顺序的排序后&#xff0c;结果不变&#xff09; Message passing neural network&#xff1a;使…