Go 1.22 之前,标准库 net/http 的路由能力非常基础——不支持方法匹配(GET POST),不支持路径参数(/users/{id})。社区因此催生了 gin、chi、echo 等第三方路由库。
Go 1.22 改变了这一切:标准库现在原生支持方法前缀、路径参数、通配符和后缀匹配。对大多数项目来说,第三方路由库不再是必需品。
新特性速览
1
2
3
4
5
6
7
8
9
10
|
mux := http.NewServeMux()
// 方法匹配:只有 GET 请求会匹配
mux.HandleFunc("GET /api/users", listUsers)
// 路径参数:用 {name} 捕获路径段
mux.HandleFunc("GET /api/users/{id}", getUser)
// 通配符匹配剩余路径
mux.HandleFunc("GET /api/static/{path...}", serveStatic)
|
💡 旧写法 mux.HandleFunc("/api/users", ...) 仍有效,但新模式中模式字符串必须包含方法,否则会按旧语义匹配所有 HTTP 方法。
实战:完整 CRUD API
以下是一个用户管理的 REST API——只依赖标准库,零第三方依赖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
package main
import (
"encoding/json"
"log/slog"
"net/http"
"sync"
"time"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
type UserStore struct {
mu sync.RWMutex
users map[int]User
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{users: make(map[int]User), nextID: 1}
}
func (s *UserStore) List() []User {
s.mu.RLock()
defer s.mu.RUnlock()
result := make([]User, 0, len(s.users))
for _, u := range s.users {
result = append(result, u)
}
return result
}
func (s *UserStore) Get(id int) (User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
u, ok := s.users[id]
return u, ok
}
func (s *UserStore) Create(name, email string) User {
s.mu.Lock()
defer s.mu.Unlock()
u := User{
ID: s.nextID, Name: name, Email: email,
CreatedAt: time.Now(),
}
s.users[u.ID] = u
s.nextID++
return u
}
func main() {
store := NewUserStore()
mux := http.NewServeMux()
mux.HandleFunc("GET /api/users", handleListUsers(store))
mux.HandleFunc("POST /api/users", handleCreateUser(store))
mux.HandleFunc("GET /api/users/{id}", handleGetUser(store))
handler := withLogging(withRecovery(mux))
slog.Info("Server starting on :8080")
http.ListenAndServe(":8080", handler)
}
|
路由处理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
func handleListUsers(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
users := store.List()
writeJSON(w, http.StatusOK, users)
}
}
func handleCreateUser(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
return
}
if req.Name == "" || req.Email == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name and email required"})
return
}
user := store.Create(req.Name, req.Email)
writeJSON(w, http.StatusCreated, user)
}
}
func handleGetUser(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := parseInt(idStr)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
return
}
user, ok := store.Get(id)
if !ok {
writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
return
}
writeJSON(w, http.StatusOK, user)
}
}
|
辅助函数与中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func parseInt(s string) (int, error) {
var n int
for _, c := range s {
if c < '0' || c > '9' {
return 0, &strconv.NumError{Func: "ParseInt", Num: s, Err: strconv.ErrSyntax}
}
n = n*10 + int(c-'0')
}
return n, nil
}
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("request", "method", r.Method, "path", r.URL.Path, "duration", time.Since(start))
})
}
func withRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
slog.Error("panic", "error", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
|
验证
1
2
3
4
5
6
7
8
9
10
|
# 创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"[email protected]"}'
# 列出用户
curl http://localhost:8080/api/users
# 获取单个用户
curl http://localhost:8080/api/users/1
|
是否该告别 gin/chi?
| 场景 |
推荐方案 |
| 小型 API / 微服务 |
标准库足够 ✅ |
| 复杂路由(正则、自定义匹配器) |
仍需要 chi / httprouter |
| 超大项目(100+ 路由) |
gin 的 group 和 binding 更省事 |
建议:新项目先用标准库。当 HandleFunc 不够用、路由数量膨胀时再引入第三方库。Go 标准库的设计哲学是足够好——对 80% 的项目来说,它确实够了。
踩坑提醒
r.URL.Query().Get("id") 不会匹配 {id}——路径参数必须用 r.PathValue("id")
- 模式中的方法不可省略——
GET /api/users 和 /api/users 是两种语义
- 模式匹配取最具体者——
/users/new 比 /users/{id} 优先级更高
- Go 版本要求——该功能在 Go 1.22 引入,
go.mod 中需声明 go 1.22
1
2
3
|
// go.mod
module myapi
go 1.22
|
Go 团队在持续减少标准库的短板。如果你还没升级到 Go 1.22+,这就是最好的理由。