2. HTTP 状态码回顾:2xx/3xx/4xx/5xx 系列语义与最佳实践

HTTP 状态码是服务端向客户端(浏览器、移动端、API 网关)传递请求处理结果的标准化信号。在微服务架构与 OpenAPI 契约测试中,状态码不仅是协议层面的约定,更是错误码断言与合规审计的基石。错误的语义映射会导致客户端异常处理失效、监控告警误报,甚至安全漏洞。

2.1 状态码分类总览

RFC 7231 将 HTTP 状态码划分为五大类,每一类代表不同的响应语义层级。后端 API 设计必须严格遵循分类边界,避免将业务异常映射到错误的系列。

系列 语义 典型场景 API 设计原则
2xx 成功 — 请求已接收、理解并处理 资源创建、数据查询、更新成功 仅用于正常业务完成,不用于业务拒绝
3xx 重定向 — 需要客户端进一步操作 资源迁移、临时 URI、负载均衡 REST API 应谨慎使用,避免破坏幂等性
4xx 客户端错误 — 请求包含错误或无法处理 参数校验失败、认证失败、资源不存在 必须附带错误体,明确错误原因
5xx 服务端错误 — 服务端处理失败 数据库宕机、内部异常、超时 不应暴露内部细节,需统一错误格式
合规提示: 在金融、医疗等强监管行业,错误码与状态码的映射关系必须通过安全合规审计。将业务拒绝(如余额不足)映射为 200 + 业务错误码,而非 4xx,可能导致客户端忽略错误,造成资金风险。

2.2 2xx 成功系列详解

2xx 系列表示请求已被成功接收、理解并接受。后端 API 中最常用的三个状态码如下:

  • 200 OK — 标准成功响应。用于 GET 查询、PUT 更新、部分 POST 操作。响应体应包含完整的业务数据。
  • 201 Created — 资源创建成功。用于 POST 创建资源。响应头必须包含 Location 字段指向新资源 URI。
  • 204 No Content — 操作成功但无返回体。用于 DELETE 删除、部分幂等更新。响应体必须为空,禁止返回 null 或空 JSON。
// 201 Created 示例响应
HTTP/1.1 201 Created
Location: /api/v2/orders/ord-20250301-001
Content-Type: application/json

{
  "orderId": "ord-20250301-001",
  "status": "pending",
  "createdAt": "2025-03-01T10:30:00Z"
}
避坑: 不要将业务逻辑错误(如重复订单、库存不足)返回 200 + 业务错误码。这会导致 API 网关无法根据状态码进行熔断、限流,且客户端难以统一处理异常。正确的做法是返回 409 Conflict 或 422 Unprocessable Entity。

2.3 3xx 重定向系列与 REST 约束

3xx 系列在 REST API 中应谨慎使用。浏览器自动跟随重定向的行为在 API 客户端中可能引发安全问题或状态丢失。

状态码 语义 API 使用建议
301 Moved Permanently 资源永久迁移 仅用于 API 版本废弃迁移,需配合 Location 头
302 Found 临时重定向 避免在 REST API 中使用,破坏幂等性
307 Temporary Redirect 临时重定向,保持请求方法 可用于负载均衡或临时维护,客户端需手动处理
308 Permanent Redirect 永久重定向,保持请求方法 替代 301,确保 POST/PUT 方法不被错误转为 GET
安全合规要点: 重定向 URI 必须进行白名单校验,防止开放重定向漏洞(Open Redirect)。攻击者可利用未校验的 Location 头将用户引导至钓鱼站点。OpenAPI 契约中应明确约束 Location 头的格式。

2.4 4xx 客户端错误系列 — 错误码断言的核心战场

4xx 系列是后端安全合规错误码断言最密集的区域。每一个 4xx 响应都必须附带结构化的错误体,帮助客户端精确识别问题。

  • 400 Bad Request — 通用请求错误。参数格式错误、缺少必填字段、JSON 解析失败。必须返回错误字段定位。
  • 401 Unauthorized — 未认证。缺少 Token、Token 过期、认证头格式错误。响应头应包含 WWW-Authenticate。
  • 403 Forbidden — 已认证但无权限。角色不足、资源隔离、IP 白名单拒绝。不应暴露具体权限细节。
  • 404 Not Found — 资源不存在。URI 错误、资源已删除。注意:不要返回 404 来隐藏敏感资源是否存在(安全考虑)。
  • 409 Conflict — 资源冲突。重复创建、版本冲突、唯一约束违反。应返回冲突的字段标识。
  • 422 Unprocessable Entity — 语义错误。业务规则校验失败、状态机不允许的操作。比 400 更精确。
  • 429 Too Many Requests — 限流。请求频率超过阈值。响应头必须包含 Retry-After。
