一、HTTP 协议基础与请求响应模型

聊到接口设计,绕不开的就是 HTTP。很多人觉得 HTTP 就是发个请求拿个数据,没什么好讲的。但我在项目中踩过不少坑——都是因为对 HTTP 理解不够深。说白了,HTTP 是你和服务器之间的沟通语言,你不懂这门语言,设计出来的接口就容易闹笑话。

1.1 HTTP 是什么?

HTTP(HyperText Transfer Protocol)是一种无状态、应用层的通信协议。无状态意味着每次请求都是独立的,服务器不会自动记住你上次说了什么。嗯,这里要注意——无状态是核心特性,但也正是它导致我们对 Cookie、 这些机制的需求。

我刚开始做后端时,老以为 HTTP 连接是一直保持的,结果压测时发现连接池配置不对,线上直接挂了一半连接。后来才搞清楚:HTTP 1.1 默认是持久连接(Keep-Alive),但连接数、超时时间都需要你自己把控。

特性 说明 我遇到过的问题
无状态 服务器不记忆请求间上下文 以为 Session 自动共享,结果跨节点丢了登录态
应用层协议 基于 TCP/IP,只管数据格式不负责传输可靠性 忽略了 TCP 粘包,自定义协议时解析出错
可扩展 头字段可以自定义,方法可以扩展 滥用自定义 Header,导致网关无法识别

1.2 请求响应模型——核心骨架

一个完整的 HTTP 事务长这样:客户端发请求,服务端回响应。是不是听起来很简单?但真正设计接口时,很多细节会把你绊倒。

请求结构(Request):

  • 请求行:方法 + 路径 + 协议版本
  • 请求头(Headers):元信息,比如 Content-Type、Authorization
  • 请求体(Body):可选,POST/PUT 通常携带数据

响应结构(Response):

  • 状态行:协议版本 + 状态码 + 原因短语
  • 响应头:类似请求头,比如 Server、Content-Type
  • 响应体:返回的数据

举个例子:你打开浏览器访问一个页面,浏览器帮你组装了一个 GET 请求。服务端返回 200 OK,附上 HTML 内容。这些看起来都挺自然,对吧?但换成接口设计时,很多人会乱用状态码:

  • 200 表示“请求成功”没问题
  • 但有人用 200 返回“用户未登录”的错误信息,还用 200 返回“参数校验失败”
  • 我见过一个项目,所有响应都返回 200,然后 body 里塞一个 code 字段来表示成功还是失败

曾经我接手过一个老项目,整个接口层统一返回 200,内部用字符串 resultCode 来区分成功和失败。前端要写一长串 if…else,而且 nginx 日志里根本看不清真实错误率。后来我们花了三周才把所有接口改成标准状态码+业务码的两层结构。这是个深刻的教训:标准协议的行为别去覆盖。

1.3 核心方法及选择策略

HTTP 方法定义了客户端想对资源做什么。我个人习惯用一个简单原则:

  • GET:查,幂等、安全(不改变资源状态)
  • POST:创建新资源,不幂等
  • PUT:覆盖更新,幂等
  • PATCH:部分更新,不保证幂等
  • DELETE:删除,幂等

你想想看,一个“修改用户昵称”的接口,该用 PUT 还是 PATCH?如果你只传昵称字段,其他字段保持不变,用 PATCH 更合适。因为 PUT 通常要求客户端发送完整资源,否则没传的字段可能被重置为默认值。我在项目里就见过这种情况:前端只传了昵称,后端用 PUT 接收,结果把用户的手机号清空了——还好是测试环境。

方法 幂等 安全 典型场景
GET 是 是 查询列表、获取详情
POST 否 否 创建订单、上传文件
PUT 是 否 完整替换资源
PATCH 否 否 更新单个字段
DELETE 是 否 删除资源

小提示:幂等性特别重要。如果你的接口在网络重试时会产生重复数据,那多半是选错了方法或没有做幂等处理。我通常会对 POST 创建类接口加一个 Idempotency-Key 头来保证重试不重复。

