Express JS添加isAdm中间件后报[ERR_HTTP_HEADERS_SENT]错误如何解决
问题原因
- 中间件重复调用
next():你在isAdm中间件中发起异步SQL查询后,没有阻断后续代码执行,会先执行if (jwtToken != '')块外的next(),第一次触发controller执行;等SQL异步回调返回后,又调用了一次next(true)/next(false),第二次触发controller执行,两次执行都会调用res.send返回响应,直接触发头重复设置的报错。
- 中间件重复调用
next()传参用法错误:Express的next()只有传Error实例时会走错误处理中间件,传其他值不会传递给下游路由处理函数的参数,你controller定义的第三个bool参数根本拿不到中间件传的值,权限判断逻辑本身无效。
- 异常分支没有阻断响应:权限验证失败的场景应该直接在中间件返回401,不需要透传到controller处理。
修复方案
第一步:修改isAdm中间件
const sqladminchecker = require("../db/index"); const JWTTOKEN = require('jsonwebtoken') require('dotenv').config() // 补充缺失的config调用,否则环境变量无法读取 const isAdmin = (req, res, next) => { let jwtToken = '' try { // 兼容cookie不存在的场景,避免split报错 const cookies = req.headers.cookie?.split('; ') || [] for (let i = 0; i < cookies.length; i++) { if (cookies[i].startsWith('jwt')) { jwtToken = cookies[i].split('=')[1] break } } // 无token直接返回401,不向下执行 if (!jwtToken) { return res.status(401).send("Unauthorized") } const jwtData = JWTTOKEN.verify(jwtToken, process.env.JWT_SECRET) const { id } = jwtData sqladminchecker.query( "SELECT * FROM users WHERE id = ? AND isAdmin = ?", [id, 1], (err, reslt) => { // 查询报错/不是管理员直接返回401 if (err || reslt.length !== 1) { return res.status(401).send("Unauthorized") } // 验证通过,将管理员ID挂载到req对象方便后续使用 req.adminId = id // 仅调用一次next放行 next() } ); } catch (e) { // 捕获JWT解析等异常直接返回401 return res.status(401).send("Unauthorized") } } module.exports = isAdmin
第二步:简化controller逻辑,删除无效的bool判断
exports.create = (req, res) => { // Validate request if (!req.body) { return res.status(400).send({ message: "Content can not be empty!" }); } const crypto = require('crypto'); const hash = crypto.createHash('md5').update(req.body.password).digest('hex'); // 创建用户实例 const user = new Users({ username: req.body.username, password: hash, fullname: req.body.fullname }); // 写入数据库 Users.create(user, (err, data) => { if (err) { return res.status(500).send({ message: err.message || "Some error occurred while creating the User." }); } res.send(data); }); };
额外优化建议
- 所有
res.send/res.status调用前建议加return,避免后续逻辑意外执行 - 密码加密不要用MD5,建议使用bcrypt等抗碰撞性更强的加密方案
- 解析Cookie可以使用官方
cookie-parser中间件,无需手动拆分字符串
内容的提问来源于stack exchange,提问作者Fahim Ahmed
相关产品推荐
相关产品推荐

