Отрисовка HTML в PDF через отрисовщик Chrome в Artisan
Мост Artisan отрисовывает HTML в процессе headless Chrome, а затем импортирует результат в документ NextPDF как векторный Form XObject. Текст не растрируется: его по-прежнему можно выделять и искать. Вы подключаете ChromeRendererConfig, вызываете writeHtmlChrome() на документе или используете ChromeHtmlRenderer напрямую, а за макет отвечает Chrome. В этом руководстве рассматриваются вызов отрисовки, сетевая изоляция, задание размера страницы, высота содержимого и жизненный цикл долгоживущего отрисовщика в обработчике.
Предварительные требования:
- Ядро NextPDF и
nextpdf/artisanустановлены. - Установлен двоичный файл Chrome или Chromium, и пользователь-обработчик может запускать его в режиме headless. Перед началом работы проверьте это командой
chromium --headless --dump-dom about:blank. Страница настройки отрисовщика Chrome, ссылка на которую приведена в разделе “См. также”, описывает предоставление двоичного файла и выбор настроек песочницы контейнера.
В этом руководстве предполагается, что вы можете запускать процесс Chrome рядом с приложением. Первый рабочий пример см. в кратком руководстве по Artisan.
Установка
Заголовок раздела «Установка»Установите мост вместе с ядром.
composer require nextpdf/artisanУстановите сборку Chrome или Chromium, которую может запускать пользователь-обработчик. В Debian или Ubuntu используйте пакет из дистрибутива.
apt-get install -y chromiumУбедитесь, что двоичный файл запускается в режиме headless от имени пользователя-обработчика.
chromium --headless --dump-dom about:blankКод возврата 0 с пустой объектной моделью документа (DOM) означает, что двоичный файл и его разделяемые библиотеки доступны. Ненулевой код возврата — тот же сбой, который мост сообщает как ChromeRenderException. Сначала устраните его на этом уровне.
Концептуальный обзор
Заголовок раздела «Концептуальный обзор»writeHtmlChrome() — это метод Document в ядре NextPDF. Он проверяет входные данные, разрешает отрисовщик Artisan, отправляет HTML в Chrome по протоколу Chrome DevTools Protocol (CDP), разбирает возвращённый PDF и встраивает страницу 0 как Form XObject в текущей позиции курсора. Chrome работает как дочерний процесс обработчика PHP. Мост управляет Chrome по CDP, а не подключается к отдельному процессу Chrome через порт отладки, поэтому нет сетевой конечной точки, которую нужно открывать или аутентифицировать.
Мост выполняет отрисовку с сетевой политикой “запрет по умолчанию”. Каждая отрисовка использует Content-Security-Policy, который запрещает все источники ресурсов (default-src 'none') и разрешает только встроенные изображения (img-src data:). Кроме того, мост блокирует каждый URL подресурса на транспортном уровне CDP с помощью Network.setBlockedURLs(['*']). В результате удалённое изображение, таблица стилей, шрифт, скрипт или iframe в вашем HTML не загружаются. Встраивайте каждый ресурс как URI вида data:. Так мост устраняет риск подделки запроса со стороны сервера (SSRF) при отрисовке потенциально недоверенного HTML, и это применяется независимо от конфигурации.
Модель размера страницы работает в двух режимах. Если заданы и ширина, и высота в пунктах PDF, Chrome печатает строго на такой размер бумаги. Если высота не указана или равна null, мост измеряет высоту отрисованного содержимого в Chrome, преобразует её в пункты и добавляет небольшой защитный буфер на переформатирование около 14,4 пункта. Так printToPDF не уходит на вторую страницу, которую импортёр, работающий только со страницей 0, всё равно обрезал бы.
Поверхность API
Заголовок раздела «Поверхность API»// On a NextPDF core Document (the HasTextOutput concern):writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static
// The standalone renderer:new ChromeHtmlRenderer(ChromeRendererConfig $config, ?LoggerInterface $logger = null)ChromeHtmlRenderer::render(string $html, float $widthPt, float $heightPt = 0.0): ChromeRenderResultChromeHtmlRenderer::close(): void
// The configuration value object (final readonly):new ChromeRendererConfig( ?string $chromeBinaryPath = null, int $renderTimeout = 30, string $defaultCss = '', int $maxHtmlSize = 5_000_000, bool $noSandbox = false,)ChromeRendererConfig::fromArray(array $config): selfChromeRendererConfig — единственная поверхность конфигурации. Он неизменяем, поэтому для изменения значения создайте новый экземпляр. ChromeRenderResult::getPdfData() возвращает байты PDF. Страница конфигурации Artisan, ссылка на которую приведена в разделе “См. также”, содержит полный справочник параметров и фиксированные флаги запуска Chrome.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»Подключите конфигурацию к документу, отрисуйте доверенный HTML и сохраните результат.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Core\Document;
$config = new ChromeRendererConfig( chromeBinaryPath: '/usr/bin/chromium',);
$document = Document::createStandalone();$document->setChromeRendererConfig($config);$document->addPage();
$document->writeHtmlChrome(' <div style="display: flex; gap: 20px; font-family: sans-serif;"> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Revenue</h2> <p style="font-size: 2em; color: #2563eb;">$124,500</p> </div> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Orders</h2> <p style="font-size: 2em; color: #16a34a;">1,847</p> </div> </div>');
$document->save('/tmp/report.pdf');Chrome обрабатывает flex-макет, а числа в результате остаются выделяемыми, потому что страница встраивается как векторный Form XObject, а не как растровое изображение. Чтобы уместить вывод на фиксированную страницу A4, передайте ширину и высоту в пунктах.
$document->writeHtmlChrome($html, width: 595.28, height: 841.89);Пример кода — продакшен
Заголовок раздела «Пример кода — продакшен»В продакшене создавайте по одному отрисовщику на обработчик, внедряйте логгер PSR-3, перехватывайте два разных типа исключений отдельно и детерминированно освобождайте процесс Chrome при завершении работы.
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Artisan\Exception\ChromeNotAvailableException;use NextPDF\Artisan\Exception\ChromeRenderException;use Psr\Log\LoggerInterface;
final class ReportRenderer{ private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger) { $config = ChromeRendererConfig::fromArray([ 'chrome_binary' => getenv('CHROME_BINARY') ?: null, 'render_timeout' => 45, 'max_html_size' => 2_000_000, 'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'), ]);
$this->renderer = new ChromeHtmlRenderer($config, $logger); }
public function render(string $html, float $widthPt, float $heightPt = 0.0): string { try { return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData(); } catch (ChromeNotAvailableException $exception) { // Deployment fault: the Chrome runtime is missing. Page on-call. throw $exception; } catch (ChromeRenderException $exception) { // Render-time fault: timeout, crash, or empty output. Retryable once. throw $exception; } }
public function shutdown(): void { $this->renderer->close(); }}Создайте отрисовщик один раз и используйте его повторно. Базовый пул браузеров держит активным один процесс Chrome и перезапускает его каждые 100 отрисовок, чтобы ограничить рост потребления памяти. Две ветви catch разделяют сбой развёртывания, например отсутствие среды выполнения, и сбой во время отрисовки, который можно повторить один раз. Ни один блок catch не пустой. Вызывайте shutdown() при завершении работы обработчика, чтобы освободить процесс Chrome, не дожидаясь деструктора.
Создавайте конфигурацию из массива конфигурации фреймворка, чтобы использовать ключи в snake-case, и закрепляйте chromeBinaryPath в продакшене, чтобы двоичный файл выбирался детерминированно.
Крайние случаи и подводные камни
Заголовок раздела «Крайние случаи и подводные камни»- Пустой HTML не выполняет никаких действий.
writeHtmlChrome('')возвращает документ без изменений. - Страницы ещё нет. Если в документе нет страницы,
writeHtmlChrome()добавляет её перед отрисовкой. - Удалённые ресурсы не загружаются — так задумано.
<img src="https://...">отрисовывается пустым. Встраивайте каждый ресурс как URI видаdata:. Это политика сетевой изоляции, а не дефект. - Импортируется только страница 0. Автоподбор высоты добавляет буфер на переформатирование, поэтому формируется одна страница. При явно заданной высоте буфер не добавляется, и результат точно соответствует запрошенному размеру бумаги, поэтому подбирайте высоту под своё содержимое.
- Мост отсутствует. Если
nextpdf/artisanне установлен, ядро выбрасывает исключение макета, а не фатальную ошибку. Если библиотекаchrome-php/chromeотсутствует, мост выбрасываетChromeNotAvailableExceptionс командой установки. defaultCssи</style>. Любая последовательность</style>вdefaultCssудаляется перед внедрением как защита от выхода за пределы блока стилей. Учитывайте это в шаблонах, если вы шаблонизируете CSS.
Производительность
Заголовок раздела «Производительность»Первая отрисовка включает стоимость запуска Chrome и построения макета. Последующие отрисовки повторно используют активный процесс Chrome, поэтому обычно не платят за запуск. Создавайте по одному отрисовщику на обработчик и используйте его повторно. Не создавайте отдельный отрисовщик на каждый запрос. Ожидайте всплеск задержки на каждой 100-й отрисовке, когда мост перезапускает процесс Chrome, чтобы ограничить память. Учитывайте это в целевых показателях задержки, а не воспринимайте как инцидент. Согласуйте renderTimeout с вышестоящим бюджетом запроса на любом пути, достижимом для недоверенного ввода.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»- Сетевая изоляция — основная мера защиты. Мост вообще не разрешает исходящую загрузку подресурсов: CSP
default-src 'none'плюс блокировка каждого URL на транспортном уровне CDP. Он не реализует список разрешённых доменов, потому что в нём нет необходимости. Встраивайте ресурсы как URI видаdata:. - Ввод ограничивается до обращения к Chrome. Мост отклоняет HTML сверх
maxHtmlSize(по умолчанию 5 МБ), чрезмерно большой data-URI в base64 (защита от бомбы распаковки) и любой тег<meta http-equiv="refresh">(который мог бы инициировать переход на внутреннюю конечную точку). ОставляйтеmaxHtmlSizeпо умолчанию, если только известная нагрузка не требует большего значения. Его увеличение расширяет поверхность исчерпания ресурсов. - Песочница Chrome — отдельная мера защиты. Установка
noSandbox: trueзапускает Chrome с--no-sandbox, что отключает изоляцию процессов Chrome. Это реальное ослабление изоляции, а не косметический флаг. За пределами контейнеров оставляйте его равнымfalse. Когда песочница контейнера не может инициализироваться, запускайте Chrome от имени пользователя без прав root в ограниченном контейнере и относитесь к такому развёртыванию как к режиму, требующему большего доверия к вводу. - Журналы содержат только метаданные. Внедрите логгер PSR-3. Мост записывает в журнал длины в байтах, размеры и события жизненного цикла, но никогда не записывает HTML, байты PDF или извлечённый текст.
- Никогда не открывайте порт удалённой отладки Chrome. Мост его не использует, а открытый порт CDP — неаутентифицированный канал управления.
Полная модель угроз, включая защиту от SSRF, явную границу песочницы и каталог режимов отказа, находится на странице безопасности и эксплуатации Artisan, ссылка на которую приведена в разделе “См. также”. На этой странице закреплены соответствующие пункты OWASP, CWE и NIST.
Соответствие
Заголовок раздела «Соответствие»Это руководство не выдвигает собственных нормативных заявлений о соответствии стандартам. Вышестоящая страница безопасности и эксплуатации Artisan сопоставляет меры защиты моста в области сети, изоляции и исчерпания ресурсов с OWASP ASVS, CWE Top 25 (SSRF / неконтролируемое потребление ресурсов) и NIST SP 800-53 SC-7. Эта страница сборника рецептов повторно излагает порядок использования и оставляет нормативные ссылки на той странице. Мост не выполняет криптографических операций; подпись и шифрование относятся к ядру или к коммерческой редакции и не затрагиваются Artisan.
См. также
Заголовок раздела «См. также»- Отрисовка на границе сети с помощью Cloudflare — отрисовка HTML на границе сети с локальным резервным вариантом.
- Краткое руководство по Artisan — минимальная первая отрисовка.
- Настройка отрисовщика Chrome — предоставление двоичного файла, выбор настроек песочницы контейнера и проверка работоспособности.
- Безопасность и эксплуатация Artisan — модель сетевой изоляции, граница песочницы и режимы отказа.