4. Go 标准库 net/http 入门:HTTP 服务器基础、处理请求与响应、路由分发基础
Go 语言的标准库 net/http 为构建 HTTP 服务器提供了强大且简洁的原生支持。本章将带你从零开始,掌握使用标准库创建服务器、处理请求与响应,以及实现基础路由分发的核心技能。这是后续构建 REST API 的基石。
4.1 HTTP 服务器基础:启动你的第一个服务
使用 net/http 启动一个 HTTP 服务器非常简单,核心函数是 http.ListenAndServe。它负责监听一个 TCP 端口,并为每个传入的 HTTP 请求创建一个新的 goroutine 进行处理。
核心函数签名:
func ListenAndServe(addr string, handler Handler) error
- addr:监听地址,例如
":8080"(监听所有网络接口的 8080 端口)。 - handler:处理器,负责处理所有传入的请求。通常传入
nil以使用默认的多路复用器http.DefaultServeMux。
示例:一个最简单的 HTTP 服务器
package main
import (
"fmt"
"net/http"
)
func main() {
// 使用默认的 ServeMux 作为处理器
// 当访问根路径 "/" 时,调用 helloHandler 函数
http.HandleFunc("/", helloHandler)
// 启动服务器,监听 8080 端口
fmt.Println("服务器启动,监听端口 :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("服务器启动失败:", err)
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, 你访问了: %s!", r.URL.Path)
}
💡 提示:
http.ListenAndServe 是一个阻塞调用,它会一直运行直到服务器因为错误而关闭。在生产环境中,你可能需要使用 http.Server 结构体进行更精细的控制(如超时设置)。
4.2 处理请求与响应:理解 Handler 与 ResponseWriter
在 Go 的 HTTP 模型中,每个请求都由一个实现了 http.Handler 接口的对象来处理。这个接口只有一个方法:
type Handler interface {
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
两个核心参数构成了请求-响应的全部交互:
| 参数 | 类型 | 作用与关键方法 |
|---|---|---|
w |
http.ResponseWriter |
用于构建和发送 HTTP 响应。关键方法:w.Write([]byte):写入响应体。w.WriteHeader(statusCode):设置状态码。w.Header().Set(key, value):设置响应头。
|
r |
*http.Request |
代表客户端发来的 HTTP 请求。关键字段:r.Method:请求方法 (GET, POST 等)。r.URL:请求的 URL 对象。r.Body:请求体 (io.ReadCloser)。r.Header:请求头。
|
构建一个完整的响应示例:
func userHandler(w http.ResponseWriter, r *http.Request) {
// 1. 设置响应头
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom-Header", "my-value")
// 2. 设置状态码 (必须在写入响应体之前调用)
w.WriteHeader(http.StatusOK) // 200
// 3. 写入响应体
response := `{"message": "获取用户信息成功", "user": "Alice"}`
w.Write([]byte(response))
}
⚠️ 重要规则:
w.WriteHeader() 必须在 w.Write() 之前调用。如果在 Write() 之后调用,Go 会自动写入 200 OK 状态码,此时再调用 WriteHeader() 将无效。同样,设置响应头 w.Header().Set() 也应在 WriteHeader() 之前完成。
4.3 路由分发基础:ServeMux 的工作原理
http.ServeMux 是 Go 标准库提供的 HTTP 请求多路复用器(路由器)。它的核心职责是:根据请求的 URL 路径,将请求分发到已注册的处理器函数。
路由注册与匹配规则:
- 精确匹配: 注册路径
/help只匹配/help。 - 子树匹配: 注册路径
/api/会匹配所有以/api/开头的路径(如/api/users,/api/orders/123)。 - 根路径兜底: 注册路径
/会匹配所有未被其他模式匹配的请求,通常作为 404 或默认页面处理器。 - 最长匹配优先: 当多个模式匹配时,选择最长的那个。例如,同时注册了
/和/api/,访问/api/users会由/api/的处理器处理。
示例:构建一个简单的 RESTful 风格路由
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
// 注册不同的路由模式
http.HandleFunc("/", rootHandler) // 兜底
http.HandleFunc("/api/", apiRouter) // API 子树
http.HandleFunc("/health", healthHandler) // 精确匹配
fmt.Println("REST API 服务器启动 :8080")
http.ListenAndServe(":8080", nil)
}
func rootHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "404 - 页面未找到: %s", r.URL.Path)
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "UP"})
}
func apiRouter(w http.ResponseWriter, r *http.Request) {
// 在 /api/ 处理器内部,根据 Method 和具体路径进行二次分发
path := r.URL.Path
switch {
case path == "/api/users" && r.Method == "GET":
fmt.Fprintf(w, "获取所有用户")
case path == "/api/users" && r.Method == "POST":
fmt.Fprintf(w, "创建新用户")
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "API 端点未实现: %s %s", r.Method, path)
}
}
4.4 深入理解 Handler 与 HandlerFunc
除了直接实现 Handler 接口,Go 还提供了 http.HandlerFunc 适配器类型,它允许将普通函数(具有 func(w, r) 签名)作为 Handler 使用。这正是 http.HandleFunc 内部的工作原理。
| 方式 | 代码示例 | 适用场景 |
|---|---|---|
| 实现 Handler 接口 | |
需要维护状态或依赖注入的复杂处理器。 |
| 使用 HandlerFunc 适配器 | |
将普通函数显式转换为 Handler。 |
| 使用 HandleFunc 便捷方法 | |
最常用,代码最简洁。 |
4.5 实战:构建一个简单的用户 API
综合以上知识,我们构建一个仅使用标准库的、具备基本路由能力的用户管理 API。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
)
// 模拟数据存储
var users = map[string]string{
"1": "Alice",
"2": "Bob",
}
func main() {
// 注册路由
http.HandleFunc("/users", usersHandler)
http.HandleFunc("/users/", userByIDHandler)
log.Println("用户 API 服务器启动 :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 处理 /users 路径
func usersHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
json.NewEncoder(w).Encode(users)
case http.MethodPost:
var newUser map[string]string
if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
http.Error(w, `{"error":"无效的请求体"}`, http.StatusBadRequest)
return
}
// 简单处理:将新用户添加到 map
for id, name := range newUser {
users[id] = name
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
default:
http.Error(w, `{"error":"方法不允许"}`, http.StatusMethodNotAllowed)
}
}
// 处理 /users/{id} 路径
func userByIDHandler(w http.ResponseWriter, r *http.Request) {
// 从路径中提取 ID: /users/123 -> "123"
id := strings.TrimPrefix(r.URL.Path, "/users/")
if id == "" {
http.Error(w, `{"error":"用户 ID 不能为空"}`, http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
name, ok := users[id]
if !ok {
http.Error(w, `{"error":"用户未找到"}`, http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(map[string]string{"id": id, "name": name})
default:
http.Error(w, `{"error":"方法不允许"}`, http.StatusMethodNotAllowed)
}
}
💡 关键点总结:
- 路径解析: 使用
strings.TrimPrefix从 URL 路径中提取动态参数(如用户 ID)。这是标准库路由的常见模式。 - 方法分发: 在处理器内部通过
r.Method进行 GET/POST/PUT/DELETE 的分发,实现 RESTful 风格。 - JSON 处理: 使用
encoding/json包轻松实现请求体的解码和响应体的编码。 - 错误处理: 使用
http.Error快速返回标准 HTTP 错误响应。
4.6 本章小结
通过本章的学习,你已经掌握了 Go 标准库 net/http 的核心用法:
- 使用
http.ListenAndServe启动一个 HTTP 服务器。 - 理解
http.ResponseWriter和*http.Request的职责,并能够构建和发送响应。 - 掌握
http.ServeMux的路由匹配规则(精确匹配、子树匹配、最长匹配优先)。 - 学会使用
http.HandleFunc和http.Handler接口注册处理器。 - 能够结合路径解析和方法分发,构建一个简单的 RESTful API。