3. REST API 基础回顾:RESTful 设计原则、HTTP 方法、状态码与资源路径
在进入契约压测之前,必须重新校准对 REST API 核心要素的理解。REST(Representational State Transfer)是一种架构风格,而非协议;它基于 HTTP 协议,将服务端数据抽象为资源,并通过统一的接口进行操作。本节将系统回顾 RESTful 设计的四大支柱:设计原则、HTTP 方法、状态码语义以及资源路径规范。
3.1 RESTful 六大设计原则
REST 架构风格由 Roy Fielding 在其博士论文中提出,核心约束如下:
| 原则 | 说明 | 违反示例 |
|---|---|---|
| 客户端-服务器 | 分离用户界面与数据存储,提升可移植性与可扩展性 | 在服务端直接渲染 HTML 模板并管理 Session |
| 无状态 | 每个请求包含所有必要信息,服务端不保存客户端上下文 | 使用服务端 Session 存储用户登录状态 |
| 可缓存 | 响应隐式或显式标记为可缓存或不可缓存,减少网络交互 | 所有 GET 请求返回 Cache-Control: no-store |
| 统一接口 | 通过标准 HTTP 方法操作资源,使用超媒体作为应用状态引擎 | 使用 /getUser、/deleteUser 等 RPC 风格路径 |
| 分层系统 | 客户端无法直接感知是否与最终服务器或中间层通信 | 客户端硬编码负载均衡器 IP 并依赖其直接返回 |
| 按需代码(可选) | 服务器可临时扩展客户端功能(如返回 JavaScript 脚本) | 强制客户端下载并执行服务端推送的二进制插件 |
契约压测视角: 无状态原则是压测的基础。如果 API 依赖服务端 Session,压测工具必须处理 Cookie 或 Token 传递,这会增加脚本复杂度并影响测试结果的纯净度。优先设计无状态 API。
3.2 HTTP 方法与 CRUD 映射
RESTful API 通过 HTTP 方法表达对资源的操作意图。以下是标准映射关系:
| HTTP 方法 | CRUD 操作 | 幂等性 | 安全性 | 请求体 | 响应体 |
|---|---|---|---|---|---|
| GET | 读取(Read) | 是 | 是 | 无 | 资源表示 |
| POST | 创建(Create) | 否 | 否 | 资源属性 | 新资源标识 |
| PUT | 全量替换(Update) | 是 | 否 | 完整资源 | 更新后资源 |
| PATCH | 部分更新(Partial Update) | 否 | 否 | 差异字段 | 更新后资源 |
| DELETE | 删除(Delete) | 是 | 否 | 无 | 空或状态 |
幂等性关键: 在压测中,幂等接口(GET、PUT、DELETE)可以安全地重复发送请求而不改变服务端状态。非幂等接口(POST)需要额外处理重复创建问题,通常通过客户端生成唯一 ID(幂等键)来解决。
3.3 HTTP 状态码语义
状态码是服务端对请求处理结果的标准化反馈。契约压测中,断言的核心就是验证状态码是否符合预期。以下是 REST API 中最常用的状态码分类:
- 2xx 成功
200 OK:GET、PUT、PATCH 成功返回201 Created:POST 成功创建资源204 No Content:DELETE 成功,无返回体
- 3xx 重定向
301 Moved Permanently:资源永久迁移304 Not Modified:条件 GET,资源未变更
- 4xx 客户端错误
400 Bad Request:请求格式错误或参数无效401 Unauthorized:未提供认证信息403 Forbidden:已认证但无权限404 Not Found:资源不存在409 Conflict:资源当前状态冲突(如重复创建)422 Unprocessable Entity:请求体语义验证失败429 Too Many Requests:触发限流
- 5xx 服务端错误
500 Internal Server Error:通用服务端异常502 Bad Gateway:上游服务不可用503 Service Unavailable:服务过载或维护中504 Gateway Timeout:上游服务超时
压测陷阱: 不要将
429 或 503 视为测试失败。在压测中,这些状态码是系统限流或过载的正常信号,应作为性能指标记录,而非断言错误。契约压测应区分“功能错误”与“容量反馈”。
3.4 资源路径设计规范
资源路径(Endpoint)是 REST API 的对外接口。良好的路径设计能提升 API 的可读性和可维护性。以下是核心规范:
| 规范 | 正确示例 | 错误示例 |
|---|---|---|
| 使用名词复数 | /users |
/getUser、/userList |
| 层级关系用斜杠 | /users/{id}/orders |
/usersOrders?id={id} |
| 查询参数用于过滤/排序 | /users?status=active&sort=created_at |
/users/active、/users/sortByDate |
| 使用小写字母和连字符 | /order-items |
/orderItems、/Order_Items |
| 避免动词 | POST /users/{id}/activate |
POST /activateUser |
| 版本号放在前缀 | /v1/users |
/users/v1、/users?version=1 |
3.5 REST API 请求-响应流程(SVG)
下图展示了一个完整的 REST API 调用流程,从客户端发起请求到服务端返回响应,涵盖方法、路径、状态码与资源状态变化:
3.6 资源路径层级与集合关系(SVG)
REST API 的资源路径通常反映数据模型的层级关系。下图展示了一个典型电商系统的资源路径设计:
3.7 契约压测中的 REST 验证要点
在后续章节的压测脚本编写中,以下 REST 要素将作为契约断言的核心:
- 方法验证: 确保压测请求使用正确的 HTTP 方法(如创建用户必须用 POST,而非 GET)。
- 路径验证: 检查 URL 是否符合资源路径规范,避免因路径错误导致 404。
- 状态码验证: 断言响应状态码与预期一致(如 POST 返回 201,DELETE 返回 204)。
- 响应体验证: 验证 JSON 结构、字段类型、必填字段是否存在。
- 幂等性验证: 对 PUT/DELETE 重复发送请求,验证状态不变。
Go 代码示例: 使用
net/http 发起 REST 请求并验证状态码
package main
import (
"fmt"
"net/http"
)
func main() {
// 创建用户(POST)
resp, err := http.Post("https://api.example.com/v1/users", "application/json", nil)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 契约断言:状态码必须为 201
if resp.StatusCode != http.StatusCreated {
fmt.Printf("契约失败:期望 201,实际 %d\n", resp.StatusCode)
} else {
fmt.Println("契约通过:用户创建成功")
}
}