为什么在这种情况下发送后我无法设置 Header?

Why am I getting can't set Header after they are sent in this case?

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw")   
     res.json(authData)
  }catch(err){     
    res.status(401);
  }
  next()
};

exports.verifyAccessToken = async (accessToken) => {  
  return new Promise((resolve, reject) => {    
    jwt.verify(accessToken, keys.accessTokenSecret, (err, authData) => {          
      if(err){
        console.log(err)
        return reject(createError.Unauthorized()); 
      }
      console.log(authData, "accesstoken")
     return resolve(authData);   
    });
  })
};

exports.verifyRefreshToken = async (refreshToken, res) => {
  return new Promise((resolve, reject) =>{
    //const refreshToken = req.body.refreshToken;
    
    jwt.verify(refreshToken, keys.refreshTokenSecret, (err, authData) =>{
      if (err) { reject(createError.Unauthorized()); return }      
      const userId = authData.userId
      return resolve(userId)
    })
  })
};

exports.logOut = async (req, res) => {
  try{
      console.log("----------- logout")
      const refreshToken = req.cookies["refresh"];     
      if(!refreshToken) {throw createError.BadRequest();}
      const user =  await this.verifyRefreshToken(refreshToken);     
       res.clearCookie("refresh")
       res.clearCookie("access")

      res.sendStatus(204);
      res.redirect("/");

      return res.send()
  }catch(err){   
    console.log(err)
  }
};

**路由文件有类似这样的代码**

app.get('/api/logout', authService.checkTokenMW,authService.logOut)

I have been trying to tackle this error from a while not exactly sure what header is setting itself multiple times

**这里是错误**

**

>     Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
>         at ServerResponse.setHeader (_http_outgoing.js:561:11)
>         at ServerResponse.header (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:771:10)
>         at ServerResponse.append (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:732:15)
>         at ServerResponse.res.cookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:857:8)
>         at ServerResponse.clearCookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:804:15)
>         at exports.logOut (E:\COURSE-WEBSITE\main-backend\wiz_backend\controllers\auth-controller.js:131:13)
>         at processTicksAndRejections (internal/process/task_queues.js:95:5) {
>       code: 'ERR_HTTP_HEADERS_SENT'
>     }

**

问题出在中间件函数内部。一旦检查了 authData,就不需要使用 res.json(authData) 将其发送回响应。因为在发送响应之后,您的 next() 函数无论如何都会被触发。由于 next() 将被调用,您的路由处理程序将尝试发送另一个响应,这是您面临的冲突。同时,在catch块中,需要有一个return语句,这样函数执行就会停止,直到next()

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw")   
    // res.json(authData) // <- remove this line
  }catch(err){     
    return res.status(401); // <- here
  }
  next()
};

您报告的特定错误是由于您的代码试图对给定的传入请求发送多个响应而引起的。每个请求只能发送一个响应。因此,所有代码路径(包括错误代码路径)都必须非常小心,以确保您发送的只是一个响应。

此外,如果您已经发送了响应,请不要调用 next(),因为这将继续路由到其他请求并最终尝试发送一些响应(如果没有其他处理程序匹配则为 404)。

考虑到这些,您的代码有几个地方需要修复。

在你的checkTokenWM中:

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw")   
     res.json(authData)
  }catch(err){     
    res.status(401);
  }
  next()
};

您正在呼叫 res.json(authData),然后还呼叫 next()。但是,如果这样做的目的是成为继续路由的中间件,那么如果令牌通过,则不应在此处发送任何响应。

然后,在 catch 块中,您将设置状态,但不发送响应 - 这在技术上并没有错,但可能不是您想要的。我建议像这样修复这两个问题:

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw");
    // the access token verified so continue routing
    next();
  }catch(err){     
    // token did not verify, send error response
    res.sendStatus(401);
  }
};

在您注销时:

exports.logOut = async (req, res) => {
  try{
      console.log("----------- logout")
      const refreshToken = req.cookies["refresh"];     
      if(!refreshToken) {throw createError.BadRequest();}
      const user =  await this.verifyRefreshToken(refreshToken);     
       res.clearCookie("refresh")
       res.clearCookie("access")

      res.sendStatus(204);
      res.redirect("/");

      return res.send()
  }catch(err){   
    console.log(err)
  }
};

您正在尝试在成功注销时发送三个响应,而在出错时不发送任何响应。

首先,res.sendStatus(204) 设置状态并发送空响应。 res.status(204) 只会设置实际发送响应的未来呼叫的状态,如果那是您想要做的。但是,以这种状态,你不能再做 res.redirect().

不完全清楚您要在这里做什么。如果你想重定向,那么它需要是 3xx 状态,所以你不能使用 204。我假设你想做一个重定向。

我建议改成这样来修复:

exports.logOut = async (req, res) => {
  try{
      console.log("----------- logout")
      const refreshToken = req.cookies["refresh"];     
      if(!refreshToken) {throw createError.BadRequest();}
      const user =  await this.verifyRefreshToken(refreshToken);     
      res.clearCookie("refresh");
      res.clearCookie("access");    
      res.redirect("/");

  }catch(err){   
      // can't call logout, if you weren't logged in
      res.sendStatus(401);
  }
};

仅供参考,如果您未登录,大多数应用程序不会将调用注销视为错误。它们只会清除 cookie 并以任何一种方式重定向到主页,因为它们之前是否登录确实没有问题或不。按照您的方式进行操作的问题是,如果 cookie 以某种方式损坏,那么您的代码不会让用户尝试通过注销并重新登录来清除内容。

所以,我可能会完全跳过令牌检查:

exports.logOut = async (req, res) => {
    res.clearCookie("refresh");
    res.clearCookie("access");    
    res.redirect("/");
};

而且,我也会改变这个:

app.get('/api/logout', authService.checkTokenMW,authService.logOut)

对此:

app.get('/api/logout', authService.logOut);