Обработка PDF: async-генерация и извлечение данных
Генерируйте PDF из HTML-шаблонов через Puppeteer (Node.js) или reportlab и WeasyPrint (Python), сохраняйте результат в объектное хранилище и отдавайте клиенту pre-signed URL — без HTTP-таймаута и без передачи бинарного файла в теле ответа.
Обновлено: 2026-06-28
Кратко
Суть ответа
Обработка PDF: async-генерация и извлечение данных. HTTP-функция принимает параметры и сразу возвращает { jobId }, а пайплайн рендерит PDF (Puppeteer в Node.js или reportlab в Python), загружает его в S3 и возвращает pre-signed URL.
Когда подходит и когда нет
- Генерация документа занимает больше двух секунд
- Документ нужно хранить и скачивать несколько раз
- Batch PDF generation (N invoices, N reports) with fan-out parallelism
На что обратить внимание
- Пока генерация идёт в том же запросе, холодный старт контейнера с Puppeteer съедает значительную часть окна до таймаута шлюза.
- Встраивание бинарника в JSON-ответ тоже не спасает: base64 раздувает payload, а клиент не может начать скачивание до получения всего тела.
Ситуация: нагрузка и где обычно ломается
Почему генерация PDF не помещается в синхронный HTTP
- Рендер в Puppeteer или WeasyPrint занимает секунды — таймаут шлюза обрывает генерацию на середине
- Передавать бинарный PDF в теле HTTP-ответа неудобно: клиент ждёт весь файл до закрытия соединения
- Холодный старт контейнера с Puppeteer съедает значительную часть окна до таймаута
Рендер в Puppeteer или WeasyPrint занимает секунды, а HTTP-ответ с готовым PDF блокирует соединение всё это время — таймаут шлюза обрывает генерацию.
Когда простых рецептов недостаточно
Почему синхронная генерация PDF в HTTP хрупка
Пока генерация идёт в том же запросе, холодный старт контейнера с Puppeteer съедает значительную часть окна до таймаута шлюза.
Встраивание бинарника в JSON-ответ тоже не спасает: base64 раздувает payload, а клиент не может начать скачивание до получения всего тела.
Как Inquir помогает в этом сценарии
PDF-операции как шаги async-пайплайна
HTTP-функция принимает параметры и сразу возвращает { jobId }, а пайплайн рендерит PDF (Puppeteer в Node.js или reportlab в Python), загружает его в S3 и возвращает pre-signed URL.
Puppeteer подключается через слой: нативный бинарник Chromium доступен в контейнере Node.js 22 без собственного Dockerfile.
Что вы получаете на платформе
Что нужно для надёжной PDF-обработки
Разделение HTTP и долгой генерации
HTTP-функция сразу возвращает идентификатор задачи, а пайплайн генерирует PDF вне окна синхронного запроса — с собственным бюджетом памяти и ретраями при сбоях.
Объектное хранилище
Готовый PDF сохраняется в S3/GCS, а клиент получает pre-signed URL для скачивания — вместо бинарного блоба в теле ответа.
Нативные зависимости через слои
Puppeteer, Chromium, reportlab и WeasyPrint подключаются через слои — без сборки собственного Dockerfile.
Прослеживаемость по jobId
Идентификатор задачи ведёт к истории прогона и логам каждого шага, поэтому ошибки рендера проще отлаживать.
Что сделать дальше, по шагам
Как организовать PDF-генерацию
Принять запрос, вернуть 202
HTTP-функция валидирует параметры, запускает global.durable.startNew('render-invoice-pdf', undefined, { invoiceId, customerId }) и возвращает { invoiceId, status: 'generating' } с кодом 202.
Сгенерировать PDF в пайплайне
Puppeteer рендерит HTML в PDF, а reportlab строит документ программно; готовый файл сохраняется в S3.
Вернуть pre-signed URL
Пайплайн сохраняет URL в базе или отправляет вебхук; клиент получает ссылку и скачивает файл напрямую из S3.
Пример кода
Async-генерация PDF-счёта
HTTP-функция ставит работу через global.durable.startNew, запуская рендерер как durable-инстанс; эта функция рендерит HTML в PDF, загружает его в объектное хранилище и обновляет запись задачи. Инстанс переживает рестарты и адресуется по id для опроса статуса.
export async function handler(event) { const { invoiceId, customerId } = JSON.parse(event.body || '{}'); if (!invoiceId) return { statusCode: 400, body: JSON.stringify({ error: 'invoiceId required' }) }; const existing = await db.invoicePdfs.find(invoiceId); if (existing?.url) return { statusCode: 200, body: JSON.stringify({ url: existing.url }) }; await global.durable.startNew('render-invoice-pdf', undefined, { invoiceId, customerId }); return { statusCode: 202, body: JSON.stringify({ invoiceId, status: 'generating' }) }; }
import puppeteer from 'puppeteer-core'; export async function handler(event) { const { invoiceId, customerId } = event; // the input passed to startNew() const invoice = await db.invoices.findById(invoiceId); const html = renderInvoiceTemplate(invoice); const browser = await puppeteer.launch({ executablePath: '/usr/bin/chromium' }); const page = await browser.newPage(); await page.setContent(html, { waitUntil: 'networkidle0' }); const pdfBytes = await page.pdf({ format: 'A4', printBackground: true }); await browser.close(); const url = await storage.upload(pdfBytes, `invoices/${invoiceId}.pdf`); await db.invoicePdfs.upsert({ invoiceId, url, generatedAt: new Date() }); return { invoiceId, url }; }
Когда подходит и когда нет
Когда нужен async PDF
Когда это уместно
- Генерация документа занимает больше двух секунд
- Документ нужно хранить и скачивать несколько раз
- Batch PDF generation (N invoices, N reports) with fan-out parallelism
Когда лучше выбрать другое
- Простые одностраничные PDF без изображений, которые стабильно генерируются быстрее секунды, — оставьте их синхронными
Вопросы и ответы
Вопросы и ответы
Как добавить Puppeteer в Inquir?
Подключите слой с puppeteer-core и chromium-min к функции на Node.js 22 — после этого Puppeteer доступен через import puppeteer from "puppeteer-core".
Python или Node.js для PDF?
Node.js с Puppeteer удобен для рендера HTML в PDF; Python с reportlab или WeasyPrint — для программной генерации документов. Обе связки подключаются через слои.