Express.js结合cookie-parser出现ERR_HTTP_HEADERS_SENT错误的修复
修复Express中间件的ERR_HTTP_HEADERS_SENT错误
问题场景
使用Express.js、jsonwebtoken和cookie-parser实现用户认证,编写了protect中间件:
const protect = catchAsync(async (req, res, next) => { // 1) Getting token and check of it's there let token; if (req.cookies.jwt) { token = req.cookies.jwt; } if (!token) { return next( new AppError('You are not logged in! Please log in to get access.', 401) ); } const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET); const currentUser = await Users.findByPk(decoded.id); if (!currentUser) { return next( new AppError( 'The user belonging to this token does no longer exist.', 401 ) ); } req.user = currentUser.toJSON(); return next(); });
接着编写了restrictTo中间件用于权限控制:
const restrictTo = async (req, res, next) => { const { permissions } = req.user.role; permissions.forEach((element) => { const { method, path, name } = element; if (req.method.toLowerCase() === method.toLowerCase()) { const ok = checkUrl(req.originalUrl, path); if (ok) { console.log('gedo'); return next(); } } }); return next(new AppError('you dont have permission', 403)); };
配置路由:
router.route('/').get(protect, restrictTo, getAllCountry);
接口能正常响应,但控制台抛出错误:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at new NodeError (node:internal/errors:405:5)
错误原因
restrictTo中间件中使用forEach循环遍历权限时,当找到匹配的权限并调用next()后,forEach不会终止循环,后续循环仍会继续执行,最终会走到末尾的return next(new AppError(...)),导致两次调用next方法:一次是允许请求继续到后续处理函数,一次是返回无权限错误。这会让Express尝试两次发送响应头,从而触发ERR_HTTP_HEADERS_SENT错误。
修复方案
将forEach替换为可以终止遍历的逻辑,比如使用for...of循环,找到匹配项后立即终止循环并调用next(),避免后续代码执行:
const restrictTo = async (req, res, next) => { const { permissions } = req.user.role; // 使用for...of循环替代forEach,支持终止遍历 for (const element of permissions) { const { method, path, name } = element; if (req.method.toLowerCase() === method.toLowerCase()) { const ok = checkUrl(req.originalUrl, path); if (ok) { console.log('gedo'); // 找到匹配权限,调用next并终止函数 return next(); } } } // 遍历完所有权限都没匹配到,返回无权限错误 return next(new AppError('you dont have permission', 403)); };
或者使用Array.some()方法,利用其找到匹配项就返回true并终止遍历的特性:
const restrictTo = async (req, res, next) => { const { permissions } = req.user.role; const hasPermission = permissions.some(element => { const { method, path, name } = element; if (req.method.toLowerCase() === method.toLowerCase()) { return checkUrl(req.originalUrl, path); } return false; }); if (hasPermission) { console.log('gedo'); return next(); } return next(new AppError('you dont have permission', 403)); };
这两种方式都能确保只有一次next()调用,避免重复发送响应头。
内容的提问来源于stack exchange,提问作者bego
相关产品推荐
相关产品推荐

