Axios调用weasyprint-rest生成PDF重复请求出现socket hang up错误
解决WeasyPrint REST API二次请求出现ECONNRESET问题
针对你遇到的首次请求正常、二次及后续请求触发ECONNRESET(socket hang up)的问题,结合你已尝试的超时配置、KeepAlive Agent方案,下面给出几个针对性的解决思路:
1. 每次请求创建全新的FormData和流实例
核心问题大概率是复用了已被读取完毕的流对象:第一次请求时fs.createReadStream生成的流已被消费,二次请求复用同一FormData或流时,没有数据可发送,服务器会主动断开连接。
修改代码,将FormData创建、流的append逻辑放到每次请求的函数内部,确保每次请求都是全新实例:
const axios = require('axios'); const FormData = require("form-data"); const fs = require('fs'); const path = require('path'); async function generatePdf() { // 每次请求都初始化新的FormData和流 const data = new FormData(); data.append("html", fs.createReadStream(path.join(__dirname, "../assets/html/report.html"))); data.append('asset[]', fs.createReadStream(path.join(__dirname, `../assets/images/footerLogo.png`))); data.append("style", fs.createReadStream(path.join(__dirname, "../assets/css/report.css"))); const configConvert = { url: process.env.HTMLTOPDF_URL, headers: { ...data.getHeaders() }, responseType: "arraybuffer", method: "post", timeout: 30000, data: data }; try { const pdfResult = await axios(configConvert); console.log(pdfResult.data); return pdfResult.data; } catch (err) { console.error(err); } }
2. 显式销毁流,避免资源泄漏
如果流未被正确清理,可能导致连接资源泄漏。可以在请求完成后(无论成功失败)手动销毁所有流对象:
async function generatePdf() { // 单独声明流对象,方便后续销毁 const htmlStream = fs.createReadStream(path.join(__dirname, "../assets/html/report.html")); const logoStream = fs.createReadStream(path.join(__dirname, `../assets/images/footerLogo.png`)); const cssStream = fs.createReadStream(path.join(__dirname, "../assets/css/report.css")); const data = new FormData(); data.append("html", htmlStream); data.append('asset[]', logoStream); data.append("style", cssStream); const configConvert = { url: process.env.HTMLTOPDF_URL, headers: { ...data.getHeaders() }, responseType: "arraybuffer", method: "post", timeout: 30000, data: data }; try { const pdfResult = await axios(configConvert); console.log(pdfResult.data); return pdfResult.data; } catch (err) { console.error(err); } finally { // 手动销毁流,释放底层资源 htmlStream.destroy(); logoStream.destroy(); cssStream.destroy(); } }
3. 完善KeepAlive Agent的配置
之前的KeepAlive配置参数不全,试试补充maxSockets和keepAliveMsecs参数,优化连接复用逻辑:
const https = require('https'); const axios = require('axios'); const FormData = require("form-data"); const fs = require('fs'); const path = require('path'); // 创建配置更完整的KeepAlive Agent const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: Infinity, // 不限制并发连接数 keepAliveMsecs: 30000, // 连接保持时长30秒 }); async function generatePdf() { const data = new FormData(); data.append("html", fs.createReadStream(path.join(__dirname, "../assets/html/report.html"))); data.append('asset[]', fs.createReadStream(path.join(__dirname, `../assets/images/footerLogo.png`))); data.append("style", fs.createReadStream(path.join(__dirname, "../assets/css/report.css"))); const configConvert = { url: process.env.HTMLTOPDF_URL, headers: { ...data.getHeaders() }, responseType: "arraybuffer", method: "post", timeout: 30000, httpsAgent: httpsAgent, data: data }; try { const pdfResult = await axios(configConvert); console.log(pdfResult.data); return pdfResult.data; } catch (err) { console.error(err); } }
4. 换用Node.js原生fetch API测试
如果以上方案都无效,试试用Node.js原生fetch替代Axios,排除Axios自身处理逻辑的问题:
const fs = require('fs'); const path = require('path'); const FormData = require("form-data"); async function generatePdfWithFetch() { const data = new FormData(); data.append("html", fs.createReadStream(path.join(__dirname, "../assets/html/report.html"))); data.append('asset[]', fs.createReadStream(path.join(__dirname, `../assets/images/footerLogo.png`))); data.append("style", fs.createReadStream(path.join(__dirname, "../assets/css/report.css"))); try { const response = await fetch(process.env.HTMLTOPDF_URL, { method: 'POST', headers: data.getHeaders(), body: data, signal: AbortSignal.timeout(30000) // 设置请求超时 }); if (!response.ok) { throw new Error(`请求失败!状态码:${response.status}`); } const pdfBuffer = await response.arrayBuffer(); console.log(pdfBuffer); return pdfBuffer; } catch (err) { console.error(err); } }
为什么Postman可以正常请求?
Postman每次请求都是独立的上下文,会自动创建全新的请求体和流对象,不会复用之前的资源,因此不会遇到流已被消费的问题。
内容的提问来源于stack exchange,提问作者Vaibhav Tamakuwala
相关产品推荐
相关产品推荐

