Spaces:
Running
Running
| /** | |
| * 复现 PDF 生成超时 bug | |
| * | |
| * 用法: node reproduce-timeout.js | |
| * | |
| * 原理: 生成一个 ~7MB 的纯文本/代码 HTML(不含图片), | |
| * 发送给后端 /api/generate_pdf,观察是否在 page.pdf() 阶段超时 120s。 | |
| */ | |
| const http = require('http'); | |
| // 端口: 测试环境 = 7861, 生产环境 = 7860 | |
| const BACKEND_PORT = process.env.PORT || 7861; | |
| const BACKEND_URL = `http://localhost:${BACKEND_PORT}`; | |
| // 生成指定大小的重复文本块 | |
| function generateLargeHtml(targetSizeMB) { | |
| // 模拟 Gemini 对话:大量代码块 + 长文本 | |
| const codeBlock = `<pre><code class="language-javascript">function fibonacci(n) { | |
| if (n <= 1) return n; | |
| return fibonacci(n - 1) + fibonacci(n - 2); | |
| } | |
| // 这是一个很长的代码注释,用来增加 HTML 体积 | |
| // Lorem ipsum dolor sit amet, consectetur adipiscing elit. | |
| // Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. | |
| // Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. | |
| const result = []; | |
| for (let i = 0; i < 100; i++) { | |
| result.push(fibonacci(i)); | |
| } | |
| console.log(result); | |
| </code></pre>`; | |
| const messageBlock = `<div class="message"><p>这是一段很长的对话内容,用来模拟真实场景下的文本体积。在实际使用中,Gemini 用户可能会产生非常长的对话,包含大量代码块、解释文本、数学公式等。这些内容累积起来会形成非常大的 HTML 文档。</p><p>Additional text to simulate real conversation output from AI assistants. The more verbose the responses, the larger the HTML becomes. This is especially common with Gemini which tends to produce lengthy, detailed answers.</p></div>`; | |
| let html = `<!DOCTYPE html><html><head><meta charset="utf-8"><style> | |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; line-height: 1.6; } | |
| pre { background: #f6f8fa; padding: 16px; border-radius: 6px; overflow-x: auto; } | |
| code { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; } | |
| .message { margin-bottom: 24px; padding: 12px; border-bottom: 1px solid #eee; } | |
| </style></head><body>`; | |
| const chunk = codeBlock + messageBlock; | |
| const chunkSize = Buffer.byteLength(chunk, 'utf8'); | |
| const targetBytes = targetSizeMB * 1024 * 1024; | |
| const iterations = Math.ceil(targetBytes / chunkSize); | |
| console.log(`每块大小: ${(chunkSize / 1024).toFixed(1)} KB, 需要重复 ${iterations} 次达到 ${targetSizeMB} MB`); | |
| for (let i = 0; i < iterations; i++) { | |
| html += `<div class="message-block">${chunk}</div>`; | |
| if ((i + 1) % 50 === 0) { | |
| process.stdout.write('.'); | |
| } | |
| } | |
| html += '</body></html>'; | |
| const actualSizeMB = (Buffer.byteLength(html, 'utf8') / 1024 / 1024).toFixed(2); | |
| console.log(`\n生成 HTML 大小: ${actualSizeMB} MB`); | |
| return html; | |
| } | |
| async function sendPdfRequest(html) { | |
| const payload = JSON.stringify({ | |
| html, | |
| codeTheme: 'github', | |
| showWatermark: false, | |
| imageCount: 0, // 关键: 0 张图片 | |
| totalImageSizeMB: 0, // 关键: 0 MB 图片 | |
| platform: 'Gemini', | |
| language: 'en-US', | |
| extensionVersion: '2.0.2', | |
| exportCount: 18, | |
| exportPdf: 3, | |
| exportMd: 7, | |
| exportTxt: 0, | |
| exportDocx: 8, | |
| exportJson: 0, | |
| exportClipboard: 0, | |
| exportNotion: 0, | |
| }); | |
| console.log(`\n发送请求 payload 大小: ${(Buffer.byteLength(payload) / 1024 / 1024).toFixed(2)} MB`); | |
| console.log(`开始时间: ${new Date().toISOString()}`); | |
| return new Promise((resolve, reject) => { | |
| const url = new URL('/api/generate_pdf', BACKEND_URL); | |
| const options = { | |
| hostname: url.hostname, | |
| port: url.port, | |
| path: url.pathname, | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Content-Length': Buffer.byteLength(payload), | |
| }, | |
| // PDF 生成可能很慢,node HTTP client 默认 timeout 较短 | |
| timeout: 600000, // 10 min | |
| }; | |
| const startTime = Date.now(); | |
| const req = http.request(options, (res) => { | |
| const chunks = []; | |
| res.on('data', (chunk) => chunks.push(chunk)); | |
| res.on('end', () => { | |
| const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); | |
| console.log(`\n结束时间: ${new Date().toISOString()}`); | |
| console.log(`总耗时: ${elapsed}s`); | |
| console.log(`HTTP 状态码: ${res.statusCode}`); | |
| if (res.statusCode === 200) { | |
| const pdfSizeMB = (Buffer.concat(chunks).length / 1024 / 1024).toFixed(2); | |
| console.log(`PDF 文件大小: ${pdfSizeMB} MB`); | |
| resolve({ status: 200, pdfSizeMB }); | |
| } else { | |
| const body = Buffer.concat(chunks).toString(); | |
| console.log(`错误响应: ${body}`); | |
| resolve({ status: res.statusCode, error: body }); | |
| } | |
| }); | |
| }); | |
| req.on('error', (e) => reject(e)); | |
| req.on('timeout', () => { | |
| req.destroy(); | |
| reject(new Error(`请求超时 (${options.timeout}ms)`)); | |
| }); | |
| req.write(payload); | |
| req.end(); | |
| }); | |
| } | |
| async function main() { | |
| console.log('=== PDF 超时问题复现脚本 ===\n'); | |
| console.log(`后端地址: ${BACKEND_URL} (端口 ${BACKEND_PORT})`); | |
| console.log(`环境变量 PORT=7860 → 生产环境, PORT=7861(默认) → 测试环境`); | |
| // 生成 ~7MB HTML(对齐日志中的 6.89 MB) | |
| const html = generateLargeHtml(7); | |
| try { | |
| console.log('\n正在发送 PDF 生成请求...'); | |
| const result = await sendPdfRequest(html); | |
| if (result.status === 200) { | |
| console.log('\n✅ PDF 生成成功'); | |
| } else { | |
| console.log(`\n❌ PDF 生成失败: HTTP ${result.status}`); | |
| } | |
| } catch (err) { | |
| console.error(`\n❌ 请求异常: ${err.message}`); | |
| } | |
| } | |
| main(); | |