3. HTTP 基础与 RESTful API:HTTP 方法、状态码、REST 设计原则、Go net/http 包快速入门
3.1 HTTP 协议核心要素
HTTP(HyperText Transfer Protocol)是 API 通信的基石。一个完整的 HTTP 请求-响应周期由请求行、请求头、请求体以及状态行、响应头、响应体构成。在契约校验中,我们最关注的是方法、路径、状态码和媒体类型。
3.2 HTTP 方法与 RESTful 语义
RESTful API 将 HTTP 方法映射为对资源的操作。下表列出核心方法及其在契约校验中的典型用途:
| HTTP 方法 | CRUD 语义 | 幂等性 | 请求体 | 响应体 | 契约校验关注点 |
|---|---|---|---|---|---|
| GET | 读取资源 | 是 | 无 | 资源表示 | 查询参数、响应结构 |
| POST | 创建资源 | 否 | 创建参数 | 新资源 / 操作结果 | 请求体 schema、201 响应 |
| PUT | 全量替换 | 是 | 完整资源 | 更新后资源 | 请求体必填字段、200/204 |
| PATCH | 部分更新 | 否 | 增量字段 | 更新后资源 | 字段可选性、JSON Merge Patch |
| DELETE | 删除资源 | 是 | 无 | 空 / 确认消息 | 204 无内容、404 处理 |
契约校验提示:POST 是非幂等的,契约测试中应避免重复执行相同的 POST 请求导致数据重复。幂等方法(GET、PUT、DELETE)可以安全重放用于契约验证。
3.3 HTTP 状态码与契约约定
状态码是契约响应端的“第一行承诺”。在 API 契约中,每个端点必须明确声明可能返回的状态码及其对应的响应结构。以下是 RESTful API 中最常用的状态码分类:
- 2xx 成功
200 OK— GET、PUT、PATCH 成功返回资源201 Created— POST 成功创建资源,响应头应包含Location204 No Content— DELETE 成功,或 PUT/PATCH 不返回 body
- 3xx 重定向
301 Moved Permanently/308 Permanent Redirect— 资源 URL 变更304 Not Modified— 条件 GET 未变更
- 4xx 客户端错误
400 Bad Request— 请求体校验失败、参数格式错误401 Unauthorized— 缺少或无效认证403 Forbidden— 认证通过但无权限404 Not Found— 资源不存在409 Conflict— 资源冲突(如唯一约束)422 Unprocessable Entity— 语义错误(如校验失败)
- 5xx 服务端错误
500 Internal Server Error— 未预期异常503 Service Unavailable— 服务过载或维护
契约设计警告:不要在契约中声明 5xx 状态码作为正常响应路径。5xx 表示服务端故障,契约应保证在输入合法时只返回 2xx 或 4xx。将 5xx 纳入契约会模糊责任边界。
3.4 RESTful 设计原则
REST(Representational State Transfer)是一组架构约束,而非具体协议。在 API 契约设计中,以下原则直接影响接口结构:
- 资源导向 — URL 表示名词资源,而非动词操作。
/users而非/getUsers。 - 无状态 — 每个请求包含所有必要信息,服务端不保存客户端会话状态。
- 统一接口 — 使用标准 HTTP 方法操作资源,使用标准媒体类型(如
application/json)表示资源。 - 超媒体驱动(HATEOAS) — 响应中包含链接,引导客户端发现下一步操作(高级实践,契约中可选)。
- 分层系统 — 客户端无需知道直接与哪一层通信(代理、网关、负载均衡)。
以下 SVG 展示了 RESTful API 请求从客户端到服务端的完整流转路径,突出契约校验的介入点:
3.5 Go net/http 包快速入门
Go 标准库 net/http 提供了构建 HTTP 客户端和服务器的核心能力。在契约校验工具开发中,我们主要用它来发送请求和启动测试服务器。
3.5.1 启动一个简单的 HTTP 服务器
package main
import (
"fmt"
"net/http"
)
func main() {
// 注册路由处理函数
http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `{"users":[]}`)
case http.MethodPost:
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, `{"id":"1","name":"alice"}`)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
})
fmt.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil)
}
关键点:
http.HandleFunc 注册的处理器默认接收所有 HTTP 方法。在契约校验中,必须显式检查 r.Method 并返回正确的状态码,否则会导致契约测试误判。
3.5.2 发送 HTTP 请求(客户端)
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
// GET 请求
resp, err := http.Get("http://localhost:8080/api/users")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Body: %s\n", string(body))
// POST 请求
payload := `{"name":"bob"}`
resp, err = http.Post(
"http://localhost:8080/api/users",
"application/json",
strings.NewReader(payload),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ = io.ReadAll(resp.Body)
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Body: %s\n", string(body))
}
3.5.3 使用 http.Client 进行精细控制
契约校验需要精确控制超时、重定向和请求头。推荐使用 http.Client 结构体:
client := &http.Client{
Timeout: 5 * time.Second,
// 禁止自动重定向,契约测试需要捕获原始响应
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req, _ := http.NewRequest(http.MethodDelete, "http://localhost:8080/api/users/1", nil)
req.Header.Set("Authorization", "Bearer test-token")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 契约校验:检查状态码是否为 204
if resp.StatusCode != http.StatusNoContent {
fmt.Printf("契约失败:期望 204,得到 %d\n", resp.StatusCode)
}
契约测试陷阱:默认的
http.Client 会跟随 3xx 重定向,导致最终状态码变为 200 而非 301/302。在契约校验中,务必设置 CheckRedirect 返回 http.ErrUseLastResponse 以获取原始重定向响应。
3.6 RESTful 路由设计模式
在 Go 中,net/http 默认的 ServeMux 不支持路径参数(如 /users/{id})。契约校验工具通常需要解析路径中的变量。以下是两种常见处理方式:
| 方案 | 实现方式 | 适用场景 | 契约校验兼容性 |
|---|---|---|---|
| 标准库 ServeMux | 手动解析 URL 路径,如 strings.Split(r.URL.Path, "/") |
小型工具、快速原型 | 需自行实现路径匹配逻辑 |
| 第三方路由(gorilla/mux) | mux.Vars(r)["id"] |
生产级 API 服务 | 路径参数提取方便,契约模板可直接映射 |
| Go 1.22+ 增强 ServeMux | http.HandleFunc("GET /api/users/{id}", handler) |
新项目、标准库优先 | 原生支持路径参数,契约定义更简洁 |
课程实践建议:本课程后续章节的契约校验工具将基于 Go 1.22+ 的增强 ServeMux 构建,因为它原生支持路径参数和方法路由,减少第三方依赖,且与 OpenAPI 路径模板的映射更直接。
以下 SVG 展示了 Go net/http 包在契约校验工具中的核心组件关系:
3.7 本章小结
- HTTP 方法(GET/POST/PUT/PATCH/DELETE)对应 CRUD 操作,幂等性是契约测试中决定重放策略的关键属性。
- 状态码是契约的“第一承诺”,每个端点必须声明允许的状态码集合,避免将 5xx 纳入正常契约路径。
- RESTful 设计原则(资源导向、无状态、统一接口)直接影响 API 的 URL 结构和交互方式。
- Go
net/http包提供了构建契约校验工具所需的核心能力:http.Client用于发送请求,http.Server用于启动模拟服务,http.HandleFunc用于路由注册。 - Go 1.22+ 的增强 ServeMux 原生支持路径参数和方法路由,是构建契约校验测试服务器的推荐选择。