Go 中的 HTTP 基础:net/http 包简介、Handler 与 ServeMux、构建第一个 RESTful 端点
在 Go 后端开发中,net/http 标准库是构建 HTTP 服务的基石。它提供了从网络监听、请求解析到响应生成的全套能力,无需依赖第三方框架即可搭建生产级别的 API 服务。本章将深入剖析 net/http 的核心抽象——Handler 与 ServeMux,并手把手构建你的第一个 RESTful 端点。
1. net/http 包的核心抽象
net/http 包的设计遵循“接口即约定”的哲学,其核心由三个关键接口与结构组成:
| 组件 | 类型 | 职责 |
|---|---|---|
| Handler | interface |
定义处理 HTTP 请求的统一契约:ServeHTTP(ResponseWriter, *Request) |
| ServeMux | struct |
HTTP 请求多路复用器,根据 URL 路径将请求路由到对应的 Handler |
| Server | struct |
监听 TCP 端口、管理连接生命周期、调用 Handler 处理请求 |
设计启示: Go 的 HTTP 处理链是
Server → ServeMux → Handler 的线性管道。任何实现了 ServeHTTP 方法的类型都可以作为 Handler,这种“鸭子类型”使得中间件、路由组、拦截器都可以用统一接口组合。
2. Handler 接口详解
Handler 接口是 Go HTTP 世界的“万用插头”。其定义极其简洁:
type Handler interface {
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
两个参数分别代表:
- w (ResponseWriter):用于构建 HTTP 响应的接口,支持写入状态码、Header 和 Body。
- r (Request):包含客户端请求的全部信息——URL、Header、Body、查询参数、Cookie 等。
以下是一个自定义 Handler 的完整示例:
type HealthHandler struct{}
func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "healthy"}`))
}
常见陷阱: 在
ServeHTTP 中必须显式调用 w.WriteHeader() 设置状态码。如果先调用 w.Write(),Go 会自动写入 200 OK,但后续再调用 WriteHeader 将无效。建议始终在写入 Body 前明确设置状态码。
3. ServeMux:请求路由的核心
ServeMux 是 Go 标准库内置的 HTTP 请求路由器。它根据请求的 路径(Path)匹配已注册的 Handler,并遵循以下匹配规则:
| 注册模式 | 匹配行为 | 示例 |
|---|---|---|
/api/users |
精确匹配路径 /api/users,不匹配子路径 |
匹配 /api/users,不匹配 /api/users/123 |
/api/users/ |
前缀匹配,匹配所有以该路径开头的请求 | 匹配 /api/users/、/api/users/123、/api/users/123/orders |
/ |
默认兜底,匹配所有未被其他模式匹配的请求 | 匹配 /favicon.ico、/unknown 等 |
注册 Handler 到 ServeMux 的标准方式:
mux := http.NewServeMux()
mux.Handle("/api/health", &HealthHandler{})
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
// 使用 HandleFunc 可以避免定义单独的结构体
w.Write([]byte("users endpoint"))
})
性能提示:
ServeMux 的匹配算法基于 最长前缀匹配。当多个模式匹配同一个请求时,选择路径最长的模式。例如,同时注册 /api/ 和 /api/users/,请求 /api/users/123 会匹配后者。
4. 构建第一个 RESTful 端点
下面我们构建一个完整的 /api/tasks 端点,支持 GET(获取列表)和 POST(创建任务)两种方法。这是 RESTful API 的最小可行实现:
package main
import (
"encoding/json"
"log"
"net/http"
"sync"
)
type Task struct {
ID int `json:"id"`
Name string `json:"name"`
}
var (
tasks = []Task{{ID: 1, Name: "学习 Go HTTP"}}
nextID = 2
mu sync.Mutex
)
func tasksHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
mu.Lock()
defer mu.Unlock()
json.NewEncoder(w).Encode(tasks)
case http.MethodPost:
var newTask Task
if err := json.NewDecoder(r.Body).Decode(&newTask); err != nil {
http.Error(w, `{"error":"invalid JSON"}`, http.StatusBadRequest)
return
}
mu.Lock()
newTask.ID = nextID
nextID++
tasks = append(tasks, newTask)
mu.Unlock()
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newTask)
default:
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/tasks", tasksHandler)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
RESTful 实践要点:
- 使用
r.Method区分 HTTP 方法,而非在 URL 中嵌入动作(如/api/tasks/create)。 - 始终设置
Content-Type响应头,告知客户端返回数据的格式。 - 使用
sync.Mutex保护共享状态(如内存中的切片),避免并发读写冲突。 - 返回合适的 HTTP 状态码:
200 OK、201 Created、400 Bad Request、405 Method Not Allowed。
5. 请求处理流程可视化
下图展示了从客户端请求到 Handler 响应的完整链路,以及 ServeMux 在其中的路由决策过程:
6. 启动与验证
将上述代码保存为 main.go,执行以下命令启动服务:
go run main.go
# 输出: Server starting on :8080
使用 curl 验证端点行为:
# 获取任务列表
curl -s http://localhost:8080/api/tasks
# 输出: [{"id":1,"name":"学习 Go HTTP"}]
# 创建新任务
curl -s -X POST http://localhost:8080/api/tasks \
-H "Content-Type: application/json" \
-d '{"name":"学习 ServeMux"}'
# 输出: {"id":2,"name":"学习 ServeMux"}
# 再次获取列表,验证新增
curl -s http://localhost:8080/api/tasks
# 输出: [{"id":1,"name":"学习 Go HTTP"},{"id":2,"name":"学习 ServeMux"}]
下一步扩展思路: 当前端点将所有逻辑写在一个函数中。随着端点增多,建议按资源拆分 Handler(如
TaskHandler 结构体),并使用 http.HandlerFunc 适配器简化注册。后续章节将引入中间件模式,实现日志、鉴权、限流等横切关注点。