一个 HTTP 请求从 Accept 到 Handler,中间发生了什么?这篇用原始 TCP 客户端和 ConnState 钩子,实测连接状态、goroutine 数、响应头的生成规则、panic 的处理和客户端断开的感知,并对着 server.go 的源码确认每个连接多出的那个 goroutine。
一个 TCP 连接上连续发 2 个请求再关闭,ConnState 依次是:new → active → idle → active → idle → closed
用 ConnState 钩子看到,一个 TCP 连接依次是 new → active → idle → active → idle → closed:keep-alive 让同一个连接服务多个请求,请求之间是 idle。
500 个连接各有一个请求卡在 Handler 里:goroutine 数 2 → 1002(增加 1000,约每个连接 2.0 个) 放行并关闭连接后 goroutine 数 2
500 个连接各有一个请求卡在 Handler 里,goroutine 增加 1000,即每个连接约 2 个;放行并关闭连接后回到基线。文档说 Serve 为每个连接创建一个服务 goroutine,多出来的那个是 Handler 运行期间的后台读 goroutine(server.go 里的 startBackgroundRead)。
/small → HTTP/1.1 200 OK | Content-Length: 100 | Content-Type: text/plain; charset=utf-8;body 100 字节 /flush → HTTP/1.1 200 OK | Content-Type: text/plain; charset=utf-8 | Transfer-Encoding: chunked;body 10 字节 /big → HTTP/1.1 200 OK | Content-Type: text/plain; charset=utf-8 | Transfer-Encoding: chunked;body 10000 字节 /twice → HTTP/1.1 200 OK | Content-Length: 1 | Content-Type: text/plain; charset=utf-8;body 1 字节 /twice 时服务器的错误日志:"http: superfluous response.WriteHeader call from main.main.func7 (main.go:95)"
100 字节的响应带 Content-Length: 100;Handler 里 Flush 过,或者响应较大(10000 字节),就是 Transfer-Encoding: chunked;没设类型时自动嗅探成 text/plain; charset=utf-8。重复调用 WriteHeader 不生效,服务器日志会出现 “superfluous response.WriteHeader call”。
请求 /boom:客户端收到 0 字节(""),读结束原因 <nil>——连接被直接关掉了
之后再请求 /ok:正常返回 "ok"(进程没有崩溃)
服务器日志第一行:http: panic serving 127.0.0.1:55085: handler 里 panic服务器恢复这个 panic、记一行 http: panic serving、关掉这个连接:客户端读到 0 字节,其它请求不受影响,进程不崩。
客户端在请求发出 100 ms 后直接关闭连接:handler 在 100 ms 后发现 r.Context() 已取消:context canceled
客户端中途关闭连接:后台读发现连接关闭,r.Context() 被取消(实验里约 100 ms 的测量粒度内就取消了),Handler 应该监听 ctx.Done() 尽早收手。
A ConnState represents the state of a client connection to a server. It's used by the optional Server.ConnState hook. const StateNew ConnState = iota ... func (c ConnState) String() string
Serve accepts incoming connections on the Listener l, creating a new service goroutine for each. The service goroutines read requests and then call s.Handler to reply to them. HTTP/2 support is only enabled if the Listener returns *tls.Conn connections and they were configured with "h2" in the TLS Config.NextProtos. Serve always returns a non-nil error and closes l. After Server.Shutdown or Server.Close, the returned error is ErrServerClosed.
server.go:731: func (cr *connReader) startBackgroundRead() {
server.go:742: go cr.backgroundRead()
server.go:2126: w.conn.r.startBackgroundRead()在本机的 server.go 里,startBackgroundRead 在 731 行,里面第 742 行启动后台 goroutine,第 2126 行调用它。行号只对 Go 1.27.1 有效。