500 是「服务端处理请求时出了意外」。责任在服务端,客户端原样重试通常没用。
上一节是客户端的错,这一节是服务端的错。 两类错的处理方向完全相反,这也是「第一位定责任方」那条规矩的价值。
一、500 的边界
500 是个兜底码:任何没被更具体的码覆盖的服务端异常,都可以回 500。
空指针、数据库连接断了、配置写错、序列化失败——统统 500。
正因为它是兜底,看到 500 等于「什么都不知道」。
所以好实践是尽量用更具体的码:502 Bad Gateway(上游挂了)、504 Gateway Timeout(上游超时)、
507 Insufficient Storage。
这也解释了为什么监控里 500 的比例是最粗的指标——它只告诉你「有异常」,不告诉你「哪儿异常」。
二、响应里绝不能放什么
堆栈、SQL 语句、文件路径、框架版本、内网 IP、环境变量。
这些在开发环境里很方便,在生产环境就是免费的情报。
攻击者从堆栈里能看出:用的什么框架和版本(对应已知漏洞)、代码部署在哪(对应路径遍历)、
SQL 怎么拼的(对应注入)、连的哪个内网库(对应横向移动)。
正确做法:响应给一句通用提示加一个 trace id,详情只写日志,并且让这个 id 能对上日志。
Demo 里服务端故意触发一个 1/0,回的是 internal error 而不是堆栈。
三、排查 500 的顺序
① 先看日志,别看代码。500 一定有异常栈,堆栈第一行往往就指明了位置。 ② 看是不是新出现的。突然涨说明有变更(发版、配置、依赖升级);一直有说明是常态化的坑。 ③ 看是不是集中在某个接口或某个参数。这能立刻区分「全局故障」和「特定输入」。 ④ 看上游。如果 500 是网关回的,实际可能是后端崩了,这时网关日志和应用日志都要看。
四、动手 Demo:故意崩一次,看响应里留下了什么
服务端对 /boom 触发一个 ZeroDivisionError,捕获后回 500;对 /ok 正常回 200。
观察响应正文里有没有泄漏细节。
if path == u'/boom':
try:
1 / 0
except ZeroDivisionError:
return (u'HTTP/1.1 500 Internal Server Error',
[(u'Content-Type', u'text/plain')],
b'internal error')
实跑输出:
--------------------------------------------------------------------
500 Internal Server Error:服务端自己崩了
--------------------------------------------------------------------
/ok HTTP/1.1 200 OK,正文「ok」
/boom HTTP/1.1 500 Internal Server Error,正文「internal error」
500 的含义 服务器处理请求时出了意外,不是请求的错
该谁修 服务端。客户端重试通常也没用(除非是偶发)
响应里能放什么 **绝不能放堆栈、SQL、文件路径**
排查靠什么 一个能对上日志的 trace id,而不是把这些写进响应
把堆栈回给客户端是常见的新手错误,它的本质是信息泄露——
攻击者从堆栈里能看出框架版本、目录结构、SQL 结构,直接拿去构造注入。
500 的正确姿势:响应给一句通用提示 + 一个 id,详情只写日志。
/ok 200 OK,正文「ok」、
/boom 500 Internal Server Error,正文「internal error」——正文里只有一句通用提示,
没有任何堆栈信息。
这就是「该返回什么」的示范。
真实框架(Django、Spring、Express)在生产模式下也是这样,
只有 debug 模式才吐堆栈——上线忘了关 debug 是经典事故。
排查靠什么 一个能对上日志的 trace id——这句是本节最实用的一条。
没有 trace id,用户报「我这儿报错了」你连是哪个请求都找不到。
30 秒实验
- 把捕获去掉,让异常直接抛出,看
http.server自己会回什么(会回 500 加一段 HTML, 里面带异常摘要——这就是不该上线的样子)。 - 给响应加一个
X-Trace-Id首部,想想它和日志怎么对应。 - 想一想:500 和 502 的界限在哪?(502 是「上游给的响应不合法」)
常见误区
- 「500 是客户端的问题」——正好相反,5XX 是服务端的锅。
- 「500 带堆栈方便排查」——方便的是攻击者。
- 「所有异常都该回 500」——能用更具体的码就用更具体的,500 是兜底不是首选。
自测三问(点开对答案)
1. 500 的定位是什么? —— 服务端未捕获异常的总兜底码,看到它基本等于「什么都不知道」。 2. 为什么不能在响应里放堆栈? —— 会泄露框架版本、目录结构、SQL 写法等情报,直接可用于构造攻击。 3. 排查 500 的第一步是什么? —— 看日志里的异常栈,而不是先读代码。500 是「我崩了」。最后一节看「我暂时腾不出手」。
- ☐ (本节完,下一节 4.5.2 503 Service Unavailable)