将头发送到客户端后,NodeJS无法设置头

时间:2019-01-13 17:08:37

标签: node.js mongodb

编辑:谢谢大家的帮助。我最终使用异步等待完全重写了代码,以提高可读性。该问题不再存在。

当我尝试在/ cart上查看网站的电子商务购物车时,出现以下错误:

Example app listening on port 8080
/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongodb/lib/utils.js:132
      throw err;
      ^

    Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
        at validateHeader (_http_outgoing.js:500:11)
        at ServerResponse.setHeader (_http_outgoing.js:507:3)
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/controllers/cart.js:99:9
        at Function.<anonymous> (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:3928:16)
        at parallel (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:2078:12)
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/parallel.js:35:9
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/once.js:12:16
        at iteratorCallback (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/eachOf.js:52:13)
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/onlyOnce.js:12:16
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/parallel.js:32:13
        at apply (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/lodash/_apply.js:15:25)
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/lodash/_overRest.js:32:12
        at callbackWrapper (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:2047:11)
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:3928:16
        at model.$__save.error (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:343:7)
        at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/kareem/index.js:297:21

此错误似乎是由以下行引起的:

Cart.create({token: token, discount: null}, function(err, cart) {
    if (err || !cart) throw err;
    res.setHeader('Set-Cookie', cookie.serialize("cart_token", token, {
      path: "/",
      maxAge: 60 * 60 * 24 * 7 // 1 week
    }));

在研究了一些类似的问题之后,我相当确定上面的几行与这些冲突:

res.render("cart/index", {
   cartItems: cartItems,
   products: products,
   cartCount: cartCount,
   discount: discount
});

但是,我不确定如何解决此问题,并且可能是错误的。

这是我的完整购物车代码:

module.exports = function(app){

// Dependencies and imported functions
const async = require('async');
const ObjectId = require('mongoose').Types.ObjectId;
const validate = require('../modules/validate');
const paypal = require('paypal-rest-sdk');
const cookie = require('cookie');
const path = require('path');
const appDir = path.dirname(require.main.filename);
const cartMod = require("../modules/cart");
const cartCount = cartMod.itemCount;
const crypto = require("crypto");

// DB
const Cart = require('../models/carts');
const CartItem = require('../models/cart_items');
const Discount = require('../models/discounts');
const Product = require('../models/products');

paypal.configure({
  "host" : "api.sandbox.paypal.com",
  "port" : "",
  'mode': 'sandbox', //sandbox or live
  'client_id': process.env.JSC_PAYPAL_CLIENT_ID,
  'client_secret': process.env.JSC_PAYPAL_CLIENT_SECRET
});

const payPalPayment = (items, total) => {
  return {
    "intent": "sale",
    "payer": {
        "payment_method": "paypal"
    },
    "redirect_urls": {
        "return_url": appDir + "/success",
        "cancel_url": appDir + "/cancel"
    },
    "transactions": [{
        "item_list": { "items": items },
        "amount": {
            "currency": "USD",
            "total": total
        },
        "description": "This is the payment description."
    }]
  };
};

const createPayPalPayment = (payment, req, res) => {
  paypal.payment.create(payment, function(err, payment) {
    if (err) {
      throw err;
    } else {
      if(payment.payer.payment_method === 'paypal') {
        req.paymentId = payment.id;
        var redirectUrl;
        for(var i = 0; i < payment.links.length; i++) {
          var link = payment.links[i];
          if (link.method === 'REDIRECT') {
            redirectUrl = link.href;
          }
        }
        res.redirect(redirectUrl);
      }
    }
  });
};

const displayCartItems = (res, cartId, cartCount, discount) => {
  CartItem.find({cart: cartId}, function(err, cartItems) {
    if (err) return next(err);
    var products = [];
    async.eachSeries(cartItems,
      function(cartItem, next) {
        Product.findById(cartItem.product, function(err, product) {
          if (err) throw err;
          products.push(product);
          return next();
        });
      },
      function(err) {
        if (err) res.status(400).send("Could not display cart items");
        res.render("cart/index", {
          cartItems: cartItems,
          products: products,
          cartCount: cartCount,
          discount: discount
        });
      }
    );
  });
};

const createCart = (req, res, next) => {
  var token = crypto.randomBytes(20).toString("hex");
  Cart.create({token: token, discount: null}, function(err, cart) {
    if (err || !cart) throw err;
    res.setHeader('Set-Cookie', cookie.serialize("cart_token", token, {
      path: "/",
      maxAge: 60 * 60 * 24 * 7 // 1 week
    }));
    return next();
  });
};

const checkCart = (req, res, next) => {
  var token = req.cookies["cart_token"];
  if (!token) {
    createCart(res, res, next);
  } else {
    Cart.find({token: token}, function(err, cart) {
      if (err || !cart) createCart(res, res, next);
      return next();
    });
  }
};

const addCartItem = (cartId, productId, quantity) => {
  CartItem.findOne(
    {'product': productId, "cart": cartId},
    function(err, item) {
      if (err || !item) {
        CartItem.create({
          cart: cartId,
          product: productId,
          quantity: quantity
        });
      } else {
        var newQuantity = item.quantity + parseInt(quantity);
        CartItem.update(
          {'_id': item._id},
          {quantity: newQuantity},
          function(err, item) {});
      }
    }
  );
};

const updateCartItem = (res, id, quantity) => {
  CartItem.findById(id, function(err, item) {
    if (err || !item) throw err;
    CartItem.update({_id: id}, {quantity: quantity}, function(err, item) {
      res.send("Successfully updated cart item quantity.");
    });
  });
};

const deleteCartItem = (res, id) => {
  CartItem.findById(id, function(err, item) {
    if (err || !item) throw err;
    CartItem.remove({_id: id}, function(err, item) {
      res.send("Successfully deleted cart item.");
    });
  });
};

const cartIndex = (req, res, cartCount) => {
  var token = req.cookies["cart_token"];
  Cart.findOne({token: token}, function(err, cart) {
    if (err || !cart) throw err;
    if (cart.discount) {
      Discount.findById(cart.discount, function(err, discount) {
        if (err || !discount) throw err;
        displayCartItems(res, cart._id, discount.percent, cartCount);
      });
    } else {
      displayCartItems(res, cart._id, 0, cartCount);
    }
  });
};

app.get("/discount", function (req, res) {
  cartCount(req, res, function(req, res, cartCount) {
    res.render("discount/index", {cartCount: cartCount});
  });
});

app.post("/discount", checkCart, function (req, res) {
  var code = req.body["code"];
  Discount.findOne({code: code}, function(err, discount) {
    if (err || !discount) {
      cartCount(req, res, function(req, res, cartCount) {
        res.render("discount/index", {err: true, cartCount: cartCount});
      });
    } else {
      var cartToken = req.cookies["cart_token"];
      Cart.findOne({token: cartToken}, function(err, cart) {
        if (err || !cart) throw err;
        Cart.update(
          {_id: cart._id},
          {discount: discount._id},
          function(err, cart) {
            cartCount(req, res, function(req, res, cartCount) {
              res.render("discount/index", {err: false, cartCount: cartCount});
            });
          }
        );
      });
    }
  });
});

app.get('/cart', checkCart, function (req, res, next) {
  cartCount(req, res, cartIndex);
});

app.post('/cart', checkCart, function (req, res, next) {
  var token = req.cookies["cart_token"];
  Cart.findOne({token: token}, function(err, cart) {
    if (err || !cart) return next(err);
    CartItem.find({cart: cart._id}, function(err, cartItems) {
      if (err) return next(err);
      var purchases = [];
      var total = 0;
      async.eachSeries(cartItems,
        function(cartItem, next) {
          Product.findById(cartItem.product, function(err, product) {
            if (err) {
              res.status(400).send("Could not find products");
            } else {
              var purchase = {};
              purchase["name"] = product.name;
              purchase["sku"] = product.name;
              purchase["price"] = product.price;
              purchase["currency"] = "USD";
              purchase["quantity"] = cartItem.quantity;
              purchases.push(purchase);
              total += product.price * cartItem.quantity;
            }
            return next();
          });
        },
        function(err) {
          if (err) {
            res.status(400).send("Could not display cart items");
          } else {
            if (cart.discount) {
              Discount.findById(cart.discount, function(err, discount) {
                if (err || !discount) throw err;
                var discountItem = {};
                discountItem["name"] = "Discount";
                discountItem["sku"] = "Discount";
                discountItem["price"] = -1 * (total * discount.percent / 100);
                discountItem["currency"] = "USD";
                discountItem["quantity"] = 1;
                purchases.push(discountItem);
                total += discountItem.price;
                var payment = payPalPayment(purchases, total);
                createPayPalPayment(payment, req, res);
              });
            } else {
              var payment = payPalPayment(purchases, total);
              createPayPalPayment(payment, req, res);
            }
          }
        }
      );
    });
  });
});

app.post('/cart/confirm', checkCart, function (req, res, next) {
  var cartToken = req.cookies["cart_token"];
  Cart.find({token: cartToken}, function(err, cart) {
    if (err || !cart) return next(err);
    else {
      CartItem.find({cart: cart._id}, function(err, cartItems) {
        if (err) return next(err);
        var products = [];
        async.eachSeries(cartItems,
          function(cartItem, next) {
            Product.findById(cartItem.product, function(err, product) {
              if (err) {
                res.status(400).send("Could not find products");
              } else {
                products.push(product);
              }
              return next();
            });
          },
          function(err) {
            if (err) {
              res.status(400).send("Could not display cart items");
            } else {
              executePayPalPayment(req);
              res.render("cart/index", {
                cartItems: cartItems,
                products: products
              });
            }
          }
        );
      });
    }
  });
});

app.post('/cart/add/:id', checkCart, function (req, res, next) {
  var cartToken = req.cookies["cart_token"];
  var productId = req.params.id;
  var quantity = validate.sanitize(req.body[`quantity`]);
  if (validate.isInt(quantity)) {
    Product.findById(productId, function(err, product) {
      if (err || !product) throw err;
      Cart.findOne({token: cartToken}, function(err, cart) {
        if (err || !cart) throw err;
        addCartItem(cart._id, product._id, quantity);
        res.send("Product added!")
      });
    });
  }
});

app.post('/cart/update/:id', checkCart, function (req, res, next) {
  var id = req.params.id;
  var quantity = req.body["quantity"];
  if (!validate.isInt(quantity)) throw err;
  else if (quantity < 1) {
    deleteCartItem(res, id);
  } else {
    updateCartItem(res, id, quantity);
  }
});

app.post('/cart/delete/:id', checkCart, function (req, res, next) {
  var id = req.params.id;
  deleteCartItem(res, id);
});

}






const createCart = (req, res, next) => {
  var token = crypto.randomBytes(20).toString("hex");
  Cart.create({token: token, discount: null}, function(err, cart) {
    if (err || !cart) throw err;
    res.setHeader('Set-Cookie', cookie.serialize("cart_token", token, {
      path: "/",
      maxAge: 60 * 60 * 24 * 7 // 1 week
    }));
    return next();
  });
};

    const cartIndex = (req, res, cartCount) => {
      var token = req.cookies["cart_token"];
      Cart.findOne({token: token}, function(err, cart) {
        if (err || !cart) throw err;
        if (cart.discount) {
          Discount.findById(cart.discount, function(err, discount) {
            if (err || !discount) throw err;
            displayCartItems(res, cart._id, discount.percent, cartCount);
          });
        } else {
          displayCartItems(res, cart._id, 0, cartCount);
        }
      });
    };

    app.get('/cart', checkCart, function (req, res, next) {
      cartCount(req, res, cartIndex);
    });

    }

本质上,我试图检查用户是否有任何cookie可以识别他们的购物车,如果没有,则创建一个新的cookie /购物车。另外,我调用cartCount在每个页面的顶部显示购物车项目的数量。

我非常感谢您的帮助!请让我知道是否需要更多信息!

编辑:

完整的app.js:

// Dependencies
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const mongoose = require("mongoose");
const path = require("path");
const pug = require("pug");
const session = require("express-session");
const validate = require("./modules/validate");
const cartMod = require("./modules/cart");
const cartCount = cartMod.itemCount;
const port = process.env.PORT || 8080;

// DB
const mongoURI = 'mongodb://localhost/db';
mongoose.connect(process.env.MONGODB_URI || mongoURI);
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function() {
  // Connected!
});

