/** * 复现 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 = `
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);
`;
const messageBlock = ``;
let html = ``;
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 += ``;
if ((i + 1) % 50 === 0) {
process.stdout.write('.');
}
}
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();