Go 高手(12):net/http 深入——Handler、中间件、ServeMux
更新时间:2026-09-01。本文是
languages/go/intermediate/高手层第 12 篇,接 结构体内存对齐。Go 的net/http标准库功能完整,除非有特殊需求,否则不需要第三方框架。理解 Handler 接口模式、中间件、ServeMux 路由、连接池,就能写出生产级的 HTTP 服务。
本文要回答的问题
http.Handler接口的设计模式是什么?为什么是 Go I/O 的典范?- 中间件在 net/http 里怎么实现?Handler 链怎么组合?
- ServeMux 的路由匹配规则是什么?Go 1.22 的路由增强有什么变化?
http.Client的连接池怎么配置?什么时候需要自定义 Transport?
一、Handler 接口:net/http 的核心
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}所有 HTTP 处理都围绕这个接口。http.HandleFunc 只是一个适配器,把函数转成 Handler:
// HandleFunc 的底层实现
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}这意味着:任何实现 ServeHTTP 的类型都可以作为 HTTP 处理器,这是 Go 接口组合的典型应用。
二、中间件:Handler 链
中间件是一个接收 Handler 返回 Handler 的函数:
// 日志中间件
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r) // 调用下一个 Handler
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
// 认证中间件
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 验证 token...
next.ServeHTTP(w, r) // 通过,继续
})
}
// 使用中间件链
handler := Logging(Auth(myHandler))
http.Handle("/", handler)中间件的工作原理:外层 Handler 包装内层 Handler,形成一个链。请求从外到内依次经过,响应从内到外返回。
三、ServeMux 路由
Go 1.22 的 ServeMux 支持路径参数和方法匹配:
mux := http.NewServeMux()
// 方法 + 路径
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("GET /users/{id}", getUser)
mux.HandleFunc("DELETE /users/{id}", deleteUser)
// 路径参数
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+
// ...
}Go 1.22 之前的路由(没有方法匹配和路径参数):
mux.HandleFunc("/users", listUsers) // 所有方法
mux.HandleFunc("/users/", userHandler) // 手动解析路径Go 1.22 的路由增强让你不需要第三方路由库就能实现 RESTful 接口。
四、http.Client 和 Transport(连接池)
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 10, // 每个 host 最大空闲连接数
IdleConnTimeout: 90 * time.Second, // 空闲连接超时
DisableCompression: false, // 启用压缩
},
}连接池参数的作用:
MaxIdleConns:控制全局空闲连接数,避免频繁建立 TCP 连接MaxIdleConnsPerHost:每个 host 的限制,避免某个 host 耗尽连接池IdleConnTimeout:空闲连接超时后关闭,释放资源
默认值问题: http.DefaultTransport 的 MaxIdleConnsPerHost 是 2,对于高并发请求同一个 host,可能不够。
// 高并发场景:增大连接池
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
}
client := &http.Client{Transport: transport}五、ResponseWriter 的细节
func handler(w http.ResponseWriter, r *http.Request) {
// 写入 Header 必须在 WriteHeader 或 Write 之前
w.Header().Set("Content-Type", "application/json")
// 主动设置状态码
w.WriteHeader(http.StatusCreated)
// 写入响应体
w.Write([]byte(`{"status": "ok"}`))
}ResponseWriter 的写入顺序:
- 先设置 Header(
Header().Set) - 然后调用
WriteHeader(可选,默认 200) - 最后
Write写入响应体
一旦调用 Write 或 WriteHeader,Header 的修改就无效了。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
忘记调用 r.Body.Close() | 连接泄漏 | defer r.Body.Close() |
| 中间件顺序搞反 | 日志在认证前记录 | 从外到内:Logging(Auth(handler)) |
| 默认连接池太小 | 高并发请求变慢 | 自定义 Transport 增大连接池 |
| 写入 Header 太晚 | Header 修改无效 | 在 Write/WriteHeader 之前设置 Header |
相关与延伸
下一篇:database/sql——连接池、事务、scan;HTTP 服务的 context 传递,见 context 包;HTTP 实战练习,见 HTTP 接口服务。
一句话总结
Go net/http 深入:Handler 接口是核心,ServeHTTP 方法实现处理逻辑;中间件是 Handler 链,包装模式实现;Go 1.22+ 的 ServeMux 支持方法和路径参数;http.Client 的 Transport 控制连接池,高并发需要增大 MaxIdleConnsPerHost;ResponseWriter 写入顺序:Header → WriteHeader → Write。