外观
net/http, Handler, Router
Phase 03 — Go Web 涵盖:
net/http·Handler·Router
1. 学习目标
完成本知识点后,你应该能够:
- 使用
net/http标准库启动 HTTP 服务器 - 理解
Handler接口和HandlerFunc适配器 - 编写处理 JSON 请求/响应的 handler 函数
- 使用
ServeMux注册路由(Go 1.22+ 增强路由) - 提取 URL 路径参数和查询参数
- 为数字孪生 API 实现基础 CRUD handler
2. 为什么需要
Go 标准库 net/http 功能完整,无需框架即可构建生产级 API。理解标准库原理后,学 Gin 等框架只是语法糖。先用 net/http 能帮你明白框架背后做了什么。
每个 HTTP 请求由 Go runtime 在独立 goroutine 中调用你的 Handler——这与 Node.js 单线程事件循环不同,你需要考虑并发安全(Phase 02 的 Mutex)。
3. 核心概念
3.1 Handler 接口
go
type Handler interface {
ServeHTTP(w http.ResponseWriter, r *http.Request)
}任何实现了 ServeHTTP 的类型都是 Handler。
3.2 HandlerFunc
go
type HandlerFunc func(w http.ResponseWriter, r *http.Request)
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
f(w, r)
}普通函数可通过 http.HandlerFunc(fn) 转为 Handler。
3.3 核心类型
| 类型 | 作用 |
|---|---|
http.ResponseWriter | 写响应状态码、Header、Body |
http.Request | 读 Method、URL、Header、Body |
http.ServeMux | 路由 multiplexer |
3.4 Go 1.22+ ServeMux
支持 method 和 path 变量:
go
mux.HandleFunc("GET /api/devices/{id}", getDevice)4. 基础语法
完整示例见 workspace/phase-03/net-http-handler-router/main.go。
go
package main
import (
"encoding/json"
"log"
"net/http"
)
type Device struct {
ID string `json:"id"`
Name string `json:"name"`
Online bool `json:"online"`
X float64 `json:"x"`
Y float64 `json:"y"`
}
var devices = map[string]Device{
"AGV-001": {ID: "AGV-001", Name: "Alpha", Online: true, X: 10, Y: 5},
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func listDevices(w http.ResponseWriter, r *http.Request) {
list := make([]Device, 0, len(devices))
for _, d := range devices {
list = append(list, d)
}
writeJSON(w, http.StatusOK, list)
}
func getDevice(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
d, ok := devices[id]
if !ok {
writeJSON(w, http.StatusNotFound, map[string]string{"message": "device not found"})
return
}
writeJSON(w, http.StatusOK, d)
}
func createDevice(w http.ResponseWriter, r *http.Request) {
var d Device
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"message": "invalid JSON"})
return
}
if d.ID == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"message": "id is required"})
return
}
devices[d.ID] = d
writeJSON(w, http.StatusCreated, d)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/devices", listDevices)
mux.HandleFunc("GET /api/devices/{id}", getDevice)
mux.HandleFunc("POST /api/devices", createDevice)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}测试:
bash
curl http://localhost:8080/api/devices
curl http://localhost:8080/api/devices/AGV-001
curl -X POST http://localhost:8080/api/devices \
-H "Content-Type: application/json" \
-d '{"id":"AGV-002","name":"Beta","online":false,"x":0,"y":0}'5. 代码解析
go
mux.HandleFunc("GET /api/devices/{id}", getDevice)Go 1.22+ 路由模式:METHOD PATH,{id} 为路径参数,通过 r.PathValue("id") 获取。
go
json.NewDecoder(r.Body).Decode(&d)从请求体流式解析 JSON,比 ioutil.ReadAll + Unmarshal 更高效。记得处理 error。
go
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)先 Set Header,再 WriteHeader,最后 Write/Encode。WriteHeader 只能调用一次。
go
http.ListenAndServe(":8080", mux)阻塞监听。每个请求在新 goroutine 中执行 handler。
6. JavaScript / TypeScript 对比
| 概念 | Go net/http | Express.js |
|---|---|---|
| 启动 | ListenAndServe | app.listen(8080) |
| 路由 | ServeMux.HandleFunc | app.get('/path', fn) |
| 路径参数 | r.PathValue("id") | req.params.id |
| 查询参数 | r.URL.Query().Get("page") | req.query.page |
| 请求体 | json.Decoder(r.Body) | express.json() middleware |
| 响应 | ResponseWriter | res.json() |
关键差异:
- Go 无内置
res.json(),需手动 Set Content-Type 和 Encode - 标准库路由较基础,复杂路由可后续用 Gin 或 chi
- 每个请求已是 goroutine,无需
async关键字,但共享 map 需 Mutex
7. 常见错误
错误 1:WriteHeader 顺序错误
go
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/json") // ❌ 太晚了错误 2:忘记关闭 Body
go
// server 端 r.Body 需 Close(通常 defer r.Body.Close())
// client 端 resp.Body 必须 Close错误 3:并发写 map
go
var devices = map[string]Device{} // 多 goroutine 写 → panic
// ✅ 用 sync.RWMutex 或 sync.Map错误 4:未设置 Content-Type
前端 axios 可能无法自动 parse JSON。
错误 5:handler 里 panic
未 recover 的 panic 导致 500 且可能中断连接。业务 error 应返回 4xx/5xx JSON。
8. 实际应用
数字孪生 — 设备 CRUD 服务
GET /api/devices → listDevices
GET /api/devices/{id} → getDevice
POST /api/devices → createDevice
PUT /api/devices/{id} → updateDevice
DELETE /api/devices/{id} → deleteDevice内存 store 起步,Phase 04 换 PostgreSQL repository,handler 签名不变。
查询参数过滤
go
deviceType := r.URL.Query().Get("type")
online := r.URL.Query().Get("online") == "true"Vue 前端:/api/devices?type=agv&online=true
9. 深入理解
9.1 http.Server 配置
go
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}生产环境应设置超时,防止慢客户端耗尽 goroutine。
9.2 中间件模式
go
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}ListenAndServe(addr, withLogging(mux)) — 下一知识点展开。
9.3 与 Gin 的关系
Gin 底层仍是 HTTP,提供路由分组、JSON helper、中间件链。标准库学会后 Gin 上手很快。
10. 练习
详细题目见
exercises/phase-03-web/02-net-http-handler-router.md。
Level 1 — 基础
练习 1.1:启动服务器,GET /health 返回 {"status":"ok"}。
练习 1.2:GET /api/devices 返回硬编码 JSON 数组。
练习 1.3:读取查询参数 ?name=xxx 并 echo。
Level 2 — 应用
练习 2.1:实现 Device CRUD 四个 handler(内存 map)。
练习 2.2:路径参数 {id},404 处理。
练习 2.3:POST 解析 JSON body,校验 id 非空。
Level 3 — 综合
练习 3.1:加 RWMutex 保护并发 map 访问。
练习 3.2:统一 writeJSON 和 writeError helper。
Level 4 — 项目实践
练习 4.1:在 workspace/phase-03/net-http-handler-router/ 完成 Device REST API,可用 curl 测试全部端点。
11. 学习检查
Handler接口的方法签名是什么?WriteHeader为什么只能调用一次?- Go 1.22+ 如何获取路径参数
{id}? - 如何从请求体解析 JSON?
- 为什么多 handler 写同一 map 需要 Mutex?
ListenAndServe阻塞吗?每个请求在哪个并发单元执行?
12. 下一步
| 已完成 | 下一知识点 | 关系 |
|---|---|---|
| net/http, Handler, Router | Middleware, Validation, CORS | 日志、鉴权、跨域、参数校验 |
| Gin, JWT, API design | 框架加速与认证 |
继续完善 net/http 服务器的横切能力,再考虑 Gin。
学习导航
上一篇:HTTP and REST API · 对应练习 · 下一篇:Middleware、Validation、CORS