Golang | http/server Gin框架简述

http/server

http指的是Golang中的net/http包,这里用的是1.23.10。

概览

http包的作用文档里写的很简明:Package http provides HTTP client and server implementations.
主要是提供http的客户端和服务端,也就是能作为客户端发http请求,也能作为服务端接收http请求。
翻译一下文档中服务端的Overview:

ListenAndServe 会启动一个带有指定地址和Handler的 HTTP 服务器。Handler通常为 nil,这意味着会使用 DefaultServeMux。Handle 和 HandleFunc 会将处理程序添加到 DefaultServeMux 中:

ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. Handle and HandleFunc add handlers to DefaultServeMux:

http.Handle("/foo", fooHandler)// 注册路由的方式一
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {// 注册路由的方式二fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))

通过创建自定义服务器,可以对服务器的行为进行更多控制:

More control over the server’s behavior is available by creating a custom Server:

s := &http.Server{Addr:           ":8080",Handler:        myHandler,ReadTimeout:    10 * time.Second,WriteTimeout:   10 * time.Second,MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

上面就是全部了,也就是说,ListenAndServe(),这个方法是作为server的核心方法,一切围绕着这个方法来的,这个方法有两个参数,一个是地址,另一个是Handler,Handler可以理解为处理器,处理路径用的。如果不传入自定义的,就会有默认的DefaultServeMux。

什么是 ServeMux(Multiplexer)?

// DefaultServeMux is the default [ServeMux] used by [Serve].
var DefaultServeMux = &defaultServeMuxvar defaultServeMux ServeMux
type ServeMux struct {mu       sync.RWMutextree     routingNodeindex    routingIndexpatterns []*pattern  // TODO(jba): remove if possiblemux121   serveMux121 // used only when GODEBUG=httpmuxgo121=1
}

有点抽象难懂,简化版如下,大致的功能是就是一个map,把路径映射到对应的handler上,也就是处理方法上,在mvc模式里就是controller。

type ServeMux struct {// 极简版mu    sync.RWMutexm     map[string]muxEntry // 保存 路径 -> handler 的映射hosts bool                 // 是否根据 host 来区分
}
http.Handle("/foo", fooHandler)   // /foo -> fooHandler
http.HandleFunc("/bar", barFunc)  // /bar -> barFunc

简单场景够用,复杂需求再自己 NewServeMux() 或换第三方路由处理器,gin框架就是如此,Gin 用自研的 压缩前缀树(Radix Tree) 实现路由,性能比原生的要好。

关于性能:在同样只测“路由匹配”这一项时,Gin 的 Radix 树比原生 ServeMux 的 map+线性扫描 快 3~5 倍;如果放到整站 QPS 场景(带 JSON 编码、Context 池化等),高并发下 Gin 可以做到原生 mux 的 2~4 倍吞吐量,极限压测甚至能到 6 倍,但 10 倍以上的说法基本只在“纯路由微基准”里出现,生产环境很难复现

另外关于启动服务的流程:
真正的逻辑应当是把handler和对应的路径注册到路由中,然后把路由作为参数放到ListenAndServe中去启动服务,比较符合开发逻辑,但是go做了简化。Handle和HandleFunc只是语法糖而已,简化了把handler方法注册到默认的ServeMux里面的步骤,调用http.Handle的时候,本质就是本handler方法及对应路径注册到ServeMux里,无论是默认还是自定义的。

// 没有语法糖的世界(繁琐)
func withoutSugar() {
mux := http.DefaultServeMux// 注册 handler 对象
mux.Handle("/handler", &myHandler{})// 注册 handler 函数需要多一步转换
mux.Handle("/handlerfunc", http.HandlerFunc(myHandlerFunc))http.ListenAndServe(":8080", nil)
}// 有语法糖的世界(简洁)
func withSugar() {
http.Handle("/handler", &myHandler{})        // 自动处理对象
http.HandleFunc("/handlerfunc", myHandlerFunc) // 自动转换函数http.ListenAndServe(":8080", nil)
}type myHandler struct{}
func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler")
}func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "HandlerFunc")
}

