为何出现Cannot set headers错误及PDF加载失败?
点击链接后PDF加载失败,提示「Failed to load PDF document」,同时服务器日志出现「Cannot set headers after they are sent to the client」错误。相关代码如下:
Order.findById(orderId) .then((order) => { if (!order) { return next(new Error("No order found.")); } if (order.user.userId.toString() !== req.user._id.toString()) { return next(new Error("unAuthorized!")); } const invoiceName = "invoice-" + orderId + ".pdf"; const invoicePath = path.join("data", "invoices", invoiceName); const pdfDoc = new PDFDocument(); res.setHeader("Content-Type", "application/pdf"); res.setHeader( "Content-Disposition", 'inline; filename="' + invoiceName + '"' ); pdfDoc.pipe(fs.createWriteStream(invoicePath)); pdfDoc.pipe(res); pdfDoc.fontSize(26).text("Invoice", { underline: true, }); pdfDoc.text("------------------------"); let totalPrice = 0; order.products.forEach((prod) => { totalPrice = totalPrice.prod.quantity * prod.product.price; pdfDoc.text( prod.product.title + " - " + prod.quantity + " x " + " $ " + prod.product.price ); }); pdfDoc.text("Total Price: $" + totalPrice); pdfDoc.end();
错误原因解析
1. 未捕获的异常触发响应头冲突
Order.findById()的Promise链没有配置catch块,一旦数据库查询、PDF生成过程中抛出异常(比如数据库连接失败、代码逻辑错误),Express的错误处理中间件会被触发。但此时代码已经通过pdfDoc.pipe(res)开始向客户端发送PDF内容,响应头已经发送完毕。错误处理中间件试图设置错误响应的头信息时,就会触发「Cannot set headers after they are sent to the client」错误,同时导致PDF内容中断,客户端加载不完整的PDF文件失败。
2. totalPrice计算存在语法错误
代码中totalPrice = totalPrice.prod.quantity * prod.product.price;是明显的逻辑错误:totalPrice是数字类型,不存在prod属性。这会直接抛出TypeError,触发上述的响应头冲突问题,中断PDF生成流程,最终客户端拿到残缺的PDF数据,无法正常加载。
3. 错误分支的响应时序问题
虽然代码中通过next(new Error())处理订单不存在、无权限的情况,但如果这些错误在res.setHeader甚至pdfDoc.pipe(res)之后才被触发(比如异步逻辑的时序偏差),此时响应头已经发送,错误处理中间件无法再设置新的响应头,同样会触发该错误。
内容的提问来源于stack exchange,提问作者SemiusBlack

