接口契约描述客户端可以依赖什么:请求字段、身份、成功结果、错误、重试方式以及版本变化。只列一个 URL 和 JSON 示例,无法回答“响应丢了以后能不能重发”。先读网络与 HTTP 基础,再把协议语义落实到服务行为。
先定义一次预约
假设接口为 POST /reservations,请求数量必须为正整数,预约属于当前登录用户。接口返回 201 Created、Location: /reservations/42 和预约记录。客户端提供 Idempotency-Key,同一用户用相同键与相同请求重试,取回原记录;同键换参数返回 409。这是业务约定,不能认为 HTTP 会自动实现。
POST /reservations HTTP/1.1
Content-Type: application/json
Idempotency-Key: reserve-20261024-001
{"sku":"book-1","quantity":2}
| 情况 | 响应 | 客户端下一步 |
|---|---|---|
| JSON 语法错或字段不合法 | 400 | 改请求,原样重试没有用 |
| 没有通过身份验证 | 401 | 完成身份验证 |
| 身份已知但没有权限 | 403 | 不能通过换参数绕过 |
| 库存不够或幂等键冲突 | 409 | 根据业务状态处理 |
| 被限流 | 429 | 尊重 Retry-After,并限制重试次数 |
| 服务内部错误 | 500 | 记录请求标识;是否重试取决于操作语义 |
错误响应可以统一为 {"code":"OUT_OF_STOCK","message":"库存不足","requestId":"..."}。客户端依据稳定的 code 决策,message 给人阅读;不要把 SQL、堆栈或密钥返回给客户端。生产接口也可采用 RFC 9457 的 Problem Details,关键是保持契约一致。
幂等不等于每次响应完全相同
GET 应是安全方法,不用它执行扣款或删除。PUT 和 DELETE 的幂等性描述重复执行的预期服务器效果;第一次删除返回 204、第二次返回 404,仍可能符合幂等语义。POST 默认没有这一保证,预约和支付需通过幂等键、唯一约束及事务建立业务幂等。
超时只说明客户端没按时拿到结果。服务器可能已提交数据,盲目换键重试会创建第二条记录。事务与隔离负责原子提交,消息投递讨论跨进程后的重复执行。
分页、更新与通信方式
对不断新增的记录,用稳定排序 (created_at DESC, id DESC) 配合游标。游标包含最后一条记录的两个排序值,服务端继续查询更小的元组,避免仅按时间排序漏掉同一时刻的记录。游标应校验格式并限制页大小,不能把客户端传来的 SQL 片段拼进去。
更新会被多人同时修改的资料时,可使用版本号或 ETag 与 If-Match;版本不符返回 412 Precondition Failed,让客户端重新读取。新增可选字段通常比改名、改类型安全;删除字段要考虑旧客户端的实际存活周期。
| 需求 | 可选方式 | 需要承担的成本 |
|---|---|---|
| 公开资源接口、容易调试 | HTTP + JSON | 契约、版本与错误需要明确约定 |
| 内部强类型调用、流式 RPC | gRPC | IDL、代码生成、代理与客户端兼容 |
| 服务器持续单向推送 | SSE | 重连、事件标识、代理缓冲和连接数 |
| 双向实时交互 | WebSocket | 消息协议、心跳、背压、重连和鉴权 |
动手检查
运行 Go HTTP 服务实验,分别发送合法 JSON、未知字段、两个连续 JSON 和超过限制的请求体。解释为什么“能解码第一个对象”还不足以证明整个请求合法。
再为预约接口列出四个用例:首次成功、同键重试、同键不同参数、两个并发请求。每个用例写清响应与数据库最终状态。不要只检查状态码。
面试自查
- GET、PUT、POST 的安全性与幂等性分别指什么?为什么网络超时不能直接判定操作失败?
- 游标分页为什么需要唯一的排序末项?它能否天然保证跨页读取的是同一个数据快照?
- 用状态码表达所有业务错误,与所有响应都返回 200,各会损失什么信息?