返回res.send后代码仍执行引发ERR_HTTP_HEADERS_SENT错误的技术咨询
我来帮你搞定这个问题!你遇到的Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client是Node.js/Express开发里非常常见的问题,核心原因很明确:当checkAuthorization函数返回响应(比如res.status(401).send(...))后,它只是终止了自身的执行,但findAll函数里的代码并没有停止,仍然会继续调用db.User.findAll,最终在数据库操作完成后再次发送响应,导致重复设置HTTP头。
问题根源分析
在你的findAll函数中,调用checkAuthorization(req, res, 'Customer')后,不管这个函数内部有没有发送响应,后续的db.User.findAll(...)都会被执行。比如当用户未授权时,checkAuthorization已经发送了401响应,但findAll还是会继续查数据库,查完后又调用res.send(...),这就触发了“不能在响应发送后再设置头”的错误。
解决方案
这里提供两种常用的解决思路,推荐第二种更符合Express最佳实践:
方案一:让权限校验函数返回状态,在主函数中判断终止
修改checkAuthorization,让它返回一个布尔值表示校验是否通过,然后在findAll中如果校验不通过就直接return,终止后续代码执行:
// 修改后的checkAuthorization const checkAuthorization = (req, res, user_type) => { // 检查user_type参数是否为空 if (user_type == null) { res.status(500).send('`user_type` parameter is required'); return false; } // 检查user_type参数值是否合法 const validuser_type = user_type === 'Customer' || user_type === 'Resto_Admin' || user_type === 'Admin'; // 验证user_type参数 if (!validuser_type) { res.status(500).send('The value for `user_type` parameter is invalid'); return false; } // 检查用户是否未授权 if (!(req.user != null && req.user.user_type === user_type)) { res.status(401).send('Oops! You are unauthorized to view your request'); return false; } // 校验通过返回true return true; };
然后更新findAll函数:
const db = require('../models'); exports.findAll = (req, res) => { // 调用校验函数,判断是否通过 const isAuthorized = checkAuthorization(req, res, 'Customer'); if (!isAuthorized) { // 校验不通过,直接终止函数执行 return; } db.User.findAll({ include: ['created', 'restaurant'] }) .then((data) => { res.send({ error: false, data: data, message: ['Retrieved successfully.'], }); }) .catch((err) => { console.log(err); res.status(500).send({ error: true, data: [], message: err.errors.map((e) => e.message), }); }); };
方案二:使用Express中间件(推荐)
Express的中间件机制天生适合做权限校验这类前置逻辑,中间件如果不调用next(),后续的路由处理函数就不会执行,从根源上避免了后续代码运行的问题:
首先把checkAuthorization改成中间件工厂函数(可以动态传入user_type):
const checkAuthorization = (user_type) => { return (req, res, next) => { // 检查user_type参数是否为空 if (user_type == null) { return res.status(500).send('`user_type` parameter is required'); } // 检查user_type参数值是否合法 const validuser_type = user_type === 'Customer' || user_type === 'Resto_Admin' || user_type === 'Admin'; // 验证user_type参数 if (!validuser_type) { return res.status(500).send('The value for `user_type` parameter is invalid'); } // 检查用户是否未授权 if (!(req.user != null && req.user.user_type === user_type)) { return res.status(401).send('Oops! You are unauthorized to view your request'); } // 校验通过,调用next()让后续路由处理函数执行 next(); }; };
然后在你的路由配置中使用这个中间件(而不是在findAll内部调用):
// 假设这是你的路由文件 const express = require('express'); const router = express.Router(); const userController = require('../controllers/userController'); const checkAuthorization = require('../middleware/checkAuthorization'); // 给/users接口添加权限校验中间件 router.get('/users', checkAuthorization('Customer'), userController.findAll); module.exports = router;
这样findAll函数就可以简化,不需要再处理权限校验逻辑:
const db = require('../models'); exports.findAll = (req, res) => { db.User.findAll({ include: ['created', 'restaurant'] }) .then((data) => { res.send({ error: false, data: data, message: ['Retrieved successfully.'], }); }) .catch((err) => { console.log(err); res.status(500).send({ error: true, data: [], message: err.errors.map((e) => e.message), }); }); };
总结
两种方案都能解决问题,但中间件的方式更符合Express的设计理念,代码结构更清晰,也更容易复用权限校验逻辑到其他接口上。
内容的提问来源于stack exchange,提问作者polekstulod