ServeMux

官方说法叫HTTP request multiplexer,http请求多路分发器,但是很书面,中文口语语境可以叫路由。

It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.
它将每个传入请求的 URL 与已注册的模式列表进行匹配,并调用与 URL 最匹配的模式对应的处理程序。
这个结构体是路由匹配的实现,先看老版本,比较朴素,使用的map。( Go 1.21+之前)

type ServeMux struct {mu    sync.RWMutexm     map[string]muxEntryes    []muxEntry // slice of entries sorted from longest to shortest.hosts bool       // whether any patterns contain hostnames
}

老版本的ServeMux用的是map结构,很符合直接,一个path直接映射一个handler,但是如果去实现前缀匹配等模式匹配就无法O(1)的映射了,需要去扫表,而前缀匹配又是web开发事实上的刚需,所以是需要实现的,因此为了改进效率放弃了单纯的朴素的map实现,改用了树实现。

前缀匹配:就是路由系统只比较 URL 的开头部分,来决定由哪个处理函数来接管请求。
例如,我们定义了以下路由:
/api/users -> list_users 函数
/api/users/create-> create_user 函数
/static/... -> 静态文件处理函数
当一个请求 GET /api/users/123 进来时,框架会从根 / 开始,先匹配到 /api,再匹配到 /api/users。虽然它不完全等于 /api/users/123,但路由系统通常会在匹配了固定前缀后,将剩余部分作为参数(例如 123)传递给处理函数,或者交由更具体的路由规则(如 /api/users/)来处理。

关于模式匹配:
文档原文写的很清晰,前缀匹配是模式匹配的一类:
Patterns
Patterns can match the method, host and path of a request. Some examples:
“/index.html” matches the path “/index.html” for any host and method.
“GET /static/” matches a GET request whose path begins with “/static/”.
“example.com/” matches any request to the host “example.com”.
“example.com/{$}” matches requests with host “example.com” and path “/”.
“/b/{bucket}/o/{objectname…}” matches paths whose first segment is “b” and whose third segment is “o”. The name “bucket” denotes the second segment and “objectname” denotes the remainder of the path.

go1.21之后的ServeMux结构如下:

type ServeMux struct {mu       sync.RWMutex// 这是核心的结构,核心路由存储结构,类似 前缀树(Trie)tree     routingNode//	用于快速定位候选节点,减少匹配树遍历// 快速索引 Method + Host + Pathindex    routingIndex // 保存所有注册过的 pattern,未来可能移除patterns []*pattern  // TODO(jba): remove if possiblemux121   serveMux121 // used only when GODEBUG=httpmuxgo121=1
}

比较关键的是tree和index字段,这两个字段的简单理解:
tree → 决策树,用于高效匹配请求 URL 找到 handler
index → 索引表,用于在注册 pattern 或检测冲突时快速筛选潜在冲突 pattern

tree的详情如下:

// A routingNode is a node in the decision tree.
// The same struct is used for leaf and interior nodes.
type routingNode struct {// A leaf node holds a single pattern and the Handler it was registered// with.pattern *patternhandler Handler// An interior node maps parts of the incoming request to child nodes.// special children keys://     "/"	trailing slash (resulting from {$})//	   ""   single wildcardchildren   mapping[string, *routingNode]multiChild *routingNode // child with multi wildcardemptyChild *routingNode // optimization: child with key ""
}
// 用ai翻译并解释了一下:
// routingNode 是决策树中的一个节点。
// 既可作为叶子节点,也可作为中间节点。
// 叶子节点存 handler,中间节点存子节点用于路径匹配。
type routingNode struct {// 与该节点关联的路由模式。// 叶子节点:存储用户注册的具体 pattern。// 中间节点:如果此路径没有 handler,可能为 nil。pattern *pattern// 与 pattern 对应的处理函数。// 只有叶子节点或注册了 handler 的节点才非 nil。handler Handler// 子节点映射:key 为路径的下一段,value 为子节点。// 用于中间节点匹配请求路径的下一个部分。// 特殊 key://   "/" - 表示结尾斜杠节点(pattern 以 {$} 结尾)//   ""  - 表示单段通配符,匹配任意单路径片段children   mapping[string, *routingNode]// 多段通配符子节点(pattern 以 '*' 结尾)。// 匹配剩余的任意路径段。multiChild *routingNode // child with multi wildcard// 空 key 子节点的优化。// 用于快速访问单段通配符节点,避免每次查 map。emptyChild *routingNode // optimization: child with key ""
}

