3. HTTP 协议基础:HTTP 请求与响应、RESTful API 设计原则、常见状态码

在 Go 语言中实现契约校验,无论是作为服务端验证入参,还是作为客户端校验响应,都离不开对 HTTP 协议的深刻理解。本章将系统梳理 HTTP 请求与响应的结构、RESTful API 的设计原则以及常见状态码的语义,为后续的校验逻辑落地打下坚实基础。

3.1 HTTP 请求与响应结构

HTTP 协议采用经典的客户端-服务器模型。一次完整的交互包含一个请求(Request)和一个响应(Response)。

3.1.1 请求结构

一个标准的 HTTP 请求由以下四部分组成:

  • 请求行(Request Line):包含方法(GET/POST/PUT/DELETE 等)、请求 URI 和协议版本(如 HTTP/1.1)。
  • 请求头(Headers):键值对形式,携带元数据,如 Content-Type、Authorization、Accept 等。
  • 空行(Blank Line):分隔头部与消息体。
  • 消息体(Body):可选,通常用于 POST、PUT 等方法传输数据。
Go 语言中的请求解析: 使用 net/http 标准库时,http.Request 结构体直接映射了上述结构。契约校验通常关注 r.Method、r.URL、r.Header 和 r.Body。

3.1.2 响应结构

响应结构与请求类似,但请求行替换为状态行:

  • 状态行(Status Line):包含协议版本、状态码(如 200)和原因短语(如 OK)。
  • 响应头(Headers):如 Content-Type、Content-Length、Set-Cookie 等。
  • 空行:分隔头部与消息体。
  • 消息体(Body):服务器返回的数据,通常是 JSON、XML 或 HTML。
校验切入点: 在 Go 服务端,响应状态码和响应头是契约校验的第一道防线。例如,校验 Content-Type: application/json 是否与 API 文档一致。
HTTP 请求与响应交互流程 客户端 (Client) 服务端 (Server) ① 发送 HTTP 请求 请求结构 请求行: POST /api/users HTTP/1.1 请求头: Content-Type: application/json 空行: \r\n 消息体: {"name": "Alice"} 响应结构 状态行: HTTP/1.1 200 OK 响应头: Content-Type: application/json 空行: \r\n 消息体: {"id": 1, "name": "Alice"} ② 返回 HTTP 响应

3.2 RESTful API 设计原则

REST(Representational State Transfer)是目前最主流的 API 设计风格。契约校验的规则定义,通常直接来源于 RESTful 设计规范。

3.2.1 核心原则

原则 说明 对契约校验的影响
资源导向 每个 URL 代表一个资源(如 /users、/orders/123) 校验 URL 路径参数是否符合资源标识符格式
使用标准 HTTP 方法 GET 查询、POST 创建、PUT 全量更新、PATCH 部分更新、DELETE 删除 校验请求方法是否与预期操作匹配
无状态(Stateless) 每个请求包含所有必要信息,服务端不保存客户端上下文 校验请求头中是否携带必要的认证令牌(Token)
统一接口 使用一致的资源命名、响应格式和错误结构 校验响应体结构是否符合统一的 JSON Schema
超媒体驱动(HATEOAS) 响应中包含可操作的链接,引导客户端下一步操作 校验返回的链接 URL 是否合法且可达

3.2.2 资源命名规范

  • 使用名词复数形式:/users、/products,避免使用动词(如 /getUser)。
  • 层级关系用斜杠表示:/users/123/orders 表示用户 123 的订单列表。
  • 查询参数用于过滤和排序:/users?status=active&page=1,不用于资源标识。
  • 版本号放在 URL 前缀或请求头:/v1/users 或 Accept: application/vnd.api.v1+json。
常见反模式: 使用 GET /deleteUser?id=1 来删除资源。正确的做法是 DELETE /users/1。契约校验应能识别并拒绝此类不符合 RESTful 规范的请求。
RESTful API 资源与方法映射 资源: /users 资源: /users/{id} 资源: /users/{id}/orders GET DELETE GET 查询用户列表 删除指定用户 查询用户订单列表

3.3 常见 HTTP 状态码

状态码是服务端对请求处理结果的直接反馈。在契约校验中,状态码是判断 API 是否按约定工作的首要指标。

3.3.1 状态码分类

分类 范围 含义 校验场景
1xx 信息性 100 - 199 请求已接收,继续处理 较少直接校验,通常由底层库处理
2xx 成功 200 - 299 请求成功接收并处理 校验响应体是否包含预期数据
3xx 重定向 300 - 399 需要客户端进一步操作 校验 Location 头是否存在且合法
4xx 客户端错误 400 - 499 请求包含错误或无法处理 校验错误响应体是否符合错误契约
5xx 服务端错误 500 - 599 服务端处理失败 校验是否返回了通用的错误结构

3.3.2 核心状态码详解

  • 200 OK:请求成功。GET 请求返回资源,POST 请求返回创建结果。
  • 201 Created:资源创建成功。通常用于 POST 请求,响应头应包含 Location 指向新资源。
  • 204 No Content:请求成功,但无返回内容。常用于 DELETE 操作。
  • 400 Bad Request:请求格式错误或参数校验失败。契约校验应返回明确的错误信息。
  • 401 Unauthorized:未提供或提供了无效的身份凭证。
  • 403 Forbidden:身份有效,但无权限访问资源。
  • 404 Not Found:请求的资源不存在。
  • 422 Unprocessable Entity:请求体语义错误,常用于校验失败场景。
  • 500 Internal Server Error:服务端内部错误,通常不包含具体错误细节。
Go 代码示例: 在 Go 中,使用 http.StatusOK 等常量来引用状态码,避免硬编码数字。
package main

import "net/http"

func handleUserGet(w http.ResponseWriter, r *http.Request) {
    // 模拟校验逻辑
    if r.URL.Query().Get("id") == "" {
        http.Error(w, `{"error":"missing id"}`, http.StatusBadRequest)
        return
    }
    // 处理成功
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"id":"123","name":"Alice"}`))
}

3.4 本章小结