Postman正常的Post接口在React+Axios调用时出现ERR_HTTP_HEADERS_SENT错误
解决ERR_HTTP_HEADERS_SENT错误:Node.js/Express商品上传接口问题
错误原因
ERR_HTTP_HEADERS_SENT错误本质是同一请求多次发送HTTP响应,代码里的问题点:
db.query是异步操作,调用它之后立刻执行了res.status(200).send(...),此时数据库插入逻辑还未完成。- 如果后续数据库查询出错(比如参数格式错误、表不存在等),回调里的
return res.json(err)会尝试再次发送响应,违反HTTP“一请求一响应”的规则,触发错误。 - Postman测试时未触发数据库错误,所以问题没暴露;React Axios调用时可能因参数或环境差异触发了数据库错误,导致重复响应。
修复方案
方案1:改用Promise封装数据库操作(推荐)
将回调式的db.query转为Promise,用async/await控制异步流程,确保所有操作完成后再发送响应:
const addProduct = async (req, res) => { const files = req.files; const urls = []; const precio_final = parseInt(req.body.precio); const product = req.body.product; let q = ''; let values = []; try { // 上传文件到S3 for (const file of files) { const fileStream = fs.createReadStream(file.path); const folderName = product + "/"; const key = folderName + file.originalname; const params = { Bucket: 'pisos-parador-colombia', Key: key, Body: fileStream }; const command = new PutObjectCommand(params); await s3Client.send(command); const fileUrl = `https://pisos-parador-colombia.s3.us-east-2.amazonaws.com/${key}`; urls.push(fileUrl); } // 构建商品插入语句 if (product === "pisos_laminados" || product === "pisos_estructurados") { q = `INSERT INTO ${product} (titulo,precio,descripcion, trafico, grosor, junta , medidas) VALUES (?)`; values = [ req.body.titulo, precio_final, req.body.descripcion, req.body.trafico, req.body.grosor, req.body.junta, req.body.medidas ]; } else { q = `INSERT INTO ${product} (titulo,precio,descripcion) VALUES (?)`; values = [ req.body.titulo, precio_final, req.body.descripcion, ]; } // 封装db.query为Promise const queryPromise = (sql, params) => { return new Promise((resolve, reject) => { db.query(sql, params, (err, data) => { if (err) reject(err); else resolve(data); }); }); }; // 插入商品 const productData = await queryPromise(q, [values]); console.log("product has been created succesfully!"); const laminateFloorId = productData.insertId; // 批量插入图片URL const imageQueries = urls.map(url => { const imageValues = [url, laminateFloorId]; const imageQuery = `INSERT INTO imagenes_${product} (image_url, product_id) VALUES (?, ?)`; return queryPromise(imageQuery, imageValues); }); await Promise.all(imageQueries); console.log('All Image URLs have been stored successfully!'); // 所有操作完成后发送响应 res.status(200).send("everything added succesfully"); } catch (err) { console.error("este es el error" + err.message); // 只发送一次错误响应 res.status(500).send('Failed to upload files'); } }; module.exports = addProduct;
方案2:调整响应位置到回调内部
如果不想改Promise,也可以把res.send放到数据库操作的回调末尾,确保所有异步操作完成后再响应:
const addProduct = async (req, res) => { const files = req.files; const urls = []; const precio_final = parseInt(req.body.precio); const product = req.body.product; let q = ''; let values = []; try { for (const file of files) { const fileStream = fs.createReadStream(file.path); const folderName = product + "/"; const key = folderName + file.originalname; const params = { Bucket: 'pisos-parador-colombia', Key: key, Body: fileStream }; const command = new PutObjectCommand(params); await s3Client.send(command); const fileUrl = `https://pisos-parador-colombia.s3.us-east-2.amazonaws.com/${key}`; urls.push(fileUrl); } if (product === "pisos_laminados" || product === "pisos_estructurados") { q = `INSERT INTO ${product} (titulo,precio,descripcion, trafico, grosor, junta , medidas) VALUES (?)`; values = [ req.body.titulo, precio_final, req.body.descripcion, req.body.trafico, req.body.grosor, req.body.junta, req.body.medidas ]; } else { q = `INSERT INTO ${product} (titulo,precio,descripcion) VALUES (?)`; values = [ req.body.titulo, precio_final, req.body.descripcion, ]; } db.query(q, [values], (err, data) => { if (err) { console.error(err); return res.status(500).json(err); // 统一错误响应 } console.log("product has been created succesfully!"); const laminateFloorId = data.insertId; // 统计图片插入完成数量 let completed = 0; const total = urls.length; if (total === 0) { return res.status(200).send("everything added succesfully"); } for (const url of urls) { const imageValues = [url, laminateFloorId]; const imageQuery = `INSERT INTO imagenes_${product} (image_url, product_id) VALUES (?, ?)`; db.query(imageQuery, imageValues, (err, imageData) => { if (err) console.error(err); else console.log('Image URL has been stored successfully!'); completed++; // 所有图片插入完成后发送响应 if (completed === total) { res.status(200).send("everything added succesfully"); } }); } }); // 移除提前发送的响应 // res.status(200).send("everything added succesfully"); } catch (err) { console.error("este es el error" + err.message); res.status(500).send('Failed to upload files'); } }; module.exports = addProduct;
额外注意事项
- 要确保
product参数的安全性,避免SQL注入(当前直接用${product}拼接SQL存在注入风险,建议用白名单过滤允许的表名)。 - 处理文件上传后的临时文件清理(比如Multer生成的临时文件,上传到S3后要删除,避免占用磁盘空间)。
内容的提问来源于stack exchange,提问作者daniel Monroy
相关产品推荐
相关产品推荐

