使用pdf-creator-node生成PDF时报phantomjs ENOENT及headers sent错误
问题排查与修复
核心错误1:phantomjs 进程启动失败
报错信息如下:
Error: spawn /app/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs ENOENT at Process.ChildProcess._handle.onexit (internal/child_process.js:277:19) at onErrorNT (internal/child_process.js:472:16) at processTicksAndRejections internal/process/task_queues.js:82:21) { errno: -2, code: 'ENOENT', syscall: 'spawn /app/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs', path: '/app/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs', spawnargs: [ '--local-url-access=false', '/app/node_modules/html-pdf/lib/scripts/pdf_a4_portrait.js' ] }
错误本质:系统找不到指定路径的phantomjs可执行文件,属于依赖安装/兼容问题,常见触发原因:
- 依赖安装不完整:
phantomjs-prebuilt安装时需要下载对应平台的二进制包,网络异常、安装时加了--no-optional参数都会导致二进制文件缺失 - 运行环境兼容问题:如果是Alpine类轻量容器镜像,默认缺少glibc依赖,就算二进制文件存在也无法正常执行;跨平台复制node_modules(比如本地macOS环境的依赖直接拷贝到Linux容器)也会因为架构不匹配导致二进制无法运行
- 构建流程问题:CI/CD构建时做了依赖裁剪,误删了phantomjs二进制文件
核心错误2:未捕获Promise异常导致响应头重复发送
报错信息如下:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at new NodeError (internal/errors.js:322:7) at ServerResponse.setHeader (_http_outgoing.js:561:11) at ServerResponse.header (/app/node_modules/express/lib/response.js:794:10) at ServerResponse.send (/app/node_modules/express/lib/response.js:174:12) at ServerResponse.json (/app/node_modules/express/lib/response.js:278:15) at Object.fail (/app/libs/response.js:51:26) at /app/routes/pdf.js:25:14 at processTicksAndRejections (internal/process/task_queues.js:95:5)
错误本质:这是第一个错误引发的连锁问题:
- PDF生成逻辑因为phantomjs启动失败抛出异常,但代码没有对Promise的rejected状态做捕获,触发了全局
unhandledRejection - 异常走到通用错误处理逻辑时,之前已经有部分响应内容发送给客户端,代码没有判断响应状态就再次调用
res.json()/res.send()返回错误信息,触发HTTP响应头重复设置的错误,触发点在/app/routes/pdf.js第25行
修复步骤
- 先解决phantomjs依赖问题
- 在项目根目录执行
npm rebuild phantomjs-prebuilt,重新触发二进制下载流程,确保下载到和当前运行环境匹配的phantomjs可执行文件;容器构建阶段不要跳过可选依赖安装 - 如果使用Alpine基础镜像,先执行
apk add --no-cache gcompat libstdc++ fontconfig ttf-dejavu安装兼容依赖,或者直接替换为Debian/Ubuntu基础镜像避免兼容问题 - 若长期维护项目,建议直接替换为基于Puppeteer的PDF生成方案,phantomjs已经停止维护多年,环境兼容问题会持续出现
- 在项目根目录执行
- 修复异常捕获逻辑
- 所有PDF生成相关的async函数、Promise调用必须用
try/catch或者.catch()做异常捕获 - 所有返回响应的分支执行前先判断
res.headersSent属性,值为true时直接终止逻辑,不要重复发送响应 - 增加全局
unhandledRejection事件监听和Express全局错误处理中间件,避免未捕获异常导致进程退出
- 所有PDF生成相关的async函数、Promise调用必须用
内容的提问来源于stack exchange,提问作者Tài Nguyễn
相关产品推荐
相关产品推荐

