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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 11:57:15