pattern + handler → 叶子节点信息,用于最终请求处理。
children → 中间节点映射,用于逐段匹配 URL。
multiChild → 多段通配符,匹配剩余路径段。
emptyChild → 单段通配符优化,减少 map 查找。

下面是index字段对应的源码:

// A routingIndex optimizes conflict detection by indexing patterns.
//
// The basic idea is to rule out patterns that cannot conflict with a given
// pattern because they have a different literal in a corresponding segment.
// See the comments in [routingIndex.possiblyConflictingPatterns] for more details.
type routingIndex struct {// map from a particular segment position and value to all registered patterns// with that value in that position.// For example, the key {1, "b"} would hold the patterns "/a/b" and "/a/b/c"// but not "/a", "b/a", "/a/c" or "/a/{x}".segments map[routingIndexKey][]*pattern// All patterns that end in a multi wildcard (including trailing slash).// We do not try to be clever about indexing multi patterns, because there// are unlikely to be many of them.multis []*pattern
}// routingIndex 用于优化路由冲突检测,通过对 pattern 做索引加速查找。
// 基本思路:
// 通过记录每个路径片段位置的字面值,快速排除不可能冲突的 pattern。
// 具体细节可参考 possiblyConflictingPatterns 方法的注释。
type routingIndex struct {// segments 是一个 map:// key 是某个路径片段的位置和对应的字面值// value 是所有在该位置有相同字面值的已注册 pattern// 举例:// key {1, "b"} 会存储 pattern "/a/b" 和 "/a/b/c"// 不会存 "/a", "b/a", "/a/c" 或 "/a/{x}" 等segments map[routingIndexKey][]*pattern// 存储所有以多段通配符结尾的 pattern(包括结尾斜杠)。// 由于这种 pattern 数量通常不多,不做复杂索引优化。multis []*pattern
}
  • index的目的
    ServeMux 在注册新路由时,需要判断是否与已有 pattern 冲突(例如 /api/users 与 /api/{id})。
    routingIndex 提供快速索引,避免每次都扫描所有 pattern。
  • segments字段的解释
    以 {位置, 字面值} 为 key,把所有对应 pattern 归类。
    这样在检测冲突时,只需看 片段位置和字面值匹配的 pattern,排除不可能冲突的 pattern,提高效率。
  • multis的解释
    用于存储以 * 结尾的多段通配符 pattern。
    由于数量通常少,直接列表遍历即可,不做复杂索引。

再次总结一下:
tree → 决策树,用于高效匹配请求 URL 找到 handler,即 请求匹配
index → 索引表,用于在注册 pattern 或检测冲突时快速筛选潜在冲突 pattern,即 冲突检测与注册优化

中间件

go没有直接提供中间件,不过提供了接口以供在此基础上进行开发。
这个接口就是Handler接口,这是中间件的基础。

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}

中间件的本质就是包装一个 Handler,在调用真正的 Handler 前后增加逻辑(例如日志、鉴权、限流)。
举例:

func loggingMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {fmt.Println("Request:", r.Method, r.URL.Path)next.ServeHTTP(w, r) // 调用下一个 handler})
}func helloHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello, World!")
}func main() {mux := http.NewServeMux()mux.HandleFunc("/", helloHandler)// 包装 mux 作为中间件loggedMux := loggingMiddleware(mux)http.ListenAndServe(":8080", loggedMux)
}

