Merender HTML ke PDF dengan perender Chrome Artisan
Sekilas
Bagian berjudul “Sekilas”Jembatan Artisan merender HTML melalui proses Chrome headless, lalu mengimpor hasilnya ke dokumen NextPDF sebagai Form XObject vektor. Teks tetap dapat dipilih dan dicari, bukan dirasterisasi. Anda dapat melampirkan ChromeRendererConfig, memanggil writeHtmlChrome() pada dokumen, atau menggunakan ChromeHtmlRenderer secara langsung, lalu membiarkan Chrome menangani tata letak. Panduan ini mencakup pemanggilan render, isolasi jaringan, penentuan ukuran halaman, tinggi konten, dan siklus hidup renderer jangka panjang untuk worker.
Prasyarat awal:
- Inti NextPDF dan
nextpdf/artisansudah terpasang. - Biner Chrome atau Chromium sudah terpasang, dan pengguna worker dapat menjalankannya secara headless. Verifikasi dengan
chromium --headless --dump-dom about:blanksebelum Anda mulai. Halaman penyiapan renderer Chrome yang ditautkan di bagian Lihat juga mencakup penyediaan biner dan keputusan sandbox kontainer.
Panduan ini mengasumsikan Anda dapat menjalankan proses Chrome di dekat aplikasi. Untuk contoh pertama yang dapat dijalankan, baca quickstart Artisan.
Instalasi
Bagian berjudul “Instalasi”Pasang jembatan ini bersama inti NextPDF.
composer require nextpdf/artisanPasang build Chrome atau Chromium yang dapat dijalankan oleh pengguna worker. Pada Debian atau Ubuntu, gunakan paket distribusi.
apt-get install -y chromiumPastikan biner dapat berjalan secara headless sebagai pengguna worker.
chromium --headless --dump-dom about:blankKode keluar 0 dengan Document Object Model (DOM) kosong berarti biner dan pustaka pendukungnya tersedia. Kode keluar selain nol adalah kegagalan yang sama dengan yang dimunculkan jembatan sebagai ChromeRenderException. Perbaiki masalah ini terlebih dahulu.
Ikhtisar konseptual
Bagian berjudul “Ikhtisar konseptual”writeHtmlChrome() adalah metode pada Document inti NextPDF. Metode ini memvalidasi masukan, menyelesaikan renderer Artisan, mengirim HTML ke Chrome melalui Chrome DevTools Protocol (CDP), mengurai PDF yang dikembalikan, lalu menyematkan halaman 0 sebagai Form XObject pada posisi kursor saat ini. Chrome berjalan sebagai proses anak dari worker PHP. Jembatan ini mengendalikan Chrome melalui CDP, bukan terhubung ke proses Chrome terpisah melalui port debugging, sehingga tidak ada endpoint jaringan yang perlu diekspos atau diautentikasi.
Jembatan ini merender dengan postur jaringan yang menolak secara bawaan. Setiap render menggunakan Content-Security-Policy yang menolak semua origin sumber daya (default-src 'none') dan hanya mengizinkan gambar inline (img-src data:). Jembatan ini juga memblokir setiap URL subsumber daya pada lapisan transport CDP dengan Network.setBlockedURLs(['*']). Akibatnya, gambar, lembar gaya, fon, skrip, atau iframe jarak jauh dalam HTML Anda tidak dimuat. Sematkan setiap aset secara inline sebagai URI data:. Dengan cara ini, jembatan menangani risiko server-side request forgery (SSRF) ketika merender HTML yang mungkin tidak tepercaya, dan berlaku apa pun konfigurasinya.
Model ukuran halaman memiliki dua mode. Jika Anda menyediakan lebar dan tinggi dalam satuan poin PDF, Chrome mencetak persis pada ukuran kertas tersebut. Jika tinggi dihilangkan atau null, jembatan mengukur tinggi konten yang dirender di Chrome, mengonversinya ke poin, lalu menambahkan buffer pengaman reflow kecil sekitar 14.4 poin. Ini mencegah printToPDF meluber ke halaman kedua, yang akan dipotong oleh pengimpor karena hanya membaca halaman 0.
Permukaan API
Bagian berjudul “Permukaan 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 adalah satu-satunya permukaan konfigurasi. Objek ini bersifat tetap, jadi buat instance baru untuk mengubah suatu nilai. ChromeRenderResult::getPdfData() mengembalikan bita PDF. Halaman konfigurasi Artisan yang ditautkan di bagian Lihat juga mencantumkan referensi opsi lengkap dan flag peluncuran Chrome yang tetap.
Contoh kode — Mulai cepat
Bagian berjudul “Contoh kode — Mulai cepat”Lampirkan konfigurasi ke dokumen, render HTML tepercaya, lalu simpan.
<?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 menangani tata letak flex, dan angka-angkanya tetap dapat dipilih pada keluaran karena halaman disematkan sebagai Form XObject vektor, bukan gambar raster. Untuk mencocokkan ukuran halaman A4 tetap, berikan lebar dan tinggi dalam satuan poin.
$document->writeHtmlChrome($html, width: 595.28, height: 841.89);Contoh kode — Produksi
Bagian berjudul “Contoh kode — Produksi”Dalam produksi, buat satu renderer per worker, suntikkan logger PSR-3, tangkap dua jenis eksepsi berbeda secara terpisah, dan lepaskan proses Chrome secara deterministik saat shutdown.
<?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(); }}Buat renderer sekali, lalu gunakan ulang. Browser pool yang mendasarinya menjaga satu proses Chrome tetap aktif dan memulai ulangnya setiap 100 render untuk membatasi pertumbuhan memori. Kedua cabang catch memisahkan kegagalan deployment, seperti runtime yang hilang, dari kegagalan saat render yang dapat Anda coba ulang sekali. Tidak ada blok catch yang kosong. Panggil shutdown() ketika worker melakukan shutdown untuk melepaskan proses Chrome, bukan menunggu destructor.
Buat konfigurasi dari array konfigurasi framework untuk menggunakan kunci snake-case, dan tetapkan chromeBinaryPath dalam produksi agar biner yang dipakai deterministik.
Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”- HTML kosong adalah no-op.
writeHtmlChrome('')mengembalikan dokumen tanpa perubahan. - Belum ada halaman. Jika dokumen tidak memiliki halaman,
writeHtmlChrome()menambahkan satu sebelum merender. - Aset jarak jauh tidak dimuat — sesuai desain.
<img src="https://...">dirender kosong. Sematkan setiap aset secara inline sebagai URIdata:. Ini adalah postur isolasi jaringan, bukan cacat. - Hanya halaman 0 yang diimpor. Penyesuaian tinggi otomatis menambahkan buffer reflow sehingga satu halaman dihasilkan. Dengan tinggi eksplisit, tidak ada buffer yang ditambahkan dan keluaran dicocokkan persis dengan ukuran kertas yang diminta, jadi tetapkan tinggi agar sesuai dengan konten Anda.
- Jembatan hilang. Jika
nextpdf/artisantidak terpasang, inti memunculkan eksepsi tata letak alih-alih galat fatal. Jika pustakachrome-php/chrometidak ada, jembatan memunculkanChromeNotAvailableExceptionbeserta perintah instalasinya. defaultCssdan</style>. Setiap urutan</style>dalamdefaultCssdihapus sebelum injeksi sebagai pertahanan terhadap pelolosan dari blok gaya (style-breakout). Perhitungkan hal ini jika Anda membuat templat CSS.
Kinerja
Bagian berjudul “Kinerja”Render pertama menanggung biaya startup dan tata letak Chrome. Render berikutnya menggunakan ulang proses Chrome yang aktif, sehingga biasanya tidak menanggung biaya startup. Buat satu renderer per worker dan gunakan ulang. Jangan membuat satu renderer per permintaan. Perkirakan lonjakan latensi pada setiap render ke-100, ketika jembatan memulai ulang proses Chrome untuk membatasi memori. Masukkan hal itu ke target latensi Anda, bukan memperlakukannya sebagai insiden. Selaraskan renderTimeout dengan anggaran permintaan upstream pada jalur mana pun yang dapat dijangkau oleh masukan tidak tepercaya.
Catatan keamanan
Bagian berjudul “Catatan keamanan”- Isolasi jaringan adalah kendali utama. Jembatan ini sama sekali tidak mengizinkan pengambilan subsumber daya keluar: CSP
default-src 'none'ditambah pemblokiran setiap URL pada lapisan transport CDP. Jembatan ini tidak menerapkan daftar izin domain karena memang tidak memerlukannya. Sematkan aset secara inline sebagai URIdata:. - Masukan dibatasi sebelum Chrome dihubungi. Jembatan ini menolak HTML yang melebihi
maxHtmlSize(standar 5 MB), URI data base64 yang terlalu besar (pengaman terhadap bom dekompresi), dan tag<meta http-equiv="refresh">apa pun (yang dapat mengarahkan navigasi ke endpoint internal). PertahankanmaxHtmlSizepada nilai standar kecuali ada beban kerja yang diketahui membutuhkan nilai lebih tinggi. Menaikkannya memperluas permukaan risiko kehabisan sumber daya. - Sandbox Chrome adalah kendali terpisah. Menyetel
noSandbox: truemeluncurkan Chrome dengan--no-sandbox, yang menghapus isolasi proses Chrome. Ini adalah penurunan kontainmen yang nyata, bukan flag kosmetik. Biarkan tetapfalsedi luar kontainer. Ketika sandbox kontainer tidak dapat diinisialisasi, jalankan Chrome sebagai pengguna non-root dalam kontainer yang dibatasi, dan perlakukan deployment itu sebagai kondisi yang mengharuskan tingkat kepercayaan lebih tinggi terhadap masukan. - Log hanya membawa metadata. Suntikkan logger PSR-3. Jembatan ini mencatat panjang bita, dimensi, dan peristiwa siklus hidup, tetapi tidak pernah mencatat HTML, bita PDF, atau teks yang diekstrak.
- Jangan pernah mengekspos port remote-debugging Chrome. Jembatan ini tidak menggunakannya, dan port CDP yang terbuka adalah saluran kendali tanpa autentikasi.
Model ancaman lengkap, termasuk pertahanan SSRF, batas sandbox eksplisit, dan katalog mode kegagalan, terdapat pada halaman security-and-operations Artisan yang ditautkan di bawah Lihat juga. Halaman itu menetapkan klausul OWASP, CWE, dan NIST yang relevan.
Kesesuaian
Bagian berjudul “Kesesuaian”Panduan ini tidak membuat klaim standar normatif tersendiri. Halaman security-and-operations Artisan upstream memetakan kendali jaringan, isolasi, dan penghabisan sumber daya jembatan ke OWASP ASVS, CWE Top 25 (SSRF / konsumsi sumber daya tak terkendali), dan NIST SP 800-53 SC-7. Halaman cookbook ini menyatakan kembali penggunaannya dan menyerahkan kutipan normatif tersebut ke halaman itu. Jembatan ini tidak melakukan operasi kriptografi apa pun; penandatanganan dan enkripsi adalah urusan inti atau edisi komersial dan tidak terpengaruh oleh Artisan.
Lihat juga
Bagian berjudul “Lihat juga”- Merender di edge dengan Cloudflare — render HTML di edge dengan fallback lokal.
- Quickstart Artisan — render pertama yang minimal.
- Penyiapan renderer Chrome — sediakan biner, keputusan sandbox kontainer, dan probe kesehatan.
- Keamanan dan operasi Artisan — model isolasi jaringan, batas sandbox, dan mode kegagalan.