MERN栈出现ERR_HTTP_HEADERS_SENT错误,请求处理异常求助
我是MERN栈新手,之前跟着YouTube教程写的代码正常运行了4-5天,没做任何改动突然失效,登录、登出和数据获取都用不了。用Postman调用API能得到正常结果,但前端调用就失败,服务器抛出以下错误:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5) at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (E:\Reacct\pos\server\node_modules\express\lib\response.js:794:10)
at ServerResponse.send (E:\Reacct\pos\server\node_modules\express\lib\response.js:174:12)
at ServerResponse.json (E:\Reacct\pos\server\node_modules\express\lib\response.js:278:15)
at getUser (E:\Reacct\pos\server\controller\user-controller.js:86:25)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
[nodemon] app crashed - waiting for file changes before starting...
我怀疑是Cookie或Token相关问题,但理解不深,相关业务代码如下:
const User = require("../model/user"); const bcrypt = require("bcryptjs"); const jwt = require("jsonwebtoken"); const signup = async (req, res, next) => { const { fname, lname, email, password, role, phone } = req.body; let existingUser; try { existingUser = await User.findOne({ email: email }); } catch (err) { console.log(err); } if (existingUser) { return res.status(400).json({ message: "user already exists" }); } const hashedPassword = bcrypt.hashSync(password); const user = new User({ fname, lname, email, password: hashedPassword, phone, role, }); try { await user.save(); } catch (err) { console.log(err); } return res.status(201).json({ message: user }); }; const login = async (req, res, next) => { const { email, password } = req.body; let existingUser; try { existingUser = await User.findOne({ email: email }); } catch (err) { console.log(err); } if (!existingUser) { return res .status(400) .json({ message: "user doesn't exist. Please signup" }); } const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password); if (!isPasswordCorrect) { return res.status(401).json({ message: "invalid email or password" }); } const token = jwt.sign({ id: existingUser._id }, "change1122", { expiresIn: "1h", }); res.cookie(String(existingUser._id), token, { path: "/", expires: new Date(Date.now() + 1000 * 3600), httpOnly: true, sameSite: "lax", }); return res .status(200) .json({ message: "user logged in sucessfully", user: existingUser, token }); }; const verifyToken = (req, res, next) => { const cookies = req.headers.cookie; const token = cookies.split("=")[1]; if (!token) { res.status(404).json({ message: "no token found" }); } jwt.verify(String(token), "change1122", (err, user) => { if (err) { return res.status(404).json({ message: "invalid token" }); } req.id = user.id; }); next(); }; const getUser = async (req, res, next) => { const id = req.id; let user; try { user = await User.findById(id, "-password"); } catch (err) { console.log(err); } if (!user) { res.status(404).json({ message: "user not found with the id" }); } return res.status(200).json({ user }); }; const logout = async (req, res, next) => { const cookies = req.headers.cookie; console.log(cookies); const token = cookies.split("=")[1]; if (!token) { res.status(404).json({ message: "no token found" }); } const user = req.id; res.clearCookie(`${user}`); req.cookies[`${user}`] = ""; return res.status(200).json({ message: "successfully logged out" }); }; exports.signup = signup; exports.login = login; exports.verifyToken = verifyToken; exports.getUser = getUser; exports.logout = logout;
问题分析与修复方案
核心错误原因:重复发送HTTP响应
报错ERR_HTTP_HEADERS_SENT的本质是服务器试图在已经向客户端发送响应后,再次设置响应头或发送响应。你的代码里有多处没有在返回错误响应后终止函数执行,导致后续代码继续执行并发送了第二次响应:
getUser函数:当!user时,发送404响应但未用return终止,后续的200响应仍会执行verifyToken函数:无token时发送404响应但未终止,后续next()会触发下一个路由/中间件再次发送响应logout函数:无token时发送404响应但未终止,后续清除Cookie和200响应仍会执行
额外潜在问题:Cookie解析与跨域(前端调用失败的可能原因)
- 直接用
req.headers.cookie.split("=")[1]解析Cookie的写法非常脆弱,多Cookie或格式变化会直接报错,建议用cookie-parser中间件 - 前端跨域调用失败,需确保后端配置CORS并允许携带凭证,同时Cookie的
sameSite设置要匹配请求模式
修复后的代码
const User = require("../model/user"); const bcrypt = require("bcryptjs"); const jwt = require("jsonwebtoken"); const signup = async (req, res, next) => { const { fname, lname, email, password, role, phone } = req.body; let existingUser; try { existingUser = await User.findOne({ email: email }); } catch (err) { console.log(err); return res.status(500).json({ message: "服务器错误" }); } if (existingUser) { return res.status(400).json({ message: "用户已存在" }); } const hashedPassword = bcrypt.hashSync(password); const user = new User({ fname, lname, email, password: hashedPassword, phone, role, }); try { await user.save(); } catch (err) { console.log(err); return res.status(500).json({ message: "注册失败,请重试" }); } return res.status(201).json({ message: "注册成功", user }); }; const login = async (req, res, next) => { const { email, password } = req.body; let existingUser; try { existingUser = await User.findOne({ email: email }); } catch (err) { console.log(err); return res.status(500).json({ message: "服务器错误" }); } if (!existingUser) { return res.status(400).json({ message: "用户不存在,请先注册" }); } const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password); if (!isPasswordCorrect) { return res.status(401).json({ message: "邮箱或密码错误" }); } const token = jwt.sign({ id: existingUser._id }, "change1122", { expiresIn: "1h", }); // 统一Cookie名称,便于解析 res.cookie("auth_token", token, { path: "/", expires: new Date(Date.now() + 1000 * 3600), httpOnly: true, sameSite: "lax", // 生产环境跨域需设置:sameSite: "none", secure: true(仅HTTPS) }); return res.status(200).json({ message: "登录成功", user: existingUser }); }; const verifyToken = (req, res, next) => { // 使用cookie-parser后直接从req.cookies获取 const token = req.cookies.auth_token; if (!token) { return res.status(401).json({ message: "未找到token,请先登录" }); } jwt.verify(token, "change1122", (err, decoded) => { if (err) { return res.status(401).json({ message: "无效的token" }); } req.id = decoded.id; next(); }); }; const getUser = async (req, res, next) => { const id = req.id; let user; try { user = await User.findById(id, "-password"); } catch (err) { console.log(err); return res.status(500).json({ message: "获取用户信息失败" }); } if (!user) { return res.status(404).json({ message: "未找到该用户" }); } return res.status(200).json({ user }); }; const logout = async (req, res, next) => { const token = req.cookies.auth_token; if (!token) { return res.status(401).json({ message: "未找到token" }); } res.clearCookie("auth_token", { path: "/", sameSite: "lax", // 生产环境跨域需匹配登录时的secure设置 }); return res.status(200).json({ message: "登出成功" }); }; exports.signup = signup; exports.login = login; exports.verifyToken = verifyToken; exports.getUser = getUser; exports.logout = logout;
额外配置建议
- 安装并配置cookie-parser:
在Express入口文件中添加:npm install cookie-parserconst cookieParser = require('cookie-parser'); app.use(cookieParser()); - 配置CORS支持跨域:
配置:npm install corsconst cors = require('cors'); app.use(cors({ origin: ['http://localhost:3000'], // 替换为你的前端地址 credentials: true, // 允许携带Cookie })); - 前端请求携带凭证:
以axios为例,需设置withCredentials: true:axios.get('/api/user', { withCredentials: true });
内容的提问来源于stack exchange,提问作者abdul wahab nawaz