1.4 状态码:让你的响应会说话

状态码是响应里最直观的信号。你拿到 200 就知道一切正常,拿到 404 就知道资源没找到。但常见误区是:把业务逻辑错误和系统错误混在一起。

我建议分成三层:

  1. 2xx:请求本身成功,业务处理成功
  2. 4xx:客户端的问题,比如参数不对、权限不足、资源不存在
  3. 5xx:服务端的问题,比如数据库挂了、程序异常

举个例子:用户请求一个不存在的订单。

// 错误做法:返回 200 + 业务错误码
HTTP/1.1 200 OK
Content-Type: application/json
{"code": 1001, "message": "订单不存在"}

// 正确做法:返回 404 + 明确提示
HTTP/1.1 404 Not Found
Content-Type: application/json
{"code": "ORDER_NOT_FOUND", "message": "订单不存在"}

为什么?因为中间件、负载均衡器、API 网关都依赖状态码做监控和路由。你统一 200,它们就失去了判断力。我曾经在一个网关项目中看到,运维把 5xx 阈值设得很低,但实际 5xx 全是业务错误码——因为所有接口都返回 200,网关没办法区分。

1.5 请求与响应模型全景图

让我们把所有要素串起来。下面这张图展示了一次完整的 HTTP 请求-响应流程,以及各阶段的关键要素。

HTTP 请求响应完整流程 客户端发送 请求行 + 头 + 体 服务端处理 路由+业务+编码 服务端响应 状态行 + 头 + 体 请求结构分解 • 请求行:GET /api/users HTTP/1.1 • 请求头:Host, Authorization, Accept... • 空行(CRLF) • 请求体:JSON / XML / 表单 / 二进制 • 注意:GET 没有请求体 响应结构分解 • 状态行:HTTP/1.1 200 OK • 响应头:Content-Type, Cache-Control... • 空行(CRLF) • 响应体:JSON / HTML / 二进制流 • 状态码:1xx~5xx 各有含义 一次完整的 HTTP 事务

这张图把一次 HTTP 事务拆成了三步:客户端发、服务端处理、服务端回。你写接口时,每一步都有讲究。例如:请求体格式用什么?JSON 还是 FormData?我建议团队统一用 JSON,除非是文件上传场景。响应体里要不要包含时间和状态码?最好统一。

1.6 常用请求头与响应头

头信息是元数据,但很多人忽略它。举个例子:

  • Content-Type 告诉接收方怎么解析 body。你发 JSON 就要写 application/json,否则对方可能当成纯文本
  • Authorization 传递认证信息,一般用 Bearer <> 格式
  • Cache-Control 控制缓存行为。我见过接口返回数据后浏览器一直用缓存,用户看不到最新数据——排查半天发现响应头漏了 no-cache

我曾经踩过一个大坑:在响应头里用了 Transfer-Encoding: chunked,但业务需要 Content-Length。结果一些老版本的代理无法正确处理 chunked 编码,导致数据截断。从那以后我坚持:能确定 body 长度时,就用 Content-Length;不确定时才用 chunked。

1.7 无状态下的会话保持

HTTP 本身无状态,但业务需要状态。常见的方案有 Cookie + Session、(JWT)、OAuth。我个人更推荐 方案:

  • 服务端不用维护会话存储,更容易水平扩展
  • 客户端在每次请求的 Authorization 头中带上
  • 注意 的过期时间和刷新机制

但 也不是银弹。我曾经设计过一个系统, 里存了太多权限信息,导致每次请求 header 都超过 4KB,最终被 nginx 截断。解决方法:只存用户 ID,权限放缓存查。

1.8 小结

HTTP 协议看似基础,但接口设计的很多问题都根源于对协议理解不足。弄清楚请求行、头、体的结构,选对方法和状态码,再用 Cookie / 解决无状态问题——这就是本章所有的核心内容。下一小节开始,我们会把这些知识用在真正的 API 结构设计里,一步步搭建规范。