以下是一个完整的例子

package httptestimport ("fmt""io""net/http""testing"
)func TestClient(t *testing.T) {resp, err := http.Get("http://127.0.0.1:8080/hello?name=tom")if err != nil {t.Fatal(err)fmt.Println("Error making GET request:", err)}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {t.Fatalf("Expected status OK, got %s", resp.Status)} else {fmt.Println("Response status:", resp.Status)// 读取响应体body, err := io.ReadAll(resp.Body)if err != nil {t.Fatal(err)fmt.Println("Error reading response body:", err)}fmt.Println("Response body:", string(body))}
}
func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, World!")
}// http.Handle 需要实现 http.Handler 接口
type MyHandler struct{}func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello from MyHandler!")
}func TestServer(t *testing.T) {mux := http.NewServeMux()mux.HandleFunc("/", handler)mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello %s", r.URL.Query().Get("name"))})// 创建全局中间件globalMiddleware := func(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// 全局日志fmt.Printf("全局日志: %s %s\n", r.Method, r.URL.Path)// 全局 CORSw.Header().Set("Access-Control-Allow-Origin", "*")next.ServeHTTP(w, r)})}err := http.ListenAndServe("localhost:8080", globalMiddleware(mux))if err != nil {return}
}

Gin

概览

原生的http包自1.22后有了基数树后,性能还是可以的,如果是很小的项目,没有什么中间件要求可以不用框架,但是很多项目还是规模大于此的,这时候框架就比可不可少了,现在(2025年9月13日)还在维护的框架中,gin是性能不错的一个。gin是基于http包的一个框架,其实现的压缩前缀树性能很不错,提供的扩展功能也多,官方文档是这么介绍自己的:

Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

