首页 > 解决方案 > 发送 Email-NodeMailer 时,将标头发送到客户端后无法设置标头

问题描述

我正在尝试创建一个登录/注册,用户必须在继续之前确认他们的电子邮件,尽管我收到了确认电子邮件,并且一切正常,但我的控制台中也出现以下错误:

Cannot set headers after they are sent to the client
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 (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\response.js:771:10)
    at ServerResponse.send (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\response.js:170:12)
    at ServerResponse.json (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\response.js:267:15)
    at errorHandler (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\server\error\errorHandler.js:20:39)    
    at Layer.handle_error (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\layer.js:71:5)
    at trim_prefix (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:315:13)
    at C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:284:7       
    at Function.process_params (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:335:12)
    at next (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:275:10)
    at C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:635:15      
    at next (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:260:14)
    at next (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\route.js:127:14)
    at exports.login (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\server\controllers\authControllers.js:131:5)

当代码如下时,我从我的登录控制器发送这封电子邮件isVerify=false

模型

const crypto = require("crypto");
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");

const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: [true, "Please enter your username"],
  },
  email: {
    type: String,
    required: [true, "Please enter your email"],
    unique: true,
    lowercase: true,
  },
  password: {
    type: String,
    required: [true, "Please enter a  valid password"],
    minlength: 8,
  },
  isAdmin: {
    type: Boolean,
    required: true,
    default: false,
  },
  isVerified: {
    type: Boolean,
    required: true,
    default: false,
  },
  resetPasswordToken: String,
  resetPasswordExpired: Date,
  verifyEmailToken: String,
  verifyEmailExpired: Date,
});

// Hashing Password
UserSchema.pre("save", async function (next) {
  if (!this.isModified("password")) {
    next();
  }

  try {
    const salt = await bcrypt.genSalt(10);
    this.password = await bcrypt.hash(this.password, salt);
    next();
  } catch (error) {
    next(error);
  }
});

// Checking if password entered is correct or not
UserSchema.methods.matchPasswords = async function (password) {
  return await bcrypt.compare(password, this.password);
};

// Converting user data into JSON WEB TOKEN
UserSchema.methods.getSignedJwtAccessToken = function () {
  return jwt.sign({ id: this._id }, process.env.JWT_ACCESS_SECRET, {
    expiresIn: process.env.JWT_ACCESS_EXPIRE,
  });
};

UserSchema.methods.getSignedJwtRefreshToken = function () {
  return jwt.sign({ id: this._id }, process.env.JWT_REFRESH_SECRET, {
    expiresIn: process.env.JWT_REFRESH_EXPIRE,
  });
};

UserSchema.methods.getResetPasswordToken = function () {
  const resetToken = crypto.randomBytes(20).toString("hex");

  // Hash token (private key) and save to database
  this.resetPasswordToken = crypto
    .createHash("sha256")
    .update(resetToken)
    .digest("hex");

  this.resetPasswordExpired = Date.now() + 10 * (60 * 1000); // Ten Minutes

  return resetToken;
};

UserSchema.methods.getVerifyEmailToken = function () {
  const confirmToken = crypto.randomBytes(20).toString("hex");

  // Hash token (private key) and save to database
  this.verifyEmailToken = crypto
    .createHash("sha256")
    .update(confirmToken)
    .digest("hex");

  this.verifyEmailExpired = Date.now() + 10 * (60 * 1000); // Ten Minutes

  return confirmToken;
};

const User = mongoose.model("User", UserSchema);

module.exports = User;

登录控制器

//  @description: Login
//  @route: POST /api/login
//  @access: Public
exports.login = async (req, res, next) => {
  const { email, password } = req.body;

  if (!email || !password) {
    return next(new ErrorResponse("Please enter credentials properly", 400));
  }

  try {
    const user = await User.findOne({ email }).select("+password");

    if (!user) {
      return next(new ErrorResponse("Email not registered", 401));
    }

    const isMatch = await user.matchPasswords(password);
    if (!isMatch) {
      return next(new ErrorResponse("Invalid Password", 401));
    }

    // Sending Email if not Verified
    if (!user.isVerified) {
      try {
        const confirmToken = user.getVerifyEmailToken();
        await user.save();

        const confirmUrl = `http://localhost:3000/confirmation/${confirmToken}`;
        console.log("confirmURL: ", confirmUrl);

        const html = `
      <h1>VERIFY YOUR EMAIL</h1>
      <p>Please verify your Email by clicking on the following link:</p>
      <a href=${confirmUrl} clicktracking=off>${confirmUrl}</a>
    `;

        const message = `Verify your Email. Click on the fllowing link: ${confirmUrl}`;
        try {
          await sendEmail({
            to: user.email,
            subject: "VERIFICATION MAIL",
            text: message,
            html: html,
          });

          res.status(200).json({
            success: true,
            data: "A verification mail is send to your email. Please confirm before login",
          });
        } catch (err) {
          user.verifyEmailToken = undefined;
          user.verifyEmailExpired = undefined;

          await user.save();
          console.log("E^: ", err);
          next(new ErrorResponse("Email could not be sent", 500));
        }
      } catch (error) {
        next(error);
      }
    }

    sendToken(user, 200, res);
  } catch (error) {
    next(error);
  }
};

const sendToken = (user, statusCode, res) => {
  const accessToken = user.getSignedJwtAccessToken();
  const refreshToken = user.getSignedJwtRefreshToken();
  refreshTokens.push(refreshToken);

  res
    .status(statusCode)
    .cookie("accessToken", accessToken, {
      expires: new Date(new Date().getTime() + 30 * 1000),
      sameSite: "strict",
      httpOnly: true,
    }) // Dummie Cookie
    .cookie("authSession", true, {
      expires: new Date(new Date().getTime() + 30 * 1000),
    })
    .cookie("refreshToken", refreshToken, {
      expires: new Date(new Date().getTime() + 31557600000),
      sameSite: "strict",
      httpOnly: true,
    })
    .cookie("refreshTokenID", true, {
      expires: new Date(new Date().getTime() + 31557600000),
    })
    .json({ success: true });
};

发送电子邮件.js

const nodemailer = require("nodemailer");

const sendEmail = (options) => {
  const transporter = nodemailer.createTransport({
    service: process.env.EMAIL_SERVICE,
    auth: {
      user: process.env.EMAIL_USERNAME,
      pass: process.env.EMAIL_PASSWORD,
    },
  });

  const mailOptions = {
    from: process.env.EMAIL_FROM,
    to: options.to,
    subject: options.subject,
    html: options.text,
  };

  transporter.sendMail(mailOptions, function (err, info) {
    if (err) {
      console.log(err);
    } else {
      console.log(info);
    }
  });
};

module.exports = sendEmail;

标签: javascriptnode.jsexpress

解决方案


您已使用此代码设置对客户端的响应。

res.status(200).json({
   success: true,
   data: "A verification mail is send to your email. Please confirm before login",
});

但是您有这种方法sendToken(user, 200, res);,我假设您向客户发送了另一个信息,或者您在该方法中有另一种或res.json()一种。res.send()res.body()

将标头发送给客户端后,您无法再次设置标头。


你可以在你的后面加一个return语句res.status().json(),这样sendToken()就不会被执行了。

或者,也许您可​​以使用自己的方式。

res.status(200).json({
   success: true,
   data: "A verification mail is send to your email. Please confirm before login",
});

return;

推荐阅读