Go的net/http标准库可能是所有语言中最好用的HTTP库。不需要任何第三方框架,几行代码就能写出一个生产级别的HTTP服务器。
最简HTTP服务器
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
})
http.ListenAndServe(":8080", nil)
}
这8行代码启动的服务器自带:连接复用(HTTP/1.1 Keep-Alive)、并发处理(每个请求一个goroutine)、优雅的超时处理。放在Java里,光Tomcat配置就不止8行。
Handler接口
Go HTTP服务器的核心是Handler接口:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
只有一个方法。任何实现了ServeHTTP的类型都可以处理HTTP请求。这个设计极度简洁,也是Go中间件模式的基础。
// 用struct实现Handler
type apiHandler struct {
db *sql.DB
}
func (h *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 可以访问h.db
users, _ := h.db.Query("SELECT name FROM users")
// ...
}
func main() {
db, _ := sql.Open("mysql", "user:pass@/dbname")
http.Handle("/api/", &apiHandler{db: db})
http.ListenAndServe(":8080", nil)
}
ServeMux路由
默认的http.DefaultServeMux是一个简单的路由器,支持精确匹配和前缀匹配:
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler) // 兜底路由
mux.HandleFunc("/api/users", usersHandler) // 精确匹配
mux.HandleFunc("/api/users/", userHandler) // 前缀匹配(注意末尾的/)
DefaultServeMux的路由能力有限——不支持路径参数(如/users/:id)、不支持按HTTP方法区分。这是很多人转向第三方路由库(chi、gorilla/mux)的原因。但对于简单的API服务,DefaultServeMux完全够用。
中间件模式
Go的中间件就是一个接收Handler返回Handler的函数:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !validateToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// 组合中间件
func chain(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", dataHandler)
server := chain(mux, loggingMiddleware, authMiddleware)
http.ListenAndServe(":8080", server)
}
这个模式没有任何魔法,就是函数组合。跟Java的Filter链或Node.js的Express中间件本质相同,但Go的实现更直白。
生产级配置
裸写http.ListenAndServe不适合生产环境,需要配置超时和连接池:
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second, // 读取请求的超时
WriteTimeout: 10 * time.Second, // 写入响应的超时
IdleTimeout: 120 * time.Second, // Keep-Alive连接的空闲超时
MaxHeaderBytes: 1 << 20, // 请求头最大1MB
}
// 优雅关闭
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
log.Fatal(server.ListenAndServe())
几个关键超时的含义:
ReadTimeout:从accept连接到读完请求体的总时间。防止慢速客户端占用连接。WriteTimeout:从读完请求头到写完响应的总时间。防止慢速客户端导致goroutine堆积。IdleTimeout:Keep-Alive连接的空闲时间。超时后关闭连接释放资源。
不设置这些超时,你的服务器可能因为慢客户端或恶意连接耗尽资源。
性能分析:pprof
Go内置了性能分析工具,启用只需要一行import:
import _ "net/http/pprof"
// 如果用了自定义的ServeMux,需要手动注册
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
然后用go tool pprof分析:
# CPU分析(采集30秒)
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30
# 内存分析
go tool pprof http://localhost:8080/debug/pprof/heap
# goroutine分析(排查泄漏)
go tool pprof http://localhost:8080/debug/pprof/goroutine
在pprof交互界面中,top显示热点函数,web生成调用图,list funcName显示逐行耗时。
性能优化要点
- 连接复用:确保客户端读完了response body(
io.Copy(io.Discard, resp.Body)),否则连接不会被放回连接池 - JSON序列化:标准库
encoding/json性能一般,高频场景考虑json-iterator或sonic - sync.Pool复用对象:减少GC压力,特别是频繁创建的临时buffer
- 避免goroutine泄漏:给所有网络操作设置context超时,定期用pprof检查goroutine数量
Go的HTTP服务器开箱即用的性能已经很好了。在大多数业务场景下,瓶颈不在HTTP层而在数据库和业务逻辑。先用pprof找到真正的热点,再有针对性地优化。