3. HTTP 基础:net/http 包快速入门
在构建 REST API 之前,必须掌握 Go 标准库 net/http 的核心用法。本章将带你从零搭建一个可运行的 HTTP 服务器,并理解请求与响应的处理流程。
3.1 net/http 包的核心组件
net/http 包提供了构建 HTTP 客户端和服务端所需的所有功能。其核心组件包括:
| 组件 | 类型 | 作用 |
|---|---|---|
| Server | 结构体 | 配置监听地址、端口、超时等参数 |
| ServeMux | 多路复用器 | 将请求 URL 路由到对应的处理函数 |
| Handler | 接口 | 定义 ServeHTTP(ResponseWriter, *Request) 方法 |
| ResponseWriter | 接口 | 用于构造 HTTP 响应 |
| Request | 结构体 | 封装客户端请求的所有信息 |
关键理解: Go 的 HTTP 处理模型是 Handler 接口驱动的。任何实现了
ServeHTTP 方法的类型都可以作为处理器,这为中间件和路由组合提供了极大的灵活性。
3.2 创建第一个 HTTP 服务器
以下代码演示了如何使用 net/http 包创建一个监听 8080 端口的服务器,并处理根路径的请求:
package main
import (
"fmt"
"net/http"
)
// 定义一个处理器
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, 欢迎来到 Go REST API 课程!")
}
func main() {
// 注册路由
http.HandleFunc("/", helloHandler)
// 启动服务器
fmt.Println("服务器启动,监听 :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}
运行步骤:
- 将上述代码保存为
main.go - 在终端执行
go run main.go - 打开浏览器访问
http://localhost:8080
3.3 请求与响应处理流程
当客户端发送请求时,Go 的 HTTP 服务器按照以下流程处理:
- 监听连接:
ListenAndServe在指定端口等待 TCP 连接。 - 解析请求: 将原始字节流解析为
http.Request结构体。 - 路由匹配:
ServeMux根据请求路径找到对应的Handler。 - 执行处理: 调用
Handler.ServeHTTP方法。 - 写入响应: 通过
ResponseWriter返回状态码、头部和正文。
3.4 处理不同的 HTTP 方法
REST API 需要根据 HTTP 方法(GET、POST、PUT、DELETE 等)执行不同的逻辑。通过 r.Method 字段可以判断请求方法:
func userHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
fmt.Fprintf(w, "获取用户列表")
case http.MethodPost:
fmt.Fprintf(w, "创建新用户")
case http.MethodPut:
fmt.Fprintf(w, "更新用户信息")
case http.MethodDelete:
fmt.Fprintf(w, "删除用户")
default:
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
}
}
func main() {
http.HandleFunc("/users", userHandler)
http.ListenAndServe(":8080", nil)
}
常见错误: 忘记处理
OPTIONS 和 HEAD 方法会导致跨域请求(CORS)失败。在生产环境中,建议使用专门的中间件统一处理。
3.5 读取请求参数
客户端可以通过三种方式传递参数:URL 查询参数、表单数据和 JSON 请求体。以下是读取查询参数的示例:
func searchHandler(w http.ResponseWriter, r *http.Request) {
// 获取查询参数
query := r.URL.Query()
keyword := query.Get("q")
page := query.Get("page")
if keyword == "" {
http.Error(w, "缺少查询参数 q", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "搜索关键词: %s, 页码: %s", keyword, page)
}
func main() {
http.HandleFunc("/search", searchHandler)
http.ListenAndServe(":8080", nil)
}
3.6 设置响应头与状态码
良好的 API 设计需要明确的状态码和响应头。以下示例展示了如何设置 JSON 响应:
func jsonHandler(w http.ResponseWriter, r *http.Request) {
// 设置响应头
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-API-Version", "1.0")
// 设置状态码
w.WriteHeader(http.StatusOK)
// 写入 JSON 数据
fmt.Fprintf(w, `{"message": "成功", "code": 200}`)
}
| 常用状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 请求成功 | GET 获取资源、PUT 更新成功 |
| 201 Created | 资源创建成功 | POST 创建新资源 |
| 400 Bad Request | 请求参数错误 | 参数校验失败 |
| 404 Not Found | 资源不存在 | 请求路径错误 |
| 500 Internal Server Error | 服务器内部错误 | 未捕获的异常 |
3.7 使用 Server 结构体进行精细控制
直接使用 http.ListenAndServe 会使用默认配置。对于生产环境,建议使用 http.Server 结构体进行精细控制:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", helloHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
}
fmt.Println("服务器启动,监听 :8080")
if err := server.ListenAndServe(); err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}
最佳实践: 始终设置
ReadTimeout 和 WriteTimeout,防止慢连接或恶意请求耗尽服务器资源。IdleTimeout 用于 HTTP/2 的持久连接管理。
3.8 本章小结
- 核心组件:
Server、ServeMux、Handler接口构成了 Go HTTP 处理的基石。 - 请求处理: 通过
r.Method区分 HTTP 方法,通过r.URL.Query()读取参数。 - 响应构建: 使用
w.Header().Set()设置头部,w.WriteHeader()设置状态码。 - 生产配置: 始终使用
http.Server结构体并设置超时参数。