调试做多了,会养成一种条件反射:看一眼HTTP响应码,心里立刻分好“这锅该谁背”。HTTP响应码这件事,表面上就是查表背答案,可真做几年前后端接口开发后你会发现,真正值钱的不是记得熟,而是看到一个码,能立刻判断它是哪个层次的问题、该往哪个方向查、该给调用方什么反馈。这篇文章不打算只列一张速查表,而是想把状态码从协议设计的底层逻辑讲到实际项目里的使用规范,把前后端、测试同学之间最容易扯皮的几个争议点一并聊透。适合刚接触接口开发的新同学,也适合写接口文档时每次都在“返回200还是201”上纠结的老手。1. 三位数字的分层设计:状态码为什么这样组织1.1 第一位数字才是真正的“问题分区”HTTP状态码永远是三位数字,第一位从1到5。这个设计看起来平平无奇,其实是最关键的一步:协议设计者把整个问题空间划分成了五个区域。1xx是过程性信息、2xx是成功、3xx是需要客户端换一种方式再做一次、4xx是客户端给进来的输入不对、5xx是服务端自己出了问题。这样一来,哪怕你遇到一个完全陌生的状态码,只要认出第一位数字,就能立刻确定排查方向,这比把每个码当独立知识点去背高效得多。后两位属于细分编号。标准在设计时就预留了扩展空间,比如HTTP/1.1时代新增了409 Conflict、410 Gone,后来的WebDAV标准又补了422,限流需求明确后又出现了429和428。也就是说,状态码不是一张封闭的枚举表,而是一套有生命力的分类法。实际开发里,我经常看到有人对404、500脱口而出,但遇到422、429就开始翻文档。这不怪记忆力,是缺少那套“分层判断”的思维。如果你先看第一位、再查后两位,会顺很多:看到4,先默认“请求有问题”,再看22,确认是“实体确实无法被处理”,整个判断过程不过一秒。1.2 原因短语不参与程序逻辑响应行里除了三位数字,还有一个人类可读的原因短语,比如200 OK、404 Not Found。很多初学者误以为程序可以拿这个短语做判断,实际上完全不行。不同服务端实现可能返回不同的短语,别说不同语言框架,同一个项目里,各种“OK”“Created”“success”也五花八门。客户端只能依赖数字,短语是给人看的。这也是我参与接口评审时经常纠正的一点:后端返回“200 + 自定义提示语”,前端拿短语做判断,一旦文案改了,整条链路就崩了。状态码还有一个容易被忽略的生态问题:标准之外存在大量自定义码。比如在一个常见的开源Web服务器里,定义了499表示“客户端提前断开连接”,444表示“连接关闭且无响应”。这些码是运维层面非常实用的信号,你会频繁在网关日志里碰到。它们不在标准规范里,但你已经不能不知道它们。准确的说法是:以标准状态码为骨架,以团队实际使用的私有码为补充,先把首位分类焊死在脑子里。2. 1xx和2xx:不只“成功”,更要关注成功的程度差在哪2.1 1xx并不是纯理论:100、101、103背后的真实动作如果你是写业务接口的,平时基本不会主动构造1xx,但这些码在你的HTTP客户端内部一直在悄悄工作。最典型的是100 Continue:客户端准备发送一个很大的请求体,它不直接整段传输,而是先发一个带Expect: 100-continue的请求头,等服务端点头说“可以”,再真正上传数据。这个机制是为了避免大body白白浪费带宽。平时你感知不到,是因为大多数请求库替你处理好了。101 Switching Protocols是WebSocket握手的基石。客户端请求里带上Upgrade: websocket,服务端同意切换协议就回101,之后的通信不再走普通的HTTP请求响应模型。如果拿不到这个码,WebSocket连接就会失败。我排查过不少“明明连上了但消息发不出去”的问题,最后都落在服务端没正确返回101上。还有一个相对年轻的103 Early Hints:服务端在真正响应之前,先把CSS、JS等静态资源的链接用Link头推给浏览器,让浏览器提前开始加载。它不能替代最终响应,只是“先派个人去排队”。原理上能省掉一个RTT,实际效果受浏览器兼容性影响,属于性能优化的进阶话题,不过值得记在脑子里。2.2 2xx家族的“成功程度”划分2xx里有几个码,它们的差异不是字面上的“成功与否”,而是“成功到什么程度、后续该怎么处理”。我给一个常用的资源操作模型:200 OK:最通用,通常用于GET查询、更新成功后返回资源。201 Created:新建资源成功,且响应头里的Location指向新资源URI。创建接口应该用它,而不是什么都回200。204 No Content:操作成功但没有返回体。DELETE操作特别合适,资源都删了,还回一个有body的200属于画蛇添足。202 Accepted:请求已被接收,但处理还没完成,适合异步任务场景,返回后配合轮询接口查询任务状态。206 Partial Content:支持Range请求时,只返回部分内容。视频拖进度条、断点续传这些功能的底层就是它。有同学会问:增加这些区分有什么意义?意义在于调用方的行为可以因此变得精准。前端拿到204就知道不需要解析body,拿到201就知道可以跳新地址或刷新列表,拿到202就知道要去轮询。如果把所有成功都压成200,调用方只能靠猜,或者靠body里的业务字段猜,后端改一个字段,前端就崩一次。2.3 “全200”反模式,我劝你别碰我见过不少团队做法:对外接口不管什么情况都返回200,真正的成败放在body里的code字段,理由是“HTTP状态码不够业务化”。这个理由我能理解,但结果通常很糟糕。全200之后,网关的限流告警、访问日志的状态码统计、前端的统一拦截器,等于全部废掉。排障时你只能一个个去打业务日志,再把日志和response里的code字段手动对齐,时间成本翻倍。更麻烦的是,一旦外部调用方只看HTTP码,你的“业务错误”会被对方当成成功处理,在数据一致性上埋大雷。不是说业务错误码完全不能用,而是不能反过来压过HTTP码。正确做法是:HTTP码负责表达“请求-响应过程的物理结果”,body里的业务码负责表达“业务规则有没有走通”,两者是主从关系,不是替代关系。该用401就用401,该用422就用422,让链路每一层都能看懂。3. 3xx:重定向的水很深,302的历史包袱到今天还在3.1 301和308:“永久搬家”里的方法语义差异301是大家最熟悉的永久重定向,但它有一个历史包袱:早期浏览器遇到POST请求返回301时,可以把它改成GET再跳转。如果此时请求body里有表单数据或登录态,body就丢了。308正是为了解决这个问题被标准化出来的永久重定向,它强制要求“保持原方法和原body”。所以我的建议是:做API迁移、域名切换这类需要保留请求方法的场景,用308;单纯想把一个页面永久搬家、而且确定目标方法只走GET的,用301就够了。千万别在不确认方法语义的情况下随手写301,否则线上就会出现“表单提交到了A接口,浏览器却跳到B接口且参数全丢光”的诡异问题。3.2 302、303、307:到底谁该把POST变GET302 Found从老规范继承下来,设计意图是“临时重定向,资源暂时在别处”。但浏览器们对302的实际处理早就走了样:很多浏览器在302下会把POST改成GET,body直接丢掉。规范后来为了挽回这个混乱局面,才补充了303和307。303 See Other:明确告诉客户端“你应该用GET去另一个URI取结果”。最经典的场景是表单提交后的Post/Redirect/Get模式:POST写数据成功,返回303加Location到结果页,浏览器自动GET新页面。刷新结果页时不会重复提交表单,这是防止脏数据的重要手段。307 Temporary Redirect:严格保留原方法和body,适合API转发、网关转发。如果你不想让请求方法被替换,307是正解。所以,如果有人建议“表单提交后返回302”,你可以解释:302在不同浏览器下的行为不可控,规范化的做法是用303。这一条在写Web框架的同学那里尤其容易踩坑。3.3 304 Not Modified:缓存协商不是“响应码错误”304是3xx里很特殊的一个,它不代表“去别处找”,而是“你本地缓存还可以继续用”。流程大概是:第一次请求,服务端返回200和资源,同时带Last-Modified或ETag;第二次请求,客户端带上If-Modified-Since或If-None-Match;服务端一看资源没变,就返回304且不带body,浏览器直接使用缓存。这个交互能省下大量带宽。实际项目里经常有人把304当异常处理,因为前端拦截器看到3xx就想拦下来。正确做法是对304放行,让它走浏览器缓存机制。304用得好,静态资源和服务端压力都能肉眼可见地降下来。3xx里还有一个坑要单独提醒:301这类永久重定向会被浏览器和HTTP客户端长期缓存。线上如果因为误配把某个URI指错了,改成正确目标之后,老客户端可能还继续访问错误地址,排障时极其迷惑。我的经验是:能先用302或307临时重定向做验证,就不要一上来就放301。迁移方案稳定后再切永久码,切换前还要考虑客户端缓存清理的问题。4. 4xx:客户端错误不是“都是用户的错”,要看错在哪一层4.1 401和403是最常见的混淆点401 Unauthorized和403 Forbidden,一个“未认证”一个“无权限”,中文翻译经常让人混淆。简单归纳:401:我根本不知道你是谁,或者你的身份凭证无效、已过期。响应头通常还会带WWW-Authenticate,告诉客户端该用什么方式认证。典型场景就是未登录访问需要登录的接口。403:我已经认识你了,但你无权做这件事。典型场景是普通用户访问管理员接口。然后有一条安全层面的建议:如果某个资源确实存在,但调用方没权限,直接返回403最诚实。如果你的业务担心“接口是否存在”本身会被探测,比如目录枚举扫描,可以对未授权请求统一伪装成404,降低信息暴露面。这个取舍要结合业务和合规要求,不能一刀切。4.2 400系列里值得掌握的细分码400 Bad Request:通用的“请求不合法”,可以是JSON解析失败、参数校验不过、请求头格式错误。它最大的问题是太笼统,所以很多团队会继续往下细分。422 Unprocessable Entity:请求格式本身合法,但内容语义无法处理。比如用户名已存在、邮箱格式非法。我推荐把“业务规则失败”归到422,把“字面格式损坏”留在400,前后端沟通成本会明显降低。405 Method Not Allowed:URL存在,但方法不对,响应头里要带Allow说明允许哪些方法。只允许GET/POST的接口收到DELETE,回405比回404更准确,调用方才知道是方法错了而不是路径错了。406 Not Acceptable:请求头里的Accept指定了服务端无法满足的格式。我处理过一个很典型的现场:前端代码把Accept写死成application/xml,后端只出JSON,接口就回406。前端盯着body里的JSON解析报错看半天,最后才发现问题出在请求头。409 Conflict:资源状态冲突,典型场景是基于版本号更新时版本对不上,回409提示用户刷新重试。410 Gone:资源以前存在,现在被故意移除。和404的差别在于,410会明确告诉搜索引擎“这页是故意下架的”,对索引清理有帮助。413 Payload Too Large:请求体超出上限,常见于上传文件过大。414 URI Too Long:URI太长,常见于GET请求塞了海量参数。415 Unsupported Media Type:Content-Type不支持,比如后端只吃JSON,前端却传了表单。416 Range Not Satisfiable:Range请求的区间不合法,服务端无法满足。428 Precondition Required:要求使用条件请求,比如必须带If-Match来避免竞态,客户端没带时就回428。另外有一种“软404”现象值得提一句:页面路由存在,但内容却是空的,用户看到的是空白页,搜索引擎也会判定为低质量页面。它严格说不算状态码问题,但在排查404时经常被误判成“找不到资源”,看到这里可以多个心眼。4.3 429和限流:被前端和后端同时忽略的码429 Too Many Requests在流量起来之前,很多团队根本记不住。它背后是限流机制:单位时间内请求次数超过阈值,服务端直接拒绝,并建议在响应头Retry-After里告诉客户端“你得等多久再试”。高并发系统里,限流是保命底线。对前端而言,拿到429的正确姿势不是立即重试,那样往往越试越糟,而是读取Retry-After做退避等待;也不要把429和5xx混在一起处理,它是预期内的“慢一点再试”。对后端而言,限流要分级:接口级、用户级、IP级,别一把锁锁死所有正常流量。4.4 你会遇到的私有4xx:499和444这类码不在标准规范里,但凡是经历过网关日志巡游的人都会遇到。499在某个常见开源Web服务器里表示“客户端在服务端完成响应前断开了连接”。我碰到的典型问题:客户端设置的请求超时是10秒,服务端某个接口正常耗时要15秒,客户端在10秒后放弃等待并断开连接,服务端却还在处理,网关最后记录成499。排查这类问题,先对齐客户端超时和服务端耗时的差值,把超时参数调合理,再考虑给慢接口做异步化或缓存。444表示“连接关闭且无响应”,多用于访问控制直接把请求丢掉,日志里出现这个码时,优先怀疑安全策略或限流拦截。4.5 4xx排查的第一反应看到4xx,第一反应永远是“检查请求本身”:URL对不对、方法对不对、请求头带没带、body格式是否合法、字段名是否匹配、编码有没有乱。4xx的排查效率不靠祈祷,靠的是先看请求再谈服务端。我调试时的习惯是开抓包工具或者直接看客户端请求日志,很少一上来就翻服务端日志,那样多半白费功夫。5. 5xx:服务端错误不是“500一出全白费”,要分清是谁没交付5.1 500、501、505的边界500 Internal Server Error是服务端的通用兜底,可以覆盖未捕获异常、数据库连接失败、依赖服务崩溃等原因。正因为它太通用,很多团队喜欢把所有异常都堆给500,这会让排障很被动。501 Not Implemented和500相反,意思是“服务器不认识这个请求方法”,更多出现在实现不完整的接口上。505则专门表示HTTP版本不受支持,多见于老系统和新客户端之间的摩擦。对于5xx有一条数据安全红线:所有5xx响应都不能把内部细节直接丢给客户端,堆栈信息、SQL语句、文件路径、密钥都严禁出现在body里,否则一个500就是一次信息泄露。正确做法是:响应体只给一个通用错误描述和trace ID,详细堆栈写到服务端日志,排障时靠trace ID关联日志定位。5.2 502、503、504:网关层三个“服务端没干成”的叙事这三个码在网关架构下天天遇到,但它们解决的问题不一样:502 Bad Gateway:网关把请求转发给上游后,发现上游返回了无效响应。常见原因包括上游进程崩溃、连接被拒绝、上游返回的协议内容不合法。可以理解为“快递员到了收件地址,发现收件人拒收或根本不在”。503 Service Unavailable:服务当前不在可用状态,比如过载、熔断、发布窗口、维护中。它和502/504的重要区别在于,服务端自己知道自己“暂时不行”,通常会带Retry-After。504 Gateway Timeout:网关等上游响应等太久了,主动放弃。常见原因是上游线程池打满、慢SQL、下游依赖响应慢。可以理解为“快递员在门口等了太久,等不起,只能先走”。排查502和504的方向完全不同:502先查上游进程是否活着、端口是否通、协议是否匹配;504先查上游的耗时、线程池、慢SQL,再看网关超时时间是不是设得太短。很多团队把“5xx”做成一个笼统的告警指标,看到告警就以为系统崩了,实际上502和504的排查路线不在一个频道上,告警分组必须分开。5.3 非标准5xx:CDN和网关私有码的识别方式除了标准5xx,不少网关和CDN服务商会有自己的私有码,用来标记“源站返回错误”“源站连接失败”“源站SSL握手失败”“源站超时”这一类更精确的情况。这类码通常以52x或其他相近数字开头,第一次见到的人会以为它是标准码,实际上只在特定网关内有效。识别方法很简单:去查这个请求实际经过的网关文档对照表,不要硬套标准码的含义。遇到这种私有码,排查重点永远在“网关和源站之间的传输层发生了什么”,而不是业务代码本身有没有异常。5.4 一个可落地的5xx排查流程遇到5xx,我一般按这个顺序来,能省很多无用功:找全链路标识:从响应头或响应体里找trace ID或request ID,没有这个标识,后面基本是盲人摸象。分层定位:明确5xx是从客户端入口网关返回的,还是应用服务自己返回的。方法很直接,看响应来源或网关日志。区分“连接层失败”和“处理层失败”:502、504分属两个大方向,先站对队。查上游健康状态:进程是否存活、端口是否监听、日志时间戳是否连续,有没有OOM触发的崩溃重启。查依赖服务:数据库连接池、缓存、下游API耗时,5xx往往是被依赖方拖垮的。结合监控看趋势:比如某个接口在发布后突然大量503,优先怀疑发布内容;某个接口持续出现504,重点查慢调用链。这些步骤不复杂,但很多人一看到5xx就直接往应用日志冲,忽略了“先分清是网关层还是应用层”这个前提。6. 从“会分类”到“会用码”:给团队一套能直接落地的规范6.1 最小够用集:别让团队背全表HTTP状态码全家有一百多个,正经项目用不到一半。我建议每个团队维护一张“最小够用集合”,作为接口开发的默认选项:场景推荐状态码说明查询成功200返回资源体创建成功201返回Location删除成功、无body204也可以用200加空body异步任务受理202配合轮询接口请求格式不合法400或细分到422业务规则不满足422语义正确但校验不过未认证401带认证方案说明无权限403或按安全需求伪装404路径/资源不存在404注意区分软404方法不对405带Allow头冲突/版本不一致409提示用户刷新请求体过大413上传场景常用频率超限429带Retry-After服务端兜底500不暴露堆栈服务不可用503带Retry-After网关超时504优先查上游耗时这只是一个起点,团队可以按业务注入自己的私有码,但一定要写进接口文档,不能只存在某位老开发员的脑子里。6.2 前端统一拦截:别在每个页面里散装处理前端拿到状态码后,最佳做法是在请求库的统一拦截层做分类处理,而不是每个页面复制粘贴一段错误弹窗。大致逻辑是:// 伪代码:统一拦截HTTP状态码 http.interceptors.response.use( res => res, err => { const status = err.response && err.response.status if (status >= 500) { // 服务端异常:统一提示 + 上报监控,避免用户反复点击 reportError(err) toast('服务暂时不可用,请稍后重试') } else if (status === 401) { // 未认证:清理登录态,跳转登录页 redirectToLogin() } else if (status === 403) { toast('当前账号没有权限') } else if (status === 429) { // 限流:读取Retry-After,做退避,不建议无脑重试 const retryAfter = err.response.headers['retry-after'] scheduleRetry(retryAfter) toast('请求太频繁,稍后再试') } else if (status === 422) { // 业务校验:把body里的字段错误展示到表单 showFieldErrors(err.response.data) } else { toast('请求失败,请检查网络') } return Promise.reject(err) } )这里的重点是“分类处理”,不是每个码写一个分支。5xx统一提示并上报,401跳登录,422回显校验错误,429退避重试,剩下的兜底提示。前后端一起把这个清单定下来,联调会顺畅很多。6.3 监控与文档:状态码的价值在于可统计状态码除了给单个请求定性,更大的价值是给整个系统做统计。5xx比例异常攀升,通常意味着发布事故或依赖故障;某个接口4xx数量突然暴涨,可能是前端参数格式改坏了,也可能是被恶意扫描。运维和开发团队应该对状态码做分类告警:5xx按错误码和接口维度告警,429按限流触发次数告警,401异常增多要考虑账号被批量尝试登录的可能。写接口文档时,每个接口除了“成功返回体”,一定要加一节“错误响应码”,把可能出现的码和对应场景列清楚。我第一次带项目时忽略了这件事,前端集成全靠猜,上线前临时补文档,结果遗漏了两个隐藏错误码,出了线上问题。从那以后,状态码清单成了接口评审的必查项。6.4 两个能救急的实操小技巧最后分享两个我在实际工作中经常用的小技巧。第一个是快速批量检查一批URL各自返回什么码,直接用curl的输出模板:curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/health把URL换成你要探测的地址,一秒就能看到状态码。批量循环时配合这个-w参数,写监控脚本也很好用。第二个是验证缓存相关状态码比如304时,浏览器和本地缓存会干扰判断,测试时可以在URL后加一个随机query参数来绕过缓存,例如https://api.example.com/resource?ts=1234。这样你看到的才是服务端真正的返回行为,而不是缓存命中后的结果。我也背不全那一百多个HTTP响应码,但我习惯把“首位定方向、后两位定动作、私有码按网关文档查”这三句话放在心里。每次遇到拿不准的响应码,先看第一位数字,排查范围立刻缩小一半,剩下的交给日志和监控。这就是状态码分类汇总这件事最值钱的地方。
调试做多了,会养成一种条件反射:看一眼HTTP响应码,心里立刻分好“这锅该谁背”。HTTP响应码这件事,表面上就是查表背答案,可真做几年前后端接口开发后你会发现,真正值钱的不是记得熟,而是看到一个码,能立刻判断它是哪个层次的问题、该往哪个方向查、该给调用方什么反馈。这篇文章不打算只列一张速查表,而是想把状态码从协议设计的底层逻辑讲到实际项目里的使用规范,把前后端、测试同学之间最容易扯皮的几个争议点一并聊透。适合刚接触接口开发的新同学,也适合写接口文档时每次都在“返回200还是201”上纠结的老手。1. 三位数字的分层设计:状态码为什么这样组织1.1 第一位数字才是真正的“问题分区”HTTP状态码永远是三位数字,第一位从1到5。这个设计看起来平平无奇,其实是最关键的一步:协议设计者把整个问题空间划分成了五个区域。1xx是过程性信息、2xx是成功、3xx是需要客户端换一种方式再做一次、4xx是客户端给进来的输入不对、5xx是服务端自己出了问题。这样一来,哪怕你遇到一个完全陌生的状态码,只要认出第一位数字,就能立刻确定排查方向,这比把每个码当独立知识点去背高效得多。后两位属于细分编号。标准在设计时就预留了扩展空间,比如HTTP/1.1时代新增了409 Conflict、410 Gone,后来的WebDAV标准又补了422,限流需求明确后又出现了429和428。也就是说,状态码不是一张封闭的枚举表,而是一套有生命力的分类法。实际开发里,我经常看到有人对404、500脱口而出,但遇到422、429就开始翻文档。这不怪记忆力,是缺少那套“分层判断”的思维。如果你先看第一位、再查后两位,会顺很多:看到4,先默认“请求有问题”,再看22,确认是“实体确实无法被处理”,整个判断过程不过一秒。1.2 原因短语不参与程序逻辑响应行里除了三位数字,还有一个人类可读的原因短语,比如200 OK、404 Not Found。很多初学者误以为程序可以拿这个短语做判断,实际上完全不行。不同服务端实现可能返回不同的短语,别说不同语言框架,同一个项目里,各种“OK”“Created”“success”也五花八门。客户端只能依赖数字,短语是给人看的。这也是我参与接口评审时经常纠正的一点:后端返回“200 + 自定义提示语”,前端拿短语做判断,一旦文案改了,整条链路就崩了。状态码还有一个容易被忽略的生态问题:标准之外存在大量自定义码。比如在一个常见的开源Web服务器里,定义了499表示“客户端提前断开连接”,444表示“连接关闭且无响应”。这些码是运维层面非常实用的信号,你会频繁在网关日志里碰到。它们不在标准规范里,但你已经不能不知道它们。准确的说法是:以标准状态码为骨架,以团队实际使用的私有码为补充,先把首位分类焊死在脑子里。2. 1xx和2xx:不只“成功”,更要关注成功的程度差在哪2.1 1xx并不是纯理论:100、101、103背后的真实动作如果你是写业务接口的,平时基本不会主动构造1xx,但这些码在你的HTTP客户端内部一直在悄悄工作。最典型的是100 Continue:客户端准备发送一个很大的请求体,它不直接整段传输,而是先发一个带Expect: 100-continue的请求头,等服务端点头说“可以”,再真正上传数据。这个机制是为了避免大body白白浪费带宽。平时你感知不到,是因为大多数请求库替你处理好了。101 Switching Protocols是WebSocket握手的基石。客户端请求里带上Upgrade: websocket,服务端同意切换协议就回101,之后的通信不再走普通的HTTP请求响应模型。如果拿不到这个码,WebSocket连接就会失败。我排查过不少“明明连上了但消息发不出去”的问题,最后都落在服务端没正确返回101上。还有一个相对年轻的103 Early Hints:服务端在真正响应之前,先把CSS、JS等静态资源的链接用Link头推给浏览器,让浏览器提前开始加载。它不能替代最终响应,只是“先派个人去排队”。原理上能省掉一个RTT,实际效果受浏览器兼容性影响,属于性能优化的进阶话题,不过值得记在脑子里。2.2 2xx家族的“成功程度”划分2xx里有几个码,它们的差异不是字面上的“成功与否”,而是“成功到什么程度、后续该怎么处理”。我给一个常用的资源操作模型:200 OK:最通用,通常用于GET查询、更新成功后返回资源。201 Created:新建资源成功,且响应头里的Location指向新资源URI。创建接口应该用它,而不是什么都回200。204 No Content:操作成功但没有返回体。DELETE操作特别合适,资源都删了,还回一个有body的200属于画蛇添足。202 Accepted:请求已被接收,但处理还没完成,适合异步任务场景,返回后配合轮询接口查询任务状态。206 Partial Content:支持Range请求时,只返回部分内容。视频拖进度条、断点续传这些功能的底层就是它。有同学会问:增加这些区分有什么意义?意义在于调用方的行为可以因此变得精准。前端拿到204就知道不需要解析body,拿到201就知道可以跳新地址或刷新列表,拿到202就知道要去轮询。如果把所有成功都压成200,调用方只能靠猜,或者靠body里的业务字段猜,后端改一个字段,前端就崩一次。2.3 “全200”反模式,我劝你别碰我见过不少团队做法:对外接口不管什么情况都返回200,真正的成败放在body里的code字段,理由是“HTTP状态码不够业务化”。这个理由我能理解,但结果通常很糟糕。全200之后,网关的限流告警、访问日志的状态码统计、前端的统一拦截器,等于全部废掉。排障时你只能一个个去打业务日志,再把日志和response里的code字段手动对齐,时间成本翻倍。更麻烦的是,一旦外部调用方只看HTTP码,你的“业务错误”会被对方当成成功处理,在数据一致性上埋大雷。不是说业务错误码完全不能用,而是不能反过来压过HTTP码。正确做法是:HTTP码负责表达“请求-响应过程的物理结果”,body里的业务码负责表达“业务规则有没有走通”,两者是主从关系,不是替代关系。该用401就用401,该用422就用422,让链路每一层都能看懂。3. 3xx:重定向的水很深,302的历史包袱到今天还在3.1 301和308:“永久搬家”里的方法语义差异301是大家最熟悉的永久重定向,但它有一个历史包袱:早期浏览器遇到POST请求返回301时,可以把它改成GET再跳转。如果此时请求body里有表单数据或登录态,body就丢了。308正是为了解决这个问题被标准化出来的永久重定向,它强制要求“保持原方法和原body”。所以我的建议是:做API迁移、域名切换这类需要保留请求方法的场景,用308;单纯想把一个页面永久搬家、而且确定目标方法只走GET的,用301就够了。千万别在不确认方法语义的情况下随手写301,否则线上就会出现“表单提交到了A接口,浏览器却跳到B接口且参数全丢光”的诡异问题。3.2 302、303、307:到底谁该把POST变GET302 Found从老规范继承下来,设计意图是“临时重定向,资源暂时在别处”。但浏览器们对302的实际处理早就走了样:很多浏览器在302下会把POST改成GET,body直接丢掉。规范后来为了挽回这个混乱局面,才补充了303和307。303 See Other:明确告诉客户端“你应该用GET去另一个URI取结果”。最经典的场景是表单提交后的Post/Redirect/Get模式:POST写数据成功,返回303加Location到结果页,浏览器自动GET新页面。刷新结果页时不会重复提交表单,这是防止脏数据的重要手段。307 Temporary Redirect:严格保留原方法和body,适合API转发、网关转发。如果你不想让请求方法被替换,307是正解。所以,如果有人建议“表单提交后返回302”,你可以解释:302在不同浏览器下的行为不可控,规范化的做法是用303。这一条在写Web框架的同学那里尤其容易踩坑。3.3 304 Not Modified:缓存协商不是“响应码错误”304是3xx里很特殊的一个,它不代表“去别处找”,而是“你本地缓存还可以继续用”。流程大概是:第一次请求,服务端返回200和资源,同时带Last-Modified或ETag;第二次请求,客户端带上If-Modified-Since或If-None-Match;服务端一看资源没变,就返回304且不带body,浏览器直接使用缓存。这个交互能省下大量带宽。实际项目里经常有人把304当异常处理,因为前端拦截器看到3xx就想拦下来。正确做法是对304放行,让它走浏览器缓存机制。304用得好,静态资源和服务端压力都能肉眼可见地降下来。3xx里还有一个坑要单独提醒:301这类永久重定向会被浏览器和HTTP客户端长期缓存。线上如果因为误配把某个URI指错了,改成正确目标之后,老客户端可能还继续访问错误地址,排障时极其迷惑。我的经验是:能先用302或307临时重定向做验证,就不要一上来就放301。迁移方案稳定后再切永久码,切换前还要考虑客户端缓存清理的问题。4. 4xx:客户端错误不是“都是用户的错”,要看错在哪一层4.1 401和403是最常见的混淆点401 Unauthorized和403 Forbidden,一个“未认证”一个“无权限”,中文翻译经常让人混淆。简单归纳:401:我根本不知道你是谁,或者你的身份凭证无效、已过期。响应头通常还会带WWW-Authenticate,告诉客户端该用什么方式认证。典型场景就是未登录访问需要登录的接口。403:我已经认识你了,但你无权做这件事。典型场景是普通用户访问管理员接口。然后有一条安全层面的建议:如果某个资源确实存在,但调用方没权限,直接返回403最诚实。如果你的业务担心“接口是否存在”本身会被探测,比如目录枚举扫描,可以对未授权请求统一伪装成404,降低信息暴露面。这个取舍要结合业务和合规要求,不能一刀切。4.2 400系列里值得掌握的细分码400 Bad Request:通用的“请求不合法”,可以是JSON解析失败、参数校验不过、请求头格式错误。它最大的问题是太笼统,所以很多团队会继续往下细分。422 Unprocessable Entity:请求格式本身合法,但内容语义无法处理。比如用户名已存在、邮箱格式非法。我推荐把“业务规则失败”归到422,把“字面格式损坏”留在400,前后端沟通成本会明显降低。405 Method Not Allowed:URL存在,但方法不对,响应头里要带Allow说明允许哪些方法。只允许GET/POST的接口收到DELETE,回405比回404更准确,调用方才知道是方法错了而不是路径错了。406 Not Acceptable:请求头里的Accept指定了服务端无法满足的格式。我处理过一个很典型的现场:前端代码把Accept写死成application/xml,后端只出JSON,接口就回406。前端盯着body里的JSON解析报错看半天,最后才发现问题出在请求头。409 Conflict:资源状态冲突,典型场景是基于版本号更新时版本对不上,回409提示用户刷新重试。410 Gone:资源以前存在,现在被故意移除。和404的差别在于,410会明确告诉搜索引擎“这页是故意下架的”,对索引清理有帮助。413 Payload Too Large:请求体超出上限,常见于上传文件过大。414 URI Too Long:URI太长,常见于GET请求塞了海量参数。415 Unsupported Media Type:Content-Type不支持,比如后端只吃JSON,前端却传了表单。416 Range Not Satisfiable:Range请求的区间不合法,服务端无法满足。428 Precondition Required:要求使用条件请求,比如必须带If-Match来避免竞态,客户端没带时就回428。另外有一种“软404”现象值得提一句:页面路由存在,但内容却是空的,用户看到的是空白页,搜索引擎也会判定为低质量页面。它严格说不算状态码问题,但在排查404时经常被误判成“找不到资源”,看到这里可以多个心眼。4.3 429和限流:被前端和后端同时忽略的码429 Too Many Requests在流量起来之前,很多团队根本记不住。它背后是限流机制:单位时间内请求次数超过阈值,服务端直接拒绝,并建议在响应头Retry-After里告诉客户端“你得等多久再试”。高并发系统里,限流是保命底线。对前端而言,拿到429的正确姿势不是立即重试,那样往往越试越糟,而是读取Retry-After做退避等待;也不要把429和5xx混在一起处理,它是预期内的“慢一点再试”。对后端而言,限流要分级:接口级、用户级、IP级,别一把锁锁死所有正常流量。4.4 你会遇到的私有4xx:499和444这类码不在标准规范里,但凡是经历过网关日志巡游的人都会遇到。499在某个常见开源Web服务器里表示“客户端在服务端完成响应前断开了连接”。我碰到的典型问题:客户端设置的请求超时是10秒,服务端某个接口正常耗时要15秒,客户端在10秒后放弃等待并断开连接,服务端却还在处理,网关最后记录成499。排查这类问题,先对齐客户端超时和服务端耗时的差值,把超时参数调合理,再考虑给慢接口做异步化或缓存。444表示“连接关闭且无响应”,多用于访问控制直接把请求丢掉,日志里出现这个码时,优先怀疑安全策略或限流拦截。4.5 4xx排查的第一反应看到4xx,第一反应永远是“检查请求本身”:URL对不对、方法对不对、请求头带没带、body格式是否合法、字段名是否匹配、编码有没有乱。4xx的排查效率不靠祈祷,靠的是先看请求再谈服务端。我调试时的习惯是开抓包工具或者直接看客户端请求日志,很少一上来就翻服务端日志,那样多半白费功夫。5. 5xx:服务端错误不是“500一出全白费”,要分清是谁没交付5.1 500、501、505的边界500 Internal Server Error是服务端的通用兜底,可以覆盖未捕获异常、数据库连接失败、依赖服务崩溃等原因。正因为它太通用,很多团队喜欢把所有异常都堆给500,这会让排障很被动。501 Not Implemented和500相反,意思是“服务器不认识这个请求方法”,更多出现在实现不完整的接口上。505则专门表示HTTP版本不受支持,多见于老系统和新客户端之间的摩擦。对于5xx有一条数据安全红线:所有5xx响应都不能把内部细节直接丢给客户端,堆栈信息、SQL语句、文件路径、密钥都严禁出现在body里,否则一个500就是一次信息泄露。正确做法是:响应体只给一个通用错误描述和trace ID,详细堆栈写到服务端日志,排障时靠trace ID关联日志定位。5.2 502、503、504:网关层三个“服务端没干成”的叙事这三个码在网关架构下天天遇到,但它们解决的问题不一样:502 Bad Gateway:网关把请求转发给上游后,发现上游返回了无效响应。常见原因包括上游进程崩溃、连接被拒绝、上游返回的协议内容不合法。可以理解为“快递员到了收件地址,发现收件人拒收或根本不在”。503 Service Unavailable:服务当前不在可用状态,比如过载、熔断、发布窗口、维护中。它和502/504的重要区别在于,服务端自己知道自己“暂时不行”,通常会带Retry-After。504 Gateway Timeout:网关等上游响应等太久了,主动放弃。常见原因是上游线程池打满、慢SQL、下游依赖响应慢。可以理解为“快递员在门口等了太久,等不起,只能先走”。排查502和504的方向完全不同:502先查上游进程是否活着、端口是否通、协议是否匹配;504先查上游的耗时、线程池、慢SQL,再看网关超时时间是不是设得太短。很多团队把“5xx”做成一个笼统的告警指标,看到告警就以为系统崩了,实际上502和504的排查路线不在一个频道上,告警分组必须分开。5.3 非标准5xx:CDN和网关私有码的识别方式除了标准5xx,不少网关和CDN服务商会有自己的私有码,用来标记“源站返回错误”“源站连接失败”“源站SSL握手失败”“源站超时”这一类更精确的情况。这类码通常以52x或其他相近数字开头,第一次见到的人会以为它是标准码,实际上只在特定网关内有效。识别方法很简单:去查这个请求实际经过的网关文档对照表,不要硬套标准码的含义。遇到这种私有码,排查重点永远在“网关和源站之间的传输层发生了什么”,而不是业务代码本身有没有异常。5.4 一个可落地的5xx排查流程遇到5xx,我一般按这个顺序来,能省很多无用功:找全链路标识:从响应头或响应体里找trace ID或request ID,没有这个标识,后面基本是盲人摸象。分层定位:明确5xx是从客户端入口网关返回的,还是应用服务自己返回的。方法很直接,看响应来源或网关日志。区分“连接层失败”和“处理层失败”:502、504分属两个大方向,先站对队。查上游健康状态:进程是否存活、端口是否监听、日志时间戳是否连续,有没有OOM触发的崩溃重启。查依赖服务:数据库连接池、缓存、下游API耗时,5xx往往是被依赖方拖垮的。结合监控看趋势:比如某个接口在发布后突然大量503,优先怀疑发布内容;某个接口持续出现504,重点查慢调用链。这些步骤不复杂,但很多人一看到5xx就直接往应用日志冲,忽略了“先分清是网关层还是应用层”这个前提。6. 从“会分类”到“会用码”:给团队一套能直接落地的规范6.1 最小够用集:别让团队背全表HTTP状态码全家有一百多个,正经项目用不到一半。我建议每个团队维护一张“最小够用集合”,作为接口开发的默认选项:场景推荐状态码说明查询成功200返回资源体创建成功201返回Location删除成功、无body204也可以用200加空body异步任务受理202配合轮询接口请求格式不合法400或细分到422业务规则不满足422语义正确但校验不过未认证401带认证方案说明无权限403或按安全需求伪装404路径/资源不存在404注意区分软404方法不对405带Allow头冲突/版本不一致409提示用户刷新请求体过大413上传场景常用频率超限429带Retry-After服务端兜底500不暴露堆栈服务不可用503带Retry-After网关超时504优先查上游耗时这只是一个起点,团队可以按业务注入自己的私有码,但一定要写进接口文档,不能只存在某位老开发员的脑子里。6.2 前端统一拦截:别在每个页面里散装处理前端拿到状态码后,最佳做法是在请求库的统一拦截层做分类处理,而不是每个页面复制粘贴一段错误弹窗。大致逻辑是:// 伪代码:统一拦截HTTP状态码 http.interceptors.response.use( res => res, err => { const status = err.response && err.response.status if (status >= 500) { // 服务端异常:统一提示 + 上报监控,避免用户反复点击 reportError(err) toast('服务暂时不可用,请稍后重试') } else if (status === 401) { // 未认证:清理登录态,跳转登录页 redirectToLogin() } else if (status === 403) { toast('当前账号没有权限') } else if (status === 429) { // 限流:读取Retry-After,做退避,不建议无脑重试 const retryAfter = err.response.headers['retry-after'] scheduleRetry(retryAfter) toast('请求太频繁,稍后再试') } else if (status === 422) { // 业务校验:把body里的字段错误展示到表单 showFieldErrors(err.response.data) } else { toast('请求失败,请检查网络') } return Promise.reject(err) } )这里的重点是“分类处理”,不是每个码写一个分支。5xx统一提示并上报,401跳登录,422回显校验错误,429退避重试,剩下的兜底提示。前后端一起把这个清单定下来,联调会顺畅很多。6.3 监控与文档:状态码的价值在于可统计状态码除了给单个请求定性,更大的价值是给整个系统做统计。5xx比例异常攀升,通常意味着发布事故或依赖故障;某个接口4xx数量突然暴涨,可能是前端参数格式改坏了,也可能是被恶意扫描。运维和开发团队应该对状态码做分类告警:5xx按错误码和接口维度告警,429按限流触发次数告警,401异常增多要考虑账号被批量尝试登录的可能。写接口文档时,每个接口除了“成功返回体”,一定要加一节“错误响应码”,把可能出现的码和对应场景列清楚。我第一次带项目时忽略了这件事,前端集成全靠猜,上线前临时补文档,结果遗漏了两个隐藏错误码,出了线上问题。从那以后,状态码清单成了接口评审的必查项。6.4 两个能救急的实操小技巧最后分享两个我在实际工作中经常用的小技巧。第一个是快速批量检查一批URL各自返回什么码,直接用curl的输出模板:curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/health把URL换成你要探测的地址,一秒就能看到状态码。批量循环时配合这个-w参数,写监控脚本也很好用。第二个是验证缓存相关状态码比如304时,浏览器和本地缓存会干扰判断,测试时可以在URL后加一个随机query参数来绕过缓存,例如https://api.example.com/resource?ts=1234。这样你看到的才是服务端真正的返回行为,而不是缓存命中后的结果。我也背不全那一百多个HTTP响应码,但我习惯把“首位定方向、后两位定动作、私有码按网关文档查”这三句话放在心里。每次遇到拿不准的响应码,先看第一位数字,排查范围立刻缩小一半,剩下的交给日志和监控。这就是状态码分类汇总这件事最值钱的地方。