快速示例

  • 最小 runnable 代码(例子)
	router := gin.Default()router.GET("/ping", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",})})router.Run() // listen and serve on 0.0.0.0:8080

和http包一样简洁,gin.Default()创建一个gin的Engine,在对应的位置注册路由,再要调用Run()就能快速启动,就如同http.ListenAndServe()一般。

gin的Engine是其核心组件,Engine实现了http中的Handler接口,因此可以把这个gin的Engine当做一个Handler来用,因此不用Run()启动也是可以的,这样就可以实现优雅停机了,下面是一个优雅停机的例子。

	router := gin.Default()router.GET("/ping", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",})})server := &http.Server{Addr:           fmt.Sprintf(":%s", "your_port"),Handler:        router,MaxHeaderBytes: 1 << 20,}go func() {if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {fmt.Println("Server error:", err)}}()sig := make(chan os.Signal, 1)signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) // Ctrl+C<-sigfmt.Println("\nShutting down server...")ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()if err := server.Shutdown(ctx); err != nil {log.Println("关闭服务时发生错误", err)return}log.Println("服务成功关闭")

启动函数与中间件

概述

常用启动函数:Default vs New
Default()相比New只是设置了两个中间件,一个日志一个恢复。

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

使用New的话就是不设置这两个中间件,用其他的,而中间件的本质也是一个接口gin.HandlerFunc,如同http包一样:

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

调用Use方法就可以添加中间件:

func main() {engine := gin.New() // 没有默认中间件// 手动添加所需的中间件engine.Use(gin.Logger())   // 如果你需要日志engine.Use(gin.Recovery()) // 如果你需要恢复功能engine.GET("/hello", func(c *gin.Context) {c.String(200, "Hello, World!")})engine.Run(":8080")
}

中间件执行流程:

func MyMiddleware(c *gin.Context) {// 1. 处理请求前的逻辑t := time.Now()c.Next() // 2. 关键:执行后续中间件和Handler// 3. 处理请求后的逻辑latency := time.Since(t)fmt.Println(latency)
}

应用场景:日志记录、权限校验、限流、 panic recovery。

以下是一个完整的例子:

// 自定义耗时中间件
func CostMiddleware() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now() // 记录开始时间c.Next() // 执行后续 handler 和其他中间件duration := time.Since(start) // 计算耗时log.Printf("[COST] %s %s | %d | %v",c.Request.Method,c.Request.URL.Path,c.Writer.Status(),duration,)}
}
func TestMiddle(t *testing.T) {router := gin.New()router.Use(CostMiddleware()) // 一定是要在注册路由之前才能生效router.GET("/ping", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",})})router.Run()
}

中间件

中间件签名
func MyMiddleware() gin.HandlerFunc {return func(c *gin.Context) {// 前置逻辑c.Next() // 调用后续中间件和最终的 handler// 后置逻辑}
}
  • 类型gin.HandlerFunc = func(*gin.Context)
  • 入参*gin.Context,贯穿整个请求生命周期,负责存取参数、请求、响应、状态。
  • 调用方式:注册时用 router.Use(MyMiddleware())group.Use(MyMiddleware())
执行顺序

Gin 的中间件和 handler 形成一条 调用链 (chain),依次调用。

--> M1(before) --> M2(before) --> Handler --> M2(after) --> M1(after)
  • c.Next() 之后的代码,在子调用返回时再执行,形成“洋葱模型”。(不写也行,就只有前方法没有后方法而已)

在这里插入图片描述

// 自定义耗时中间件
func CostMiddleware() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now() // 记录开始时间fmt.Println("CostMiddleware start...")c.Next() // 执行后续 handler 和其他中间件duration := time.Since(start) // 计算耗时log.Printf("[COST] %s %s | %d | %v",c.Request.Method,c.Request.URL.Path,c.Writer.Status(),duration,)}
}
func M1() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("M1 before")c.Next()fmt.Println("M1 after")}
}func M2() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("M2 before")c.Next()fmt.Println("M2 after")}
}func TestMiddle(t *testing.T) {router := gin.New()router.Use(CostMiddleware(), M1(), M2()) // 一定是要在注册路由之前才能生效router.GET("/ping", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",})})router.Run()
}
/*
CostMiddleware start...
M1 before
M2 before
M2 after
M1 after
20xx/xx/xx hh:mm:ss [COST] GET /ping | 200 | 0s
*/

c.Abort() 会立即停止执行后续中间件和 handler,但不会影响当前中间件中 Abort() 之后的逻辑。

Context

Gin 的 gin.Context 和 Go 标准库中的 context.Context 是两个不同的概念,用途也不同。

  • gin.Context:是 Gin 框架中的一个结构体类型,用于封装 HTTP 请求和响应的信息,以及提供一些方法,用于获取请求和响应的信息、设置响应头、设置响应状态码等操作。gin.Context 只在 Gin 框架内部使用,用于处理 HTTP 请求和响应。它与 HTTP 请求和响应一一对应,每个 HTTP 请求都会创建一个新的 gin.Context 对象,并在处理过程中传递。
  • context.Context:是 Go 标准库中的一个接口类型,用于在 Goroutine 之间传递上下文信息。context.Context 可以在 Goroutine 之间传递信息,例如传递请求 ID、数据库连接、请求超时等信息。context.Context 的具体实现是由各种库和框架提供的,例如 Gin 框架中也提供了一个 gin.Context 的实现,用于在 Gin 框架中使用 context.Context

总之,gin.Context 是 Gin 框架中用于处理 HTTP 请求和响应的上下文对象,而 context.Context 是 Go 标准库中用于在 Goroutine 之间传递上下文信息的接口类型。

在使用 Gin 框架时,可以通过 gin.Context 来访问 context.Context,从而在 Gin 框架中使用上下文信息。

ref: gin学习记录,gin.Context,context.Context - 知乎

  • 生命周期:一个请求一个 Context。
  • 核心作用:封装请求/响应、参数解析、数据传递(Set/Get)。

内部实现原理

路由匹配原理(前缀树/路由树)

简介

大名鼎鼎的前缀树,算法思想和工程实践的优秀结合范例,在http还是用map的时候,gin的这个路由树可以很大幅度的挺高性能。
gin.go这个文件中可以找到路由树放置的位置。

type Engine struct {RouterGroup
// ...delims           render.DelimssecureJSONPrefix stringHTMLRender       render.HTMLRenderFuncMap          template.FuncMapallNoRoute       HandlersChainallNoMethod      HandlersChainnoRoute          HandlersChainnoMethod         HandlersChainpool             sync.Pooltrees            methodTrees // 在这里maxParams        uint16maxSections      uint16trustedProxies   []stringtrustedCIDRs     []*net.IPNet
}

这个数据结构是tree.go这个文件中的。

type methodTrees []methodTree
type methodTree struct {method string // 每个 HTTP Method(GET/POST/...)维护一棵独立的路由树root   *node
}
type node struct {path      string // 存放该节点所表示的路径片段(可能是静态字符串,也可能是参数段 :id 或通配符 *filepath)indices   string // 存放子节点的首字符,用于快速判断往哪棵子树走wildChild bool // 标记当前节点是否有通配符(参数/通配)子节点nType     nodeType // 节点类型:static / param / catchAll / rootpriority  uint32children  []*node // child nodes, at most 1 :param style node at the end of the arrayhandlers  HandlersChain // 命中的处理函数链,如同ServeMux.tree中的HandlerfullPath  string 
}
r.GET("/user/:id/profile", handler)

Gin 会做以下事情:

  1. 找到 GET 方法对应的路由树根节点
  2. 把路径 "/user/:id/profile"/ 分割成片段:["user", ":id", "profile"]
  3. 从根节点开始逐层插入:
    • user → 静态节点
    • :id → 参数节点(wildChild=true)
    • profile → 静态节点,挂上最终 handler

这棵路由树长这样(伪图示):

(root)└── "user"└── ":id"   (wild param)└── "profile" → handler
与原生树对比
  • 静态路由(如 /ping)
    ServeMux 和 Gin 都很快,几乎差不多
  • 动态路由 / 参数路由
    Gin 明显快,因为 ServeMux 不支持参数,需要自己解析
  • 通配符匹配
    Gin 更灵活,内置解析
    ServeMux 只能末尾 *,额外处理开销由用户承担

总结性能:

  • Gin 更适合复杂路由(参数、通配符、多 Method)
  • ServeMux 适合简单静态路由、高并发短路径匹配

Context 的复用(sync.Pool)

Gin Context 复用原理
Gin 中,每一个 HTTP 请求都会创建一个 *gin.Context 对象,负责:

  • 保存请求和响应对象(*http.Requesthttp.ResponseWriter
  • 管理中间件链执行
  • 存储路径参数(c.Params
  • 保存状态(c.Writer.Status()c.Errors 等)
    如果每次请求都 new 一个 Context,会导致 大量内存分配和 GC,在高并发场景下性能损失明显。
    为了解决这个问题,Gin 使用了 sync.Pool 来复用 Context。
  • 中间件链实现思路
  • Recovery 的实现要点…

等等一系列功能的基石作用,复用的源码在下面:

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context) // 此处c.writermem.reset(w)c.Request = reqc.reset()engine.handleHTTPRequest(c)engine.pool.Put(c)
}

每次都复用,这样性能好一些。


以上皆为学习笔记,难免有不准确的地方

ref
Introduction | Gin Web Framework
一步一步分析Gin框架路由源码及radix tree基数树 - 九卷技术 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/97842.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/97842.shtml

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

相关文章

Vision Transformer (ViT) :Transformer在computer vision领域的应用(三)

Experiment 上来的一段话就概括了整章的内容。 We evaluate the representation learning capabilities of ResNet, Vision Transformer (ViT), and the hybrid. 章节的一开头就说明了,对比的模型就是 ResNet,CNN领域中的代码模型。 ViT。 上一篇中提到的Hybrid模型,也就是…

5-12 WPS JS宏 Range数组规范性测试

Range()数组是JS宏中不缺少的组成部分,了解Range()数组的特性必不可少,下面我们一起测试一下各种Range()数组。 1.Range()数组特性 单元格区域:Range("a2:m2")与Range("a2","m2")的类型都是:Range/Object,功能都为单元格区域,功能…

uniapp微信小程序保存海报到手机相册canvas

在uniapp中实现微信小程序保存海报到手机相册&#xff0c;主要涉及Canvas绘制和图片保存。以下是关键步骤和代码示例&#xff1a; 一、关键代码展示&#xff1a; 1. 模板配置&#xff1a;页面展示该海报&#xff0c;可直接查看&#xff0c;也可下载保存到手机相册&#xff0c;h…

glib2-2.62.5-7.ky10.x86_64.rpm怎么安装?Kylin Linux RPM包安装详细步骤

一、准备工作 ​确认系统版本​ 这个包是 ky10的&#xff08;也就是 openEuler 20.03 LTS SP3 或类似版本&#xff09;&#xff0c;而且是 ​x86_64 架构&#xff08;就是常见的64位电脑&#xff09;​。 你要先确认你的系统是不是这个版本&#xff0c;不然可能装不上或者出问题…

webrtc之语音活动下——VAD人声判定原理以及源码详解

文章目录前言一、高斯混合模型介绍1.高斯模型举例1&#xff09;定义2&#xff09;举例说明2.高斯混合模型(GMM)1&#xff09;定义2&#xff09;举例说明3&#xff09;一维曲线二、VAD高斯混合模型1.模型训练介绍1&#xff09;训练方法2&#xff09;训练结果2.噪声高斯模型分布1…

【Redis】-- 主从复制

文章目录1. 主从复制1.1 主从复制是怎么个事&#x1f914;1.2 拓扑结构1.2.1 一主一从拓扑1.2.2 一主多从拓扑1.2.3 树形拓扑1.3 主从复制原理1.3.1 复制过程1.3.2 数据同步PSYNC1.3.2.1 replicationid/replid (复制id)1.3.2.2 复制偏移量维护1.3.3 psync运行流程1.3.4 全量复制…

开源炸场!阿里通义千问Qwen3-Next发布:80B参数仅激活3B,训练成本降90%,长文本吞吐提升10倍​

开源炸场&#xff01;阿里通义千问Qwen3-Next发布&#xff1a;80B参数仅激活3B&#xff0c;训练成本降90%&#xff0c;长文本吞吐提升10倍​ 开源世界迎来震撼突破&#xff01; 通义千问团队最新发布的Qwen3-Next架构&#xff0c;以其独创的"小而精"设计理念&#x…

【C++入门】C++基础

目录 1. 命名空间 1.1 命名空间的创建和使用 2. 输入输出 2.1 输出 2.2 输入 3. 缺省参数 3.1 全缺省 3.2 半缺省 4.函数重载 4.1 为什么C支持重载而C语言不支持&#xff1f; 4.1.2 编译的四个过程 4.2 extern是什么 5.引用 5.1 引用的特性 5.1.1 引用的“隐式类…

如何往mp4视频添加封面图和获取封面图?

前言&#xff1a;大家好&#xff0c;之前有给大家分享过mp4录像的方案&#xff0c;今天给大家分享的内容是&#xff1a;如何在添加自定义的封面图到mp4里面去&#xff0c;以及在进入回放mp4视频列表的时候&#xff0c;怎么获取mp4视频里面的封面图&#xff0c;当然这个获取到的…

你的第一个Transformer模型:从零实现并训练一个迷你ChatBot

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;注册即送-H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;破除神秘感&#xff0c;拥抱核心思想 …

【20期】沪深指数《实时交易数据》免费获取股票数据API:PythonJava等5种语言调用实例演示与接口API文档说明

