Spaces:
Running
Running
fix: dynamic timeout for Puppeteer PDF generation
Browse filesReplace fixed timeouts with dynamic calculation based on image count
and total image size to prevent TimeoutError on large PDF exports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
server.js
CHANGED
|
@@ -74,6 +74,28 @@ app.post('/api/generate_pdf', async (req, res) => {
|
|
| 74 |
const sizeWaitTime = imgSizeMB > 0 ? Math.min(imgSizeMB * 100, 3000) : 0;
|
| 75 |
const totalWaitTime = Math.min(baseWaitTime + sizeWaitTime, 8000);
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
console.log(`[PDF-GEN] [${getElapsed()}] 正在启动浏览器...`);
|
| 78 |
browser = await puppeteer.launch({
|
| 79 |
executablePath: '/usr/bin/chromium',
|
|
@@ -91,14 +113,16 @@ app.post('/api/generate_pdf', async (req, res) => {
|
|
| 91 |
console.log(`[PDF-GEN] [${getElapsed()}] 浏览器启动成功`);
|
| 92 |
|
| 93 |
const page = await browser.newPage();
|
|
|
|
| 94 |
await page.setViewport({ width: 1200, height: 800 });
|
|
|
|
| 95 |
|
| 96 |
console.log(`[PDF-GEN] [${getElapsed()}] 正在填充页面内容...`);
|
| 97 |
-
await page.setContent(htmlToUse, {
|
| 98 |
waitUntil: ['load', 'networkidle0'],
|
| 99 |
-
timeout:
|
| 100 |
});
|
| 101 |
-
await page.waitForNetworkIdle({ idleTime: 500 });
|
| 102 |
console.log(`[PDF-GEN] [${getElapsed()}] 页面内容加载完成`);
|
| 103 |
|
| 104 |
// 等待 base64 图片完全渲染(检测实际加载状态)
|
|
@@ -158,7 +182,7 @@ app.post('/api/generate_pdf', async (req, res) => {
|
|
| 158 |
});
|
| 159 |
}
|
| 160 |
|
| 161 |
-
return {
|
| 162 |
initial: results,
|
| 163 |
final: finalResults,
|
| 164 |
total: images.length
|
|
@@ -208,7 +232,7 @@ app.post('/api/generate_pdf', async (req, res) => {
|
|
| 208 |
|
| 209 |
// 根据 GitHub Issue #10341:截图可以强制触发渲染流水线
|
| 210 |
console.log(`[PDF-GEN] [${getElapsed()}] 截图强制渲染(Issue #10341 workaround)...`);
|
| 211 |
-
await page.screenshot({ type: 'png' });
|
| 212 |
console.log(`[PDF-GEN] [${getElapsed()}] 截图完成`);
|
| 213 |
} else {
|
| 214 |
console.log(`[PDF-GEN] [${getElapsed()}] 无图片,等待 DOM 稳定...`);
|
|
@@ -216,6 +240,7 @@ app.post('/api/generate_pdf', async (req, res) => {
|
|
| 216 |
}
|
| 217 |
|
| 218 |
console.log(`[PDF-GEN] [${getElapsed()}] 正在生成 PDF 二进制流...`);
|
|
|
|
| 219 |
const pdfBuffer = await page.pdf({
|
| 220 |
format: 'A4',
|
| 221 |
printBackground: true,
|
|
|
|
| 74 |
const sizeWaitTime = imgSizeMB > 0 ? Math.min(imgSizeMB * 100, 3000) : 0;
|
| 75 |
const totalWaitTime = Math.min(baseWaitTime + sizeWaitTime, 8000);
|
| 76 |
|
| 77 |
+
// 动态计算 waitForNetworkIdle 超时上限
|
| 78 |
+
const baseTimeout = 30000;
|
| 79 |
+
let extraMs = 0;
|
| 80 |
+
if (imgCount <= 30) {
|
| 81 |
+
extraMs = imgCount * 3000;
|
| 82 |
+
} else if (imgCount <= 50) {
|
| 83 |
+
extraMs = 90000 + (imgCount - 30) * 10000;
|
| 84 |
+
} else if (imgCount <= 100) {
|
| 85 |
+
extraMs = 290000 + (imgCount - 50) * 7000;
|
| 86 |
+
} else {
|
| 87 |
+
extraMs = 640000 + (imgCount - 100) * 5600;
|
| 88 |
+
}
|
| 89 |
+
const sizeExtraMs = (imgSizeMB || 0) * 3000;
|
| 90 |
+
const networkTimeout = Math.min(baseTimeout + extraMs + sizeExtraMs, 1200000);
|
| 91 |
+
|
| 92 |
+
// 动态计算 setContent 超时上限(setContent 需要解析 HTML + 加载资源)
|
| 93 |
+
const setContentTimeout = Math.min(60000 + extraMs + sizeExtraMs, 600000);
|
| 94 |
+
|
| 95 |
+
// 动态计算截图超时上限
|
| 96 |
+
const screenshotTimeout = Math.min(30000 + (imgCount > 0 ? extraMs / 10 : 0), 120000);
|
| 97 |
+
|
| 98 |
+
console.log(`[PDF-GEN] [${getElapsed()}] 动态超时: setContent=${(setContentTimeout / 1000).toFixed(0)}s, waitForNetworkIdle=${(networkTimeout / 1000).toFixed(0)}s`);
|
| 99 |
console.log(`[PDF-GEN] [${getElapsed()}] 正在启动浏览器...`);
|
| 100 |
browser = await puppeteer.launch({
|
| 101 |
executablePath: '/usr/bin/chromium',
|
|
|
|
| 113 |
console.log(`[PDF-GEN] [${getElapsed()}] 浏览器启动成功`);
|
| 114 |
|
| 115 |
const page = await browser.newPage();
|
| 116 |
+
// 设置 viewport 满足大部分页面渲染需求
|
| 117 |
await page.setViewport({ width: 1200, height: 800 });
|
| 118 |
+
console.log(`[PDF-GEN] [${getElapsed()}] Viewport: 1200x800`);
|
| 119 |
|
| 120 |
console.log(`[PDF-GEN] [${getElapsed()}] 正在填充页面内容...`);
|
| 121 |
+
await page.setContent(htmlToUse, {
|
| 122 |
waitUntil: ['load', 'networkidle0'],
|
| 123 |
+
timeout: setContentTimeout
|
| 124 |
});
|
| 125 |
+
await page.waitForNetworkIdle({ idleTime: 500, timeout: networkTimeout });
|
| 126 |
console.log(`[PDF-GEN] [${getElapsed()}] 页面内容加载完成`);
|
| 127 |
|
| 128 |
// 等待 base64 图片完全渲染(检测实际加载状态)
|
|
|
|
| 182 |
});
|
| 183 |
}
|
| 184 |
|
| 185 |
+
return {
|
| 186 |
initial: results,
|
| 187 |
final: finalResults,
|
| 188 |
total: images.length
|
|
|
|
| 232 |
|
| 233 |
// 根据 GitHub Issue #10341:截图可以强制触发渲染流水线
|
| 234 |
console.log(`[PDF-GEN] [${getElapsed()}] 截图强制渲染(Issue #10341 workaround)...`);
|
| 235 |
+
await page.screenshot({ type: 'png', timeout: screenshotTimeout });
|
| 236 |
console.log(`[PDF-GEN] [${getElapsed()}] 截图完成`);
|
| 237 |
} else {
|
| 238 |
console.log(`[PDF-GEN] [${getElapsed()}] 无图片,等待 DOM 稳定...`);
|
|
|
|
| 240 |
}
|
| 241 |
|
| 242 |
console.log(`[PDF-GEN] [${getElapsed()}] 正在生成 PDF 二进制流...`);
|
| 243 |
+
|
| 244 |
const pdfBuffer = await page.pdf({
|
| 245 |
format: 'A4',
|
| 246 |
printBackground: true,
|