讲解
net/http 包让写一个 HTTP 服务器变成几行代码的事:定义处理函数(签名固定为 func(w http.ResponseWriter, r *http.Request)),注册到路由,然后监听端口。ResponseWriter 是写响应的出口,r 携带请求的一切信息(方法、路径、头部、查询参数、请求体)。
Go 1.22 给路由带来了期待已久的增强:注册时可以写 "GET /hello" 这样的「方法 + 路径」模式,还能用 "GET /users/{id}" 捕获路径参数、用 r.PathValue("id") 取出来。http.NewServeMux 创建自己的路由器,比全局默认路由更干净——下面的示例都用它。
写一个可自测的完整服务是本教程的收尾方式:用 net.Listen 监听 127.0.0.1:0 让系统分配空闲端口,go 起服务,再用 http.Get 请求自己、打印响应后退出。这套「起服务—请求—验证—关闭」的模式,正是标准库测试 HTTP 处理函数的日常形态。返回 JSON 响应就是上一章的 json.NewEncoder(w).Encode(数据),记得设置 Content-Type: application/json。
示例
一个完整的服务:监听随机端口,自己请求自己,打印响应后退出:
package main
import (
"fmt"
"io"
"net"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你好,Go HTTP!")
})
listener, err := net.Listen("tcp", "127.0.0.1:0") // 0 = 系统分配空闲端口
if err != nil {
fmt.Println("监听失败:", err)
return
}
server := &http.Server{Handler: mux}
go server.Serve(listener)
defer server.Close()
url := "http://" + listener.Addr().String() + "/hello"
fmt.Println("请求", url)
resp, err := http.Get(url)
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("状态码:", resp.StatusCode, "响应:", string(body))
}
JSON API 与带方法的路由模式(Go 1.22+):
package main
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/ping", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"msg": "pong"})
})
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
fmt.Println("监听失败:", err)
return
}
server := &http.Server{Handler: mux}
go server.Serve(listener)
defer server.Close()
resp, err := http.Get("http://" + listener.Addr().String() + "/api/ping")
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Print("JSON 响应: ", string(body))
}
常见坑
- 响应体忘记 Close:客户端侧 resp.Body 不关闭会泄漏连接,服务端侧 r.Body 由框架处理。记法:谁拿到的 Body 是资源,谁负责 Close,照例 defer。
- ListenAndServe(":8080") 硬编码端口:测试和示例里写死端口容易撞车、并行测试互相干扰。测试用 127.0.0.1:0 让系统分配,生产再配置真实端口。
- http.ListenAndServe 的返回值没处理:它正常关闭也会返回 error(http.ErrServerClosed),启动代码至少要 log.Fatal 或判断是否为 ErrServerClosed,静默丢弃会让你错过端口被占用的真实报错。
- 在全局 DefaultServeMux 上注册:第三方库也可能往全局路由注册,产生意外路由甚至安全隐患。自己的服务用 http.NewServeMux,边界清晰。
- 处理函数里忽略并发:每个请求跑在独立 goroutine 里,处理函数访问共享状态(计数器、缓存 map)必须同步——回到并发章节的纪律。
小结
net/http 几行起服务,处理函数读写 ResponseWriter 与 Request,Go 1.22 路由支持方法与路径参数,测试用随机端口自闭环。下一节学习保证代码质量的机制:测试。