API 什么时候该返回 204 No Content?
题干与适用场景
请为一个 REST API 设计空响应契约:删除资源成功后如何响应?查询集合没有匹配项时是否返回 204?更新成功但不需要返回表示时如何选择?需要区分资源不存在、操作成功但无表示、异步处理中和空集合是合法结果。假设客户端由多种语言生成,契约需要长期兼容。
面试官考察点
强回答会把状态码当作资源语义,而非“有没有数据”的快捷开关。204 表示请求成功且没有消息内容,响应不能带消息体;200 表示成功并可返回稳定的表示,例如 [];404 表示目标资源不存在或服务器没有其当前表示。候选人还应考虑 DELETE 幂等性、缓存、SDK 解码和 OpenAPI 文档。
回答前需要澄清的问题
- 操作目标是什么?删除单个资源、更新资源和查询集合的语义不同。
- 空集合是正常业务结果吗?若是,200 加空数组通常比 204 更能保持响应类型稳定。
- 客户端是否必须解码统一 JSON?若 SDK 总是读取 body,204 可能触发“意外 EOF”,需要明确分支。
- 成功后客户端是否需要新资源表示、ETag 或异步任务 ID?需要时不应丢弃响应内容,应选 200、201 或 202。
推荐解法与推导
建立按操作和表示需求划分的契约:
DELETE /users/42成功且没有要返回的表示,可用 204;重复删除若业务视为幂等成功,也可继续 204,但必须在文档中固定。GET /users?team=none若集合存在但没有成员,返回 200 和[],保持列表类型稳定;不能把“零行”误判成资源不存在。GET /users/42找不到目标资源返回 404;这是目标资源语义,不是空列表语义。PUT /users/42成功且客户端需要更新后的表示,用 200 和资源 JSON;成功但不返回表示,可用 204,并通过 ETag 等响应头提供元数据。- 接受请求但后台仍处理,用 202 和任务状态链接,而不是伪装成 204。
HTTP/1.1 204 No Content
ETag: "user-42-v7"
Cache-Control: no-store
HTTP/1.1 200 OK
Content-Type: application/json
[]RFC 9110 明确 204 不包含消息内容;因此客户端、代理和测试都应以“无 body”为契约。不要为了统一 HTTP 200 而把业务错误塞进 200,也不要为了省几个字节让每个空结果都变成 204。
替代方案与取舍
200 空数组的优势是类型稳定、生成 SDK 容易处理、列表分页和元数据可继续返回;代价是响应体多几个字节。204 的优势是明确表达“成功且无表示”,适合 DELETE 或不需要回显的更新;代价是客户端必须处理无 body,不能在 body 中返回错误细节或资源。404 应保留给目标资源不存在,不能拿来表示合法的空集合。
失败场景、边界与反例
- 对
GET空列表返回 204,导致客户端把“空结果”当成另一种响应类型,破坏分页和泛型解码。 - 204 仍发送 JSON body;标准语义禁止消息内容,代理可能丢弃或客户端行为不一致。
- DELETE 第一次 204、第二次 404,却没有说明幂等策略,客户端重试会产生不必要的错误。
- 用 200
{ "error": ... }表示失败,监控和 SDK 会把业务错误当成功。 - 更新后需要新 ETag 或版本号却返回 204 且不提供响应头,客户端无法安全缓存或继续并发控制。
测试与验证清单
为每个端点写状态码、body、Content-Type、ETag 和缓存头契约测试。覆盖第一次和重复 DELETE、空集合、缺失单体资源、成功更新有无表示、202 异步分支、代理转发和 SDK 解码。用 OpenAPI 生成至少一种客户端,验证 204 不触发 JSON 解析错误;同时检查监控按 2xx、404 和业务错误字段正确分组。
追问与延伸
204 能否带 ETag 或其他响应头?
可以带响应头;禁止消息内容不等于禁止元数据。ETag、缓存控制或追踪 ID 可帮助客户端并发控制和诊断,但要在契约中说明其存在条件。
空分页返回 200 还是 204?
若端点的表示类型是列表,优先 200 加空数组,并保留分页元数据。只有端点明确把“成功但无表示”作为语义,且所有客户端都能处理无 body 时,才考虑 204。
DELETE 找不到资源时必须 404 吗?
不一定。若 API 把删除定义为幂等的“确保资源不存在”,重复请求可以返回 204;若调用者需要知道目标曾否存在,则返回 404。选择应稳定记录在文档、SDK 和监控中。