Go HTTP服务器启动后阻塞于net.Listener.Accept()等待连接,每接受一个连接即启goroutine调用ServeHTTP处理请求;默认使用http.DefaultServeMux路由,超时需显式配置ReadTimeout等字段,且Handler必须在该goroutine内完成以保障上下文与超时生效。

Go HTTP 服务器如何启动并进入请求处理循环
Go 的 http.ListenAndServe 启动后,并不直接执行业务逻辑,而是阻塞在 net.Listener.Accept() 上等待连接。每次 Accept 到一个新连接,就会起一个 goroutine 调用 srv.ServeConn()(或 srv.Serve()),最终落到 serverHandler.ServeHTTP —— 它把请求交给注册的 http.Handler 实例处理。
关键点在于:整个生命周期始于一次 Accept,止于响应写完并关闭连接(或超时)。中间所有 handler 链都必须在这个 goroutine 内完成,否则上下文取消、超时等信号就失效了。
-
http.DefaultServeMux是默认 handler,路由匹配逻辑在ServeHTTP内部完成;第三方路由器(如 Gin 的Engine、Echo 的Echo)本质也是实现了http.Handler接口 - 不要在 handler 外部起 goroutine 去“提前处理请求”,除非你显式传入
context.Context并监听ctx.Done() - 若用
http.Server自定义配置,务必设置ReadTimeout/WriteTimeout或ReadHeaderTimeout,否则慢连接可能长期占用 goroutine
Gin/Echo 框架中 Context 创建与中间件执行顺序
以 Gin 为例:Engine.ServeHTTP 一进来就 new 一个 *gin.Context,然后依次调用全局中间件、路由匹配、路由级中间件、最终 handler。Echo 同理,Echo.ServeHTTP 构造 echo.Context 后走 findRouter() → applyMiddleware() → handle()。
这个顺序不可跳过或重排——中间件必须在 handler 执行前拿到原始 *http.Request,并在 handler 返回后还能访问响应状态(如 status code、body size)。否则日志、鉴权、耗时统计都会出错。
立即学习“go语言免费学习笔记(深入)”;
- 中间件函数签名必须接收框架 Context(
*gin.Context或echo.Context),不能只用原生*http.Request - Gin 中
c.Request.Context()是起点,所有透传(如 trace_id)必须基于它派生,而非用context.Background() - Echo 的
c.Response().Status只在 handler 返回后才可读取准确值;想记录响应体大小,得用echo.WrapResponseWriter包装ResponseWriter
为什么 context.WithValue 不该在 handler 内部多次调用
每个请求对应唯一一个根 context(来自 r.Context()),所有子 context 都应由它派生。如果在 handler 里反复调用 context.WithValue(ctx, key, val),会导致 key 冲突、覆盖或漏传——尤其当多个中间件都往同一个 key 写值时。
正确做法是:在最外层中间件(如 trace 注入)一次性注入必要值,下游只读不写;需要修改时,用新 key 或结构体字段封装,避免字符串 key 拼写错误。
- 定义
type ctxKey string+const TraceIDKey ctxKey = "trace_id",比直接用"trace_id"更安全 - 别在异步 goroutine 里用原始
r.Context(),必须用ctx = context.WithTimeout(r.Context(), timeout)派生,否则父 context 取消时子 goroutine 不会退出 -
context.WithValue性能开销极小,但滥用会导致 context 树膨胀,调试时ctx.Value()查不到值往往是因为中间某层没传递或覆盖了
Handler 返回后,连接何时真正关闭
Go 的 http.Server 默认启用 keep-alive,只要客户端没发 Connection: close,且响应头没设 Content-Length 或 Transfer-Encoding: chunked,连接就会复用。Handler 返回 ≠ 连接关闭。
这意味着:你在 handler 末尾打的日志,不代表请求链路已彻底结束;如果有 defer 清理资源(如 DB 连接、文件句柄),必须确保它们不依赖“连接已断”这一假设。
- 连接关闭时机由
http.Transport和底层 TCP 状态决定,服务端无法强制立即断开(除非设srv.SetKeepAlivesEnabled(false)) - 若 handler 启动了后台 goroutine(如发 MQ、写日志到远端),务必用
ctx.Done()控制其生命周期,否则可能 panic 或泄漏 - 观察连接是否复用,可看响应头是否有
Connection: keep-alive和Keep-Alive: timeout=...


















