生产环境使用Puppeteer生成PDF报504 Gateway Time-out错误如何解决
常见原因及对应解决办法
1. 生产环境缺失Chrome运行依赖
无桌面的Linux服务器默认缺少Chromium运行所需的系统依赖库,本地有图形环境所以可以正常运行,生产环境启动浏览器时会卡住导致超时。
解决方法:安装对应系统的依赖包,以Debian/Ubuntu为例,执行以下命令:
apt update && apt install -y \ gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \ libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \ libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 \ libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation \ libappindicator1 libnss3 lsb-release xdg-utils wget
2. 页面加载规则过于严格导致超时
当前代码使用waitUntil: "networkidle0",要求页面500ms内没有任何网络请求才判定加载完成,如果目标页面存在轮询、长连接、持续上报的埋点请求,会一直达不到判定条件触发超时。
解决方法:调整页面加载配置,放宽判定规则同时设置最大超时时间:
// 修改page.goto配置,最多等待30秒,使用更宽松的networkidle2(允许最多2个活跃请求) await page.goto(url, { waitUntil: "networkidle2", timeout: 30 * 1000 });
3. Puppeteer启动参数缺失
无桌面环境下仅配置--no-sandbox不足以正常启动Chrome,还需要补充其他适配参数:
解决方法:调整launch配置:
const browser = await puppeteer.launch({ args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", // 规避/dev/shm空间不足问题 "--disable-gpu", // 无GPU环境禁用硬件加速 "--no-first-run", "--no-zygote" ] });
4. 生产环境网络连通性异常
生产服务器可能无法正常访问你传入的目标URL,比如DNS解析失败、防火墙限制出站请求、内部域名没有配置host等,导致page.goto一直卡住。
解决方法:登录生产服务器执行curl命令验证连通性:
curl https://apps.socialtools.me/preview/discount-coupon/613e0c6a9115bb003681958d/print/0
如果请求失败,排查服务器DNS、防火墙、路由规则配置。
5. 进程资源不足
如果是Docker/K8s等容器化部署,默认给的内存配额过低(低于1G),Chrome启动和渲染PDF需要占用大量内存,资源不足会触发OOM被系统kill,导致请求无响应。
解决方法:调高进程内存配额,建议至少分配1.5G以上内存。
6. 缺失错误捕获逻辑
当前代码没有异常捕获逻辑,任意步骤报错后既不会关闭浏览器也不会返回响应,会一直挂起直到网关超时。
解决方法:添加try/catch/finally逻辑,优化后的完整代码如下:
const puppeteer = require("puppeteer"); module.exports = { async generate(req, res) { let browser = null; try { let url = req.query.url; if (!url) return res.status(400).send('缺少url参数'); let name = req.query.name ? req.query.name : "file"; browser = await puppeteer.launch({ args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--no-first-run", "--no-zygote" ] }); const page = await browser.newPage(); await page.goto(url, { waitUntil: "networkidle2", timeout: 30 * 1000 }); const pdf = await page.pdf({ format: "A4" }); res.set({ "Content-Type": "application/pdf", "Content-Length": pdf.length, }); res.setHeader("Content-disposition", `attachment; filename=${encodeURIComponent(name)}.pdf`); res.send(pdf); } catch (err) { console.error('PDF生成失败:', err); res.status(500).send('PDF生成失败:' + err.message); } finally { if (browser) await browser.close(); } }, };
注:代码中增加了encodeURIComponent处理文件名,避免中文或特殊字符导致的文件名乱码问题。
内容的提问来源于stack exchange,提问作者sagar

