调用res.status().json()触发ERR_HTTP_HEADERS_SENT错误的原因是什么?
问题根因
这个ERR_HTTP_HEADERS_SENT报错没有特殊逻辑,本质就是同一个请求你发了不止一次HTTP响应。HTTP协议规定一个请求只能对应一个响应,第一次响应发出去之后响应头就已经写入TCP流发给客户端了,后面再尝试修改响应头、写入响应体就会抛出这个错误。
结合你提到的「正确用户名+错误密码必现,正确密码时接口正常」的复现条件,问题出在argon2.verify的调用逻辑上:
你默认verify只会返回true/false两种结果,但实际上只要传入的第一个参数不是合法的argon2哈希(比如哈希是bcrypt等其他算法生成的、哈希串被截断修改、生成哈希时用的argon2版本和当前项目安装的版本不兼容),verify根本不会返回布尔值,而是直接抛出异常。
Express 4.x 默认无法捕获async路由中抛出的异步异常,你又没写try/catch捕获这个错误,异常抛出后,要么是框架默认逻辑先给客户端返回了500错误响应,要么是你自己加的全局异常监听先返回了错误,这时候第一次响应已经发送完成,后续失控的流程又走到你写的401响应逻辑,尝试第二次发送响应就直接触发崩溃。
另外你代码里虽然写了return,但没有和异常处理逻辑配合,只要异步逻辑抛错,return根本执行不到,流程会直接穿透。
修复方法
- 给
verify逻辑加异常捕获,所有返回响应的地方都直接写成return res.xxx的形式,从写法上杜绝流程穿透。 - 检查
users.json里存储的password_hash是否为合法的argon2格式,确认哈希生成环境和当前运行环境的argon2版本兼容。
修复后的核心代码:
router.post("/", async (req, res) => { const { phone_number, password } = req.body; const user = users.find((user) => user.phone_number === phone_number); if (!user) { return res.status(401).json({ message: "Invalid username/password" }); } let isPasswordValid = false; try { isPasswordValid = await verify(user.password_hash, password); } catch (verifyErr) { console.error("密码校验异常:", verifyErr); return res.status(500).json({ message: "服务内部错误" }); } if (!isPasswordValid) { return res.status(401).json({ message: "Invalid username/password" }); } const { id } = user; sign({ id }, key, { expiresIn }, (err, token) => { if (err) { return res.status(500).json({ message: "Something went wrong" }); } return res.status(200).json({ token }); }); });
后续写async路由建议统一用包装函数处理异步异常,不要裸写async路由,不然很容易出现这种流程失控的问题。
内容的提问来源于stack exchange,提问作者ckl
相关产品推荐
相关产品推荐

