You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MERN栈出现ERR_HTTP_HEADERS_SENT错误,请求处理异常求助

MERN栈登录/登出/数据获取失效问题排查与修复

我是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的本质是服务器试图在已经向客户端发送响应后,再次设置响应头或发送响应。你的代码里有多处没有在返回错误响应后终止函数执行,导致后续代码继续执行并发送了第二次响应:

  1. getUser函数:当!user时,发送404响应但未用return终止,后续的200响应仍会执行
  2. verifyToken函数:无token时发送404响应但未终止,后续next()会触发下一个路由/中间件再次发送响应
  3. 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;

额外配置建议

  1. 安装并配置cookie-parser:
    npm install cookie-parser
    
    在Express入口文件中添加:
    const cookieParser = require('cookie-parser');
    app.use(cookieParser());
    
  2. 配置CORS支持跨域:
    npm install cors
    
    配置:
    const cors = require('cors');
    app.use(cors({
      origin: ['http://localhost:3000'], // 替换为你的前端地址
      credentials: true, // 允许携带Cookie
    }));
    
  3. 前端请求携带凭证:
    以axios为例,需设置withCredentials: true:
    axios.get('/api/user', { withCredentials: true });
    

内容的提问来源于stack exchange,提问作者abdul wahab nawaz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 19:15:12