HTTP 服务与路由
HTTP 服务与路由 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
这课用标准库 net/http 把书签服务挂上端口:健康检查、列表、详情三个接口,处理方法、路径参数、请求头和状态码,不引入任何 Web 框架。
问题场景
前端想知道书签服务活没活着、有哪些书签、单个长什么样。标准库从 Go 1.22 起路由能力就够用了:方法加路径直接写进模式串,路径参数一行取出。框架迟早要接触,但先弄明白「一个 HTTP 请求在标准库里怎么走」,之后看框架只是看一层糖。
核心解释
路由表用 ServeMux,Go 1.22 的模式语法支持方法和通配符:
func newMux(store *MemoryStore) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
mux.HandleFunc("GET /bookmarks", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, store.List())
})
mux.HandleFunc("GET /bookmarks/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
b, err := store.Get(id)
if errors.Is(err, ErrNotFound) {
http.Error(w, "bookmark not found", http.StatusNotFound)
return
}
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
writeJSON(w, http.StatusOK, b)
})
return mux
}
func writeJSON(w http.ResponseWriter, code int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
json.NewEncoder(w).Encode(v)
}
func main() {
store := NewMemoryStore()
store.Add(Bookmark{Title: "Go 官网", URL: "https://go.dev", Tags: []string{"go"}})
log.Fatal(http.ListenAndServe(":8080", newMux(store)))
}
四件事值得盯紧:模式串 GET /bookmarks/{id} 同时声明了方法和路径,其他方法打进来自动得到 405 Method Not Allowed,不用手写;r.PathValue("id") 按大括号里的名字取路径段;响应头必须在 WriteHeader 之前 Set,写了状态码再改头会被静默丢弃;JSON 编码统一收进 writeJSON,Content-Type 只写一次。
把路由注册提成 newMux(store) 函数不是随手为之:G07 的中间件要包在这层外面,G13 的测试要直接调它,现在提成函数是给后面两课铺路。ListenAndServe 的第二个参数就是 Handler 接口——ServeMux 实现了它,中间件以后也在这层包。log.Fatal 兜住启动失败(比如端口被占),进程退出码非 0,脚本里能立刻发现。
路由匹配还有两条默认规则值得知道:精确模式优先于通配符,GET /bookmarks/new 和 GET /bookmarks/{id} 同时注册时,new 请求走精确那条,不会被当成 id=new;没有任何模式匹配的路径由 mux 统一回 404,不用自己写兜底。
示例拆解
go run .
curl -i http://127.0.0.1:8080/healthz
# HTTP/1.1 200 OK,body: ok
curl -i http://127.0.0.1:8080/bookmarks/1
# HTTP/1.1 200 OK,body: {"id":1,...}
curl -i http://127.0.0.1:8080/bookmarks/abc
# HTTP/1.1 400 Bad Request,invalid id
curl -i -X POST http://127.0.0.1:8080/bookmarks/1
# HTTP/1.1 405 Method Not Allowed
curl -i http://127.0.0.1:8080/bookmarks/999
# HTTP/1.1 404 Not Found
五种响应对应五类真实情况:健康、正常、参数错、方法错、不存在。逐个 curl 一遍,比看十遍代码记得牢——每个状态码在代码里都有对应的那一行。顺手再发一个 GET /bookmarks/new,观察它落在 404 而不是 id 解析错误:路由怎么分流,测过才有底。
动手任务
- 加
DELETE /bookmarks/{id}:成功 204,不存在 404(给 MemoryStore 补 Delete)。 - 给列表加过滤:
GET /bookmarks?tag=go,用 r.URL.Query().Get("tag"),为空则不过滤。 - 给 writeJSON 加
X-Content-Type-Options: nosniff头,用 curl -i 确认生效。
常见错误
- 先 WriteHeader 再 Set Content-Type:头被忽略,浏览器可能把 JSON 当纯文本显示。
- 忘记检查 ParseInt 的 err:解析失败拿到 0,把「无效 id」查成 0 号书签。
- 用字符串拼接路径再手动 if/else 分发:路由一多就失控,交给 ServeMux 模式串。
- healthz 里做重活(全表扫描):探活工具会高频调用,只做最轻的存活判断。
检查与复盘
完成下方检查题。自查清单:
- 能不查资料写出方法+通配符的路由模式并取路径参数
- 说得清 400/404/405/500 分别由谁触发
- JSON 响应统一走 writeJSON,状态码之前设置响应头
参考资料
- 标准库 net/http —— Server、Handler、ServeMux 总览
- 标准库 ServeMux —— Go 1.22 路由模式语法的权威说明
- Go 官方教程:编写 Web 应用 —— 标准库建站的完整走查
读完这一课了?
标记完成后,工作台会安排接下来的学习。