直接调用 http.Server.Close() 会立即关闭监听 socket 并中断所有活跃请求,导致响应截断、panic 和 io.EOF 错误;应改用 Shutdown() 实现优雅关闭,它停止新连接并等待现有请求完成,超时后强制终止。

为什么直接调用 http.Server.Close() 会导致请求被中断
直接调用 srv.Close() 会立即关闭监听 socket,新连接被拒绝,但更重要的是——它**不会等待正在处理的请求完成**。底层 net.Listener 的 Close() 调用会触发所有活跃 net.Conn 的读写返回 io.EOF 或 use of closed network connection,导致中间件、handler 内部的 WriteHeader 或 Write panic,或返回不完整响应。
典型错误现象:write tcp 127.0.0.1:8080->127.0.0.1:54321: use of closed network connection;客户端收到截断的 JSON 或空响应;日志里出现 http: Server closed without waiting for connections to finish。
用 http.Server.Shutdown() 替代 Close() 是唯一推荐方式
Shutdown() 是 Go 1.8+ 引入的优雅关闭标准方案,它会:停止接受新连接、等待所有活跃请求完成(包括长连接上的流式响应)、在超时后强制关闭剩余连接。
实操要点:
立即学习“go语言免费学习笔记(深入)”;
- 必须传入一个
context.Context,超时由context.WithTimeout控制,而非Shutdown()自身参数 - 不能在 handler 里调用
Shutdown(),应在信号监听或管理命令中触发 - 需确保所有 handler 都遵守 HTTP 协议语义(比如不阻塞在无界 channel 上、不忽略
Request.Context().Done()) - 若使用自定义
http.Transport或反向代理,它们的 idle 连接池需单独处理,Shutdown()不影响它们
示例关键片段:
srv := &http.Server{Addr: ":8080", Handler: myMux}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// 收到 SIGTERM 后
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
log.Println("shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server shutdown error:", err)
}
如何让长轮询、流式响应(如 SSE)也受控退出
Shutdown() 默认只等 handler 函数返回,但若 handler 在 for-select 循环中持续写入(如 text/event-stream),它可能永远不退出——因为连接未关闭,ResponseWriter 仍可写。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
正确做法是:在 handler 中监听 Request.Context().Done(),并在该 channel 关闭时主动 break:
func sseHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "streaming unsupported", http.StatusInternalServerError)
return
}
for {
select {
case <-r.Context().Done():
return // Shutdown 触发后,此分支立即执行
case msg := <-messageChan:
fmt.Fprintf(w, "data: %s\n\n", msg)
flusher.Flush()
}
}
}
注意:r.Context() 会在 Shutdown() 开始时被取消,无需额外同步机制。
常见陷阱:TLS、Graceful Restart 和第三方库兼容性
使用 http.Server.TLSConfig 时,Shutdown() 行为一致,无需特殊处理;但若用 http.ListenAndServeTLS() 启动,则需改用 srv.Serve(tlsListener) 模式才能控制 listener 生命周期。
Graceful Restart(零停机重启)不是 Shutdown() 的职责,它需要 fork 子进程、传递 fd、协调旧进程退出,应使用 facebookgo/grace 或 cloudflare/graceful 等专用库,而非自行实现。
某些中间件(如旧版 gorilla/handlers.CompressHandler)可能忽略 Request.Context(),导致无法及时响应关闭信号——务必检查所用中间件是否支持 context 取消。
最易被忽略的一点:如果服务启用了 http.Server.IdleTimeout,它会影响 Shutdown() 的等待逻辑——空闲连接会被提前关闭,但活跃请求不受影响;而 ReadTimeout/WriteTimeout 已被弃用,不应设置。

















