本地AWS Lambda中Puppeteer抛出Target closed错误求助
AWS Lambda上NodeJS+Puppeteer转HTML为PDF频繁故障排查与解决
实现代码
生成PDF的核心逻辑:
const puppeteer = require("puppeteer-core"); const chromium = require("@sparticuz/chromium"); async generatePdf(html = ''): Promise<Buffer> { try { const browser = await puppeteer.launch({ executablePath: await chromium.executablePath(), headless: true, defaultViewport: chromium.defaultViewport, ignoreHTTPSErrors: true, args: [ ...chromium.args, '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--single-process', '--headless', '--disable-gpu', '--disable-features=AudioServiceOutOfProcess', '--no-zygote', ], pipe: true }); const page = await browser.newPage(); await page.setContent(html, { waitUntil: 'domcontentloaded' }); const pdfBuffer = await page.pdf({ displayHeaderFooter: true, format: 'a4', margin: { top: 24, bottom: 50, left: 24, right: 24 }, footerTemplate: (` <div style="display: table-footer-group; position: fixed; font-family: 'Open Sans', Verdana, Helvetica, sans-serif; font-size: 8px; width: 545px; text-align:right; padding: 0 20px;"> <div>Page <span class="pageNumber"></span>/<span class="totalPages"></span></div> </div> `) }); await page.close(); await browser.close(); return pdfBuffer; } catch(err) { console.log(err); throw err; } }
Docker配置
容器化Lambda的Dockerfile:
FROM public.ecr.aws/lambda/nodejs:16 as builder WORKDIR /var/task/ COPY package.json ./ COPY package-lock.json ./ COPY locales ./locales RUN npm install FROM public.ecr.aws/lambda/nodejs:16 WORKDIR ${LAMBDA_TASK_ROOT} ADD ./ ${LAMBDA_TASK_ROOT} COPY --from=builder /var/task/ / ENTRYPOINT [ "/lambda-entrypoint.sh" ]
SAM部署模板(template.yaml)
AWSTemplateFormatVersion: 2010-09-09 Description: > Todo Transform: "AWS::Serverless-2016-10-31" Parameters: NodeEnv: Type: String Default: "default" AwsRegion: Type: String Default: "default" AppEnv: Type: String Default: "default" SGid: Type: String Default: "default" SubnetIds: Type: List<String> Default: "default" Globals: Function: Runtime: nodejs16.x MemorySize: 2024 Timeout: 100 Environment: Variables: NODE_ENV: !Ref NodeEnv VpcConfig: SecurityGroupIds: - !Ref SGid SubnetIds: !Ref SubnetIds Resources: ChromiumLayer: Type: AWS::Serverless::LayerVersion Properties: Description: Chromium with Node.js integration for AWS Lambda ContentUri: layers/chromium CompatibleRuntimes: - &nodejsRuntime nodejs16.x CompatibleArchitectures: - &chromiumArch x86_64 RetentionPolicy: Delete Metadata: BuildMethod: *nodejsRuntime BuildArchitecture: *chromiumArch ReportFunction: Type: AWS::Serverless::Function Metadata: Properties: Architectures: - *chromiumArch Layers: - !Ref ChromiumLayer Timeout: 30 MemorySize: 2024 Handler: report/src/main.handler CodeUri: dist/apps Description: Events: Report: Type: Api Properties: Path: /report Method: ANY
环境版本
- puppeteer-core 19.8.0
- chromium 112
- Docker desktop 4.26.1 (131620)
- Mac OS 14.2.1(M1芯片)
故障现象
- 约90%请求失败,抛出错误:
[Nest] 26 - 12/25/2023, 1:18:55 PM ERROR [ExceptionsHandler] Protocol error (Page.printToPDF): Target closed ProtocolError: Protocol error (Page.printToPDF): Target closed at new Callback (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:61:35) at CallbackRegistry.create (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:106:26) at Connection._rawSend (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:216:26) at CDPSessionImpl.send (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:419:78) at CDPPage.createPDFStream (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Page.js:741:88) at CDPPage.pdf (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Page.js:767:37) at FilesService.generatePdf (/var/task/files/src/files.service.js:225:42)
- 偶尔返回损坏PDF,无法打开,终端查看为乱码。
解决方案
1. 优化页面加载等待策略
将page.setContent的waitUntil从domcontentloaded改为networkidle0,确保页面所有资源加载完成后再生成PDF:
await page.setContent(html, { waitUntil: 'networkidle0' });
如果页面有持续网络请求,可改用networkidle2(等待2秒内没有超过2个网络请求)。
2. 精简并优化Puppeteer启动参数
移除重复参数(如--headless已包含在chromium.args中),调整参数避免进程异常:
args: [ ...chromium.args, '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-features=AudioServiceOutOfProcess', '--no-zygote', // 新增避免Chrome被后台挂起的参数 '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding' ],
移除--single-process,该参数在Lambda环境下易导致进程崩溃。
3. 确保Chromium层架构匹配
本地M1为arm64架构,Lambda运行环境为x86_64,需跨架构构建Chromium层:
- 使用Docker buildx构建:
docker buildx build --platform linux/amd64 -t chromium-layer .
- 直接使用
@sparticuz/chromium官方提供的预构建层,避免自行打包架构不匹配问题。
4. 完善资源释放逻辑
在finally块中确保页面和浏览器资源被释放,避免资源泄漏:
async generatePdf(html = ''): Promise<Buffer> { let browser; let page; try { browser = await puppeteer.launch({ // 启动参数... }); page = await browser.newPage(); await page.setContent(html, { waitUntil: 'networkidle0' }); const pdfBuffer = await page.pdf({ // PDF配置... }); return pdfBuffer; } catch(err) { console.error(err); throw err; } finally { if (page) await page.close().catch(() => {}); if (browser) await browser.close().catch(() => {}); } }
5. 调整Lambda资源配置
- 将Lambda内存提升至3008MB(更高内存对应更高CPU和带宽)
- 超时时间从30秒调整为60秒,给PDF生成足够时间
6. 移除不必要的VPC配置
如果Lambda无需访问VPC内资源,删除VpcConfig配置,VPC内的Lambda可能存在网络延迟或资源限制,导致Chromium启动失败。
7. 验证PDF返回的Content-Type
确保API返回PDF时设置正确的响应头:
// 示例:NestJS中设置响应头 @Get('/report') async getReport(@Res() res: Response) { const pdfBuffer = await this.filesService.generatePdf(html); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'inline; filename="report.pdf"'); res.send(pdfBuffer); }
避免响应头错误导致PDF文件传输损坏。
内容的提问来源于stack exchange,提问作者lutaev
相关产品推荐
相关产品推荐