app.set('trust proxy', 1)

app.set("views", __dirname + "/views");

app.set("view engine", "pug");

app.use(bodyParser.urlencoded({ extended: false }));

app.use(cookieParser());

app.use(express.static(__dirname + "/assets"));

app.use(session({
  secret: process.env.JSC_SESSIONS_SECRET,
  resave: true,
  saveUninitialized: false
}));

// Routes
require("./controllers/cart")(app);
require("./controllers/categories")(app);
require("./controllers/discount")(app);
require("./controllers/nav_pages")(app);
require("./controllers/pages")(app);
require("./controllers/products")(app);
require("./controllers/users")(app);

app.use(function(req, res, next) {
  res.status(404);
  // respond with html page
  if (req.accepts('html')) {
    cartCount(req, res, function(req, res, cartCount) {
      res.render("errors/404", { cartCount: cartCount, url: req.url });
      return;
    });
  }
});

app.listen(port, function() {
  console.log("Example app listening on port " + port)
});

2 个答案:

答案 0 :(得分:0)

此错误通常意味着您调用了res.send()res.end()或任何将响应发送到前端的方法。这些方法只能被调用一次,并且在调用它们之后,您将不再与该响应进行交互(例如,如果您之前未调用res.setHeader,则无法调用res.render

此代码:

        async.eachSeries(cartItems,
          function(cartItem, next) {
            Product.findById(cartItem.product, function(err, product) {
              if (err) throw err;
              products.push(product);
              return next();
            });
          },
          function(err) {
            if (err) res.status(400).send("Could not display cart items");
            res.render("cart/index", {
              cartItems: cartItems,
              products: products,
              cartCount: cartCount,
              discount: discount
            });
          }
        );

如果出现错误(并且触发了res.render),则您不应再将信息发送给客户端(因为您已经这样做了)。似乎其中一项购物车商品失败,但是您仍然继续与res对象进行互动。

要解决此问题,请避免在将数据发送到客户端后调用res

答案 1 :(得分:0)

如果return中不存在err或购物车,则添加checkCart

if (err || !cart) return createCart(res, res, next);
      return next();