// 422 Unprocessable Entity 标准错误体
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "type": "https://api.example.com/errors/order-status-conflict",
  "title": "Order status transition not allowed",
  "status": 422,
  "detail": "Cannot cancel an order in 'shipped' status",
  "instance": "/api/v2/orders/ord-20250301-001/cancel",
  "errors": [
    {
      "field": "status",
      "code": "INVALID_TRANSITION",
      "message": "Current status 'shipped' does not allow 'cancelled'"
    }
  ]
}
契约测试要点: 在 OpenAPI 契约中,每个 4xx 响应必须定义明确的 schema,包括错误码枚举、字段路径、时间戳。自动化测试应断言:状态码、错误体结构、错误码枚举值、必填字段是否存在。

2.5 5xx 服务端错误系列 — 安全合规红线

5xx 系列表示服务端无法完成合法请求。在安全合规视角下,5xx 响应是信息泄露的高危通道。

状态码 语义 安全合规要求
500 Internal Server Error 通用服务端错误 禁止返回堆栈跟踪、SQL 语句、内部 IP
502 Bad Gateway 上游服务返回无效响应 网关层应脱敏上游错误,统一错误格式
503 Service Unavailable 服务暂时不可用(过载/维护) 必须包含 Retry-After 头,避免客户端死循环重试
504 Gateway Timeout 上游服务超时 不应暴露上游服务名称或超时阈值
合规红线: 生产环境必须配置全局异常过滤器,将所有未捕获异常转换为统一的 500 错误体,并记录完整堆栈到日志系统(非响应体)。PCI-DSS、GDPR 等标准明确禁止在响应中泄露内部错误细节。

2.6 状态码选择流程与最佳实践

以下 SVG 流程图展示了后端 API 设计时选择正确状态码的决策路径,帮助开发者在契约设计阶段避免语义混淆。

HTTP 状态码选择决策流程 收到客户端请求 请求格式/认证/权限是否合法? 否 4xx 客户端错误 是 业务逻辑是否成功完成? 否 409 / 422 业务冲突 是 资源是否已迁移/需要重定向? 是 3xx 重定向 否 2xx 成功响应 说明:5xx 服务端错误由全局异常过滤器统一处理,不进入业务决策流程

2.7 状态码与错误码断言映射表

在 OpenAPI 契约测试中,每个状态码必须与特定的错误码枚举值形成固定映射。以下表格展示了后端安全合规场景下的标准映射关系:

HTTP 状态码 错误码枚举 触发条件 断言检查项
400 INVALID_PARAMETER 参数格式错误、JSON 解析失败 错误体包含 field 和 reason
401 UNAUTHORIZED Token 缺失或过期 响应头包含 WWW-Authenticate
403 FORBIDDEN 权限不足、IP 受限 错误体不暴露具体权限细节
404 RESOURCE_NOT_FOUND 资源 URI 不存在 统一错误体,不区分资源类型
409 RESOURCE_CONFLICT 唯一约束违反、版本冲突 错误体包含冲突字段标识
422 UNPROCESSABLE_ENTITY 业务规则校验失败 错误体包含 code 和 message
429 RATE_LIMIT_EXCEEDED 请求频率超限 响应头包含 Retry-After
500 INTERNAL_ERROR 未捕获的服务端异常 错误体不包含堆栈、SQL、内部路径
503 SERVICE_UNAVAILABLE 服务过载或维护 响应头包含 Retry-After
契约测试断言模板: 在自动化测试中,每个 API 端点应至少覆盖以下断言:
1. 正常流程返回 200/201,响应体结构符合 schema。
2. 参数错误返回 400,错误体包含 field 和 code。
3. 认证失败返回 401,响应头包含 WWW-Authenticate。
4. 权限不足返回 403,错误体不暴露内部信息。
5. 资源不存在返回 404,错误体统一格式。
6. 服务端异常返回 500,错误体不包含堆栈信息。

2.8 状态码在 OpenAPI 契约中的声明规范

OpenAPI 3.0 要求每个响应必须显式声明状态码及其 schema。以下是一个符合安全合规要求的契约片段:

paths:
  /api/v2/orders/{orderId}/cancel:
    post:
      summary: 取消订单
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            pattern: '^ord-\d{14}-\d{3}$'
      responses:
        '200':
          description: 取消成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderCancelResponse'
        '401':
          description: 未认证
          headers:
            WWW-Authenticate:
              schema:
                type: string
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: 无权限
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: 订单不存在
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: 订单状态不允许取消
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '500':
          description: 服务端内部错误
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
合规审计视角: 安全合规专家在审查 API 契约时,会重点检查:每个 4xx 响应是否都有明确的错误 schema?5xx 响应是否统一且不暴露内部细节?401 响应是否包含 WWW-Authenticate 头?这些细节是合规审计的硬性通过条件。