HTML naar PDF renderen met de Artisan-Chrome-renderer
In een oogopslag
Sectie met titel “In een oogopslag”De Artisan-bridge rendert HTML met een headless Chrome-proces en importeert het resultaat vervolgens als een vector-Form-XObject in een NextPDF-document. De tekst blijft selecteerbaar en doorzoekbaar in plaats van te worden gerasterd. U koppelt een ChromeRendererConfig, roept writeHtmlChrome() aan op een document of gebruikt ChromeHtmlRenderer rechtstreeks, waarna Chrome de lay-out verzorgt. Deze handleiding behandelt de renderaanroep, netwerkisolatie, paginaformaat, contenthoogte en de levenscyclus van een langlevende renderer in een worker.
De vereisten vooraf:
- NextPDF core en
nextpdf/artisanzijn geïnstalleerd. - Er is een Chrome- of Chromium-binary geïnstalleerd en de worker-gebruiker kan die headless uitvoeren. Controleer dit met
chromium --headless --dump-dom about:blankvoordat u begint. De pagina over het opzetten van de Chrome-renderer, waarnaar onder Zie ook wordt gelinkt, behandelt het beschikbaar stellen van de binary en de keuze voor de container-sandbox.
Deze handleiding gaat ervan uit dat u een Chrome-proces dicht bij de applicatie kunt uitvoeren. Lees voor het eerste uitvoerbare voorbeeld de Artisan-quickstart.
Installatie
Sectie met titel “Installatie”Installeer de bridge naast core.
composer require nextpdf/artisanInstalleer een Chrome- of Chromium-build die de worker-gebruiker kan uitvoeren. Gebruik op Debian of Ubuntu het distributiepakket.
apt-get install -y chromiumControleer dat de binary headless draait als de worker-gebruiker.
chromium --headless --dump-dom about:blankExitcode 0 met een leeg Document Object Model (DOM) betekent dat de binary en de bijbehorende gedeelde libraries aanwezig zijn. Een exitcode die niet nul is, levert dezelfde fout op die de bridge rapporteert als een ChromeRenderException. Los dit hier eerst op.
Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”writeHtmlChrome() is een methode op de Document van NextPDF core. De methode valideert de invoer, resolvet de Artisan-renderer, stuurt de HTML naar Chrome via het Chrome DevTools Protocol (CDP), parseert de teruggegeven PDF en sluit pagina 0 in als een Form XObject op de huidige cursorpositie. Chrome draait als een onderliggend proces van de PHP-worker. De bridge bestuurt Chrome via CDP in plaats van verbinding te maken met een afzonderlijk Chrome-proces via een debugpoort, dus er is geen netwerk-endpoint dat blootgesteld of geauthenticeerd hoeft te worden.
De bridge rendert met een netwerkbeleid dat standaard alles weigert. Elke render gebruikt een Content-Security-Policy die alle resource-origins weigert (default-src 'none') en alleen inline-afbeeldingen toestaat (img-src data:). De bridge blokkeert daarnaast elke subresource-URL op de CDP-transportlaag met Network.setBlockedURLs(['*']). Daardoor wordt een externe afbeelding, stylesheet, lettertype, script of iframe in uw HTML niet geladen. Neem elke asset inline op als een data:-URI. Zo pakt de bridge het risico van server-side request forgery (SSRF) aan wanneer deze HTML rendert die mogelijk niet vertrouwd kan worden, en dit geldt ongeacht de configuratie.
Het model voor paginaformaat heeft twee modi. Wanneer u zowel breedte als hoogte in PDF-punten opgeeft, drukt Chrome af op precies dat papierformaat. Wanneer de hoogte wordt weggelaten of null is, meet de bridge de gerenderde contenthoogte in Chrome, zet die om naar punten en voegt een kleine veiligheidsmarge voor reflow van ongeveer 14,4 punten toe. Dat voorkomt dat printToPDF overloopt naar een tweede pagina die de importer, die alleen pagina 0 verwerkt, zou afkappen.
API-oppervlak
Sectie met titel “API-oppervlak”// 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 is het enige configuratieoppervlak. Het object is onveranderlijk, dus bouw een nieuwe instantie om een waarde te wijzigen. ChromeRenderResult::getPdfData() geeft de PDF-bytes terug. De Artisan-configuratiepagina, waarnaar onder Zie ook wordt gelinkt, bevat de volledige optiereferentie en de vaste Chrome-startvlaggen.
Codevoorbeeld — Snelstart
Sectie met titel “Codevoorbeeld — Snelstart”Koppel de config aan een document, render vertrouwde HTML en sla het resultaat op.
<?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 verzorgt de flex-lay-out en de cijfers blijven selecteerbaar in de uitvoer, omdat de pagina als een vector-Form-XObject wordt ingesloten en niet als een rasterafbeelding. Geef breedte en hoogte in punten op om op een vaste A4-pagina te passen.
$document->writeHtmlChrome($html, width: 595.28, height: 841.89);Codevoorbeeld — Productie
Sectie met titel “Codevoorbeeld — Productie”Maak in productie één renderer per worker, injecteer een PSR-3-logger, vang de twee verschillende exceptietypen afzonderlijk af en geef het Chrome-proces deterministisch vrij bij het afsluiten.
<?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(); }}Bouw de renderer één keer en hergebruik deze vervolgens. De onderliggende browserpool houdt één Chrome-proces in leven en herstart het elke 100 renders om de geheugengroei te begrenzen. De twee catch-takken scheiden een implementatiefout, zoals een ontbrekende runtime, van een renderfout waarbij u het eenmaal opnieuw kunt proberen. Geen van beide catch-blokken is leeg. Roep shutdown() aan wanneer de worker wordt afgesloten om het Chrome-proces vrij te geven in plaats van te wachten op de destructor.
Bouw de config op vanuit een framework-configuratiearray om snake-case-sleutels te gebruiken, en pin chromeBinaryPath vast in productie, zodat deterministisch vastligt welke binary wordt gebruikt.
Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”- Lege HTML doet niets.
writeHtmlChrome('')geeft het document ongewijzigd terug. - Nog geen pagina. Als het document geen pagina heeft, voegt
writeHtmlChrome()er een toe vóór het renderen. - Externe assets worden niet geladen — met opzet.
<img src="https://...">wordt leeg gerenderd. Neem elke asset inline op als eendata:-URI. Dit is het netwerkisolatiebeleid, geen defect. - Alleen pagina 0 wordt geïmporteerd. Bij automatisch passende hoogte wordt de reflow-marge toegevoegd, zodat één pagina wordt geproduceerd. Bij een expliciete hoogte wordt geen marge toegevoegd en komt de uitvoer exact overeen met het opgevraagde papierformaat; stem de hoogte dus af op uw content.
- Bridge ontbreekt. Als
nextpdf/artisanniet is geïnstalleerd, werpt core een lay-out-exceptie op in plaats van een fatale fout. Als de librarychrome-php/chromeontbreekt, werpt de bridge eenChromeNotAvailableExceptionop met het installatiecommando. defaultCssen</style>. Elke</style>-reeks indefaultCsswordt vóór injectie verwijderd als verdediging tegen een style-breakout. Houd daar rekening mee wanneer u CSS via templates samenstelt.
Prestaties
Sectie met titel “Prestaties”De eerste render betaalt de kosten voor het opstarten van Chrome en voor de lay-out. Latere renders hergebruiken het draaiende Chrome-proces, dus betalen ze zelden de opstartkosten. Maak één renderer per worker en hergebruik deze. Maak er niet één per verzoek aan. Verwacht een latentiepiek bij elke 100e render, wanneer de bridge het Chrome-proces herstart om het geheugen te begrenzen. Houd daar rekening mee in uw latentiedoelstellingen in plaats van het als een incident te behandelen. Combineer renderTimeout met een upstream verzoekbudget op elk pad dat bereikbaar is via niet-vertrouwde invoer.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”- Netwerkisolatie is de primaire maatregel. De bridge staat geen enkele uitgaande subresource-fetch toe: CSP
default-src 'none'plus een blokkering van elke URL op CDP-transportniveau. De bridge implementeert geen domein-allowlist, omdat die niet nodig is. Neem assets inline op alsdata:-URI’s. - De invoer wordt begrensd voordat Chrome wordt aangesproken. De bridge weigert HTML boven
maxHtmlSize(standaard 5 MB), een te grote base64-data-URI (een maatregel tegen een decompressiebom) en elke<meta http-equiv="refresh">-tag (die een navigatie naar een intern endpoint zou kunnen aansturen). HoudmaxHtmlSizeop de standaardwaarde, tenzij een bekende werklast meer nodig heeft. Het verhogen ervan vergroot het aanvalsoppervlak voor resource-uitputting. - De Chrome-sandbox is een afzonderlijke maatregel. Door
noSandbox: truein te stellen start Chrome met--no-sandbox, waardoor de procesisolatie van Chrome wordt verwijderd. Dat is een werkelijke vermindering van de isolatie, geen cosmetische vlag. Laat het buiten containers opfalsestaan. Wanneer de container-sandbox niet kan initialiseren, voer Chrome dan uit als een niet-root-gebruiker in een afgeschermde container, en behandel de implementatie als een scenario waarin de invoer meer vertrouwen vereist. - Logs bevatten alleen metadata. Injecteer een PSR-3-logger. De bridge logt bytelengtes, afmetingen en levenscyclusgebeurtenissen, nooit HTML, PDF-bytes of geëxtraheerde tekst.
- Stel nooit een Chrome-remote-debuggingpoort bloot. De bridge gebruikt er geen en een open CDP-poort is een niet-geauthenticeerd controlekanaal.
Het volledige dreigingsmodel, inclusief de SSRF-verdediging, de expliciete sandboxgrens en de catalogus van faalmodi, staat op de Artisan-pagina over beveiliging en operations, gelinkt onder Zie ook. Die pagina legt de relevante OWASP-, CWE- en NIST-clausules vast.
Conformiteit
Sectie met titel “Conformiteit”Deze handleiding doet zelf geen normatieve standaardenclaim. De bovenliggende Artisan-pagina over beveiliging en operations koppelt de netwerk-, isolatie- en resource-uitputtingsmaatregelen van de bridge aan OWASP ASVS, de CWE Top 25 (SSRF / ongecontroleerd resourceverbruik) en NIST SP 800-53 SC-7. Deze cookbookpagina herhaalt het gebruik en laat die normatieve citaties aan die pagina over. De bridge voert geen cryptografische bewerking uit; ondertekening en versleuteling vallen onder core of de commerciële editie en worden niet beïnvloed door Artisan.
Zie ook
Sectie met titel “Zie ook”- Renderen aan de edge met Cloudflare — render HTML aan de edge met lokale fallback.
- Artisan-quickstart — de minimale eerste render.
- Chrome-renderer opzetten — de binary beschikbaar stellen, de keuze voor de container-sandbox en een health-probe.
- Artisan-beveiliging en operations — het netwerkisolatiemodel, de sandboxgrens en de faalmodi.