Я пытаюсь добиться очень простой генерации PDF-файлов с помощью Express JS, puppeteer и ejs. Я использовал следующий код несколько раз и достиг аналогичных результатов.
const router = require('express').Router();
const puppeteer = require('puppeteer');
const {_generateHtml} = require("./utils");
router.post('/offer-letter', async (req, res) => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
const html = await _generateHtml({
doj: req.body.doj,
name: req.body.name,
role: req.body.role,
}, 'offer-template.ejs');
await page.setContent(html, {waitUntil: 'load', timeout: 0});
const pdfBuffer = await page.pdf({
printBackground: true,
});
res.set('Content-Type', 'application/pdf');
res.send(pdfBuffer);
});
module.exports = router;
Вот также функция генерацииHtml
const {compile} = require("ejs");
const fs = require("fs");
async function _generateHtml(data, templatePath) {
const template = fs.readFileSync(templatePath, 'utf8');
let html = compile(template);
return html(data);
}
module.exports = {
_generateHtml
}
Вот структура папок
.env
index.js
node_modules
offer-letter-template-html.html
offer-template.ejs
package-lock.json
package.json
pdfRoutes.js
utils
Однако теперь я обнаружил сообщение об ошибке, которое просто поражает голову. Как именно вы это решаете?
throw new Errors_js_1.TimeoutError(`Timed out after waiting ${ms}ms`, { cause });
^
TimeoutError: Timed out after waiting 30000ms
at C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\puppeteer\common\util.js:289:19
at C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:3986:35
at OperatorSubscriber2._this._next (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1055:13)
at Subscriber2.next (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:678:16)
at AsyncAction2.<anonymous> (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:4863:24)
at AsyncAction2._execute (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1974:16)
at AsyncAction2.execute (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1963:26)
at AsyncScheduler2.flush (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:2235:30)
at listOnTimeout (node:internal/timers:573:17)
at process.processTimers (node:internal/timers:514:7) {
[cause]: undefined
}
Node.js v20.12.1
Вот образец файла ejs, который вы можете использовать
<html xmlns = "http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div id = "page-container">
test
</div>
</body>
</html>
22 мая 2024 г. — Редактировать --------------------------------
Это минимальный код ошибки для всех, кто пытается воспроизвести проблему.
const puppeteer = require("puppeteer");
async function generatePDF() {
const browser = await puppeteer.launch({headless: true, timeout:0});
const page = await browser.newPage();
await page.setContent("<h1>Hello World</h1>", { waitUntil: 'domcontentloaded' });
console.info("Content set");
const pdfBuffer = await page.pdf({ printBackground: true });
console.info("PDF generated");
await browser.close();
}
generatePDF().catch(console.error);
package.json для проекта
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"puppeteer": "^22.9.0"
}
}
Пока что я отладил, проблема заключается в page.pdf()
. Я получаю ту же ошибку даже при использовании await page.setContent(html);
вместо await page.setContent(html, {waitUntil: 'load', timeout: 0});
.
Приятно знать, но информации все еще недостаточно, чтобы воспроизвести это. Пожалуйста, ответьте на все части моего комментария выше. Вы можете отредактировать свое сообщение, включив в него минимально воспроизводимый пример.
@ggorlen добавил в сообщение образец файла ejs.
Спасибо, но, как показывает мой ответ, это все еще невозможно воспроизвести.
Я не могу воспроизвести вашу проблему. Вот мой код, который отлично работает на Node 20 и Ubuntu 22.04.
сервер.js:
// Note: error handling is not included. I don't recommend this setup;
// it's purely for reproduction and doesn't follow best practices.
const fs = require("fs");
const {compile} = require("ejs"); // ^3.1.10
const express = require("express"); // ^4.19.2
const puppeteer = require("puppeteer"); // ^22.7.1
const app = express();
async function _generateHtml(data, templatePath) {
const template = fs.readFileSync(templatePath, "utf8");
const html = compile(template);
return html(data);
}
app.get("/", async (req, res) => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
const html = await _generateHtml({}, "offer-template.ejs");
await page.setContent(html);
const pdfBuffer = await page.pdf({
printBackground: true,
});
res.set("Content-Type", "application/pdf");
res.send(pdfBuffer);
// browser should be closed in a finally
// block here if this were a real script.
});
app.listen(3000);
предложение-шаблон.ejs:
<html xmlns = "http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div id = "page-container">
test
</div>
</body>
</html>
Запустите node server
, зайдите в свой браузер по адресу http://localhost:3000 и вы увидите, что PDF-файл отображается правильно. Поскольку данные POST не используются в шаблоне (и шаблоны не должны иметь значения с точки зрения зависания — в конечном итоге они возвращают строку, которую вы можете жестко запрограммировать для минимальной воспроизводимости), я удалил это. То же самое и с экспортированным модулем — это не имеет значения.
Пожалуйста, предоставьте более подробную информацию и шаги, чтобы воспроизвести проблему. В приведенном вами коде нет ничего явно неправильного, кроме использования таймаута: 0, который подавляет ошибки и может вызвать бесконечное зависание.
спасибо за быстрые ответы чувак. Мне нужно спрятать кровать, так как я буквально падаю со стула, учитывая, что сейчас 5 утра, я первым делом попробую это и вернусь с более воспроизводимым кодом.
Звучит отлично. Возможно, вы захотите попробовать запустить мой код здесь в чистой среде и убедиться, что он работает для вас (пожалуйста, подтвердите тот или иной способ, когда сможете). Если этот код у вас не работает, то это интересно — возможно, это какая-то деталь среды.
Как ни странно, при использовании именно того кода, которым вы поделились, я все еще получаю ту же ошибку. Я использую узел 20 на Windows 11 с ryzen 7840HS. Вот файл package.json { "name": "backend", "version": "1.0.0", "main": "index.js", "scripts": { "start": "node index.js", "dev": "nodemon index.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "cors": "^2.8.5", "ejs": "^3.1.10", "express": "^4.19.2", "puppeteer": "^22.9.0" }, "devDependencies": { "nodemon": "^3.1.0" } }
Можете ли вы минимизировать проблему еще больше? У вас должна быть возможность обновить свой пост, просто запустив, создав страницу и вызвав page.pdf()
в качестве минимального репрекса — удалите отвлекающие факторы, такие как EJS, Express и Nodemon, которые, вероятно, не связаны с проблемой. Я не уверен, в чем причина, но это может быть ошибка в Puppeteer. Включите в обновление файл package.json вместе с полным работоспособным минимальным ошибочным кодом.
Если это происходит только в Windows и Chrome 125+, возможно, это проблема pptr.dev/… (проверьте с помощью dumpio: true)
@ggorlen добавил, что это, похоже, помогло. args: ['--no-sandbox', '--disable-setuid-sandbox']
Спасибо за ваши идеи. Я свел его к простому сценарию узла, а затем перешел к нему оттуда.
Пожалуйста, обновите сообщение, указав минимальный ошибочный код, чтобы помочь будущим посетителям, и опубликуйте самоответ. Я не думаю, что отключение песочницы — хорошая идея или решение, но рад, что у вас все заработало.
Запуск экземпляра puppeteer с отключением песочницы на данный момент работал для меня как обходной путь. В ближайшее время мне придется развернуть фактическую реализацию этого в производстве, поэтому я постараюсь опубликовать более подходящее решение позже.
const browser = await puppeteer.launch({headless: true, args: ['--no-sandbox']});
Это работает для меня, но мне интересно, каковы минусы его реализации.
Привет! Это работает и для меня. Но какие могут быть минусы? Есть ли другая реализация?
Проблема с песочницей заключается в том, что она отключает некоторые функции безопасности в Chrome, и это может открыть совершенно другую банку червей. Мой код не предназначался для производства, поэтому на тот момент я не беспокоился об этом, но с моей стороны это все еще открытый вопрос.
У меня тоже эта проблема. Просто через неделю внезапно перестал работать
Обновлено: Как упоминалось выше, отключение песочницы — это обходной путь, но скорее хак. Проблема заключается в разрешениях. Если вы запустите page.pdf({ dumpio: 'true' }), вы увидите вывод ошибок.
ИСПРАВИТЬ:
Вы можете попытаться решить эту проблему, вручную установив необходимые разрешения для исполняемого файла Chromium с помощью команды icacls, как предложено в сообщении об ошибке. Откройте командную строку от имени администратора и выполните следующую команду:
icacls %USERPROFILE%/.cache/puppeteer/chrome /grant *S-1-15-2-1:(OI)(CI)(RX)
Эта команда предоставляет необходимые разрешения исполняемому файлу Chromium, который использует Puppeteer.
offer-template.ejs
, чтобы я мог запустить код и увидеть ошибку? Я предполагаю, что там нет ничего существенного и это явно не причина зависания, но пример должен быть полным и легко запускаемым. На самом деле, я бы полностью удалил EJS, поскольку он почти наверняка не является частью проблемы.