​ 随着量化投资在金融市场的快速发展&#xff0c;高质量数据源已成为量化研究的核心基础设施。本文将系统介绍股票量化分析中的数据获取解决方案&#xff0c;涵盖实时行情、历史数据及基本面信息等关键数据类型。 本文将重点演示这些接口在以下技术栈中的实现&#xff1a; P…

RabbitMQ如何保障消息的可靠性

文章目录什么是消息可靠性&#xff1f;RabbitMQ消息可靠性的三个维度1. 生产者到Exchange的可靠性2. Exchange到Queue的可靠性3. Queue到消费者的可靠性核心机制详解Publisher Confirm机制消息持久化Mandatory参数消费者确认机制&#xff08;ACK&#xff09;最佳实践建议1. 合理…

二十、DevOps落地:Jenkins基础入门(一)

二十、DevOps落地&#xff1a;Jenkins基础入门&#xff08;一&#xff09; 文章目录二十、DevOps落地&#xff1a;Jenkins基础入门&#xff08;一&#xff09;1、DevOps初识1.1 什么是DevOps1.2 DevOps相关工具链1.3 什么是CICD&#xff1f;1.4 持续集成CI介绍1.5 持续交付和持…

简单易实现的数据校验方法Checksum

简单易实现的数据校验方法Checksum 在数据传输中&#xff0c;Checksum&#xff08;校验和&#xff09; 扮演着 “数据完整性哨兵” 的角色。它的主要作用是 快速检测数据在传输过程中是否发生了错误 。 下面我将详细解释它的作用、工作原理、优缺点以及典型应用。 核心作用&…

再次深入学习深度学习|花书笔记1

我已经两年没有碰过深度学习了&#xff0c;写此文记录学习过程&#xff0c;加深理解。 深度学习再次深入学习深度学习|花书笔记1信息论第四节 数值计算中的问题上溢出 和 下溢出病态条件优化法再次深入学习深度学习|花书笔记1 这本书说的太繁琐了&#xff0c;如果是想要基于这…

DeerFlow实践:华为LTC流程的评审智能体设计

目录 一、机制设计核心逻辑 二、4 个评审点智能体机制详解 &#xff08;一&#xff09;立项决策&#xff08;ATI&#xff09;智能体机制 1. 知识调用与匹配 2. 评审校验流程 3. 异常处理 &#xff08;二&#xff09;投标决策&#xff08;ATB&#xff09;智能体机制 1. …

C++与Lua交互:从原理到实践指南

核心原理&#xff1a;Lua虚拟栈机制 C与Lua能够高效交互的核心在于Lua虚拟栈的设计&#xff0c;这是一个精巧的中立通信区&#xff0c;解决了两种语言间的本质差异&#xff1a;特性对比CLua语言类型静态编译型动态解释型数据管理明确内存布局虚拟机统一管理类型系统编译时确定运…

CSS 编码规范

CSS 编码规范1 CSS1.1 编码规范1.1.1 【强制】所有声明必须以分号结尾1.1.2 【推荐】使用 2 个空格缩进1.1.3 【推荐】选择器与 { 之间保留一个空格1.1.4 【推荐】属性值规范1.1.5 【推荐】组合器规范1.1.6 【推荐】逗号分隔规范1.1.7 【推荐】注释规范1.1.8 【推荐】右大括号规…

ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务

已经不止一次自己本机电脑安装的Oracle使用plsqldev软件登入提示这个了.一般前一天还好好的&#xff0c;今天就不行了.好好总结一下吧&#xff0c;也共大家一起借鉴.主要原因还是数据的归档日志因为内部内存已经耗尽&#xff0c;不能在进行归档导致数据库启动异常&#xff0c;没…

Spring框架的JDBC模板技术和事务管理

SpringJDBCJDBC模板技术概述JDBC的模板类的使用Spring框架的事务管理配置文件方式半注解的方式纯注解的方式JDBC模板技术概述 什么是 JDBC 模板技术&#xff1f; JDBC 模板技术是 Spring 框架为简化持久层&#xff08;数据库操作&#xff09;编程而提供的一种封装机制&#xf…