Salta ai contenuti

Artisan in produzione

In produzione, iniettare un renderer configurato e un logger PSR-3, riutilizzare il processo Chrome attivo tra un rendering e l’altro, fornire altezze esplicite per i documenti multi-elemento e delimitare il percorso di rendering con un timeout a monte.

BrowserPool mantiene attivo un solo processo Chrome (keepAlive: true) e lo riavvia ogni 100 rendering per limitare la crescita della memoria, un pattern di accumulo noto nei client CDP a lunga durata. Per un worker che esegue il rendering di molti documenti, è preferibile un unico renderer a lunga durata anziché uno per richiesta, in modo da pagare di rado il costo di avvio di Chrome.

render-service.php
<?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 $e) {
// Deployment fault: Chrome runtime missing. Page the on-call owner.
throw $e;
} catch (ChromeRenderException $e) {
// Render-time fault: timeout, crash, empty output. Retryable once.
throw $e;
}
}
public function shutdown(): void
{
$this->renderer->close();
}
}

Costruire il renderer una sola volta e riutilizzarlo. Chiamare close() all’arresto del worker per rilasciare il processo Chrome in modo deterministico, anziché attendere il distruttore. I due rami catch distinguono un guasto di deployment (runtime mancante) da un guasto in fase di rendering (da ritentare). Non vengono utilizzati blocchi catch vuoti.

Registrarlo in un container come singleton:

$container->singleton(ReportRenderer::class, fn ($c) =>
new ReportRenderer($c->get(Psr\Log\LoggerInterface::class)));

Quando l’altezza viene omessa, il bridge misura l’altezza del contenuto in Chrome (max delle altezze di scroll e offset di body/document), la converte in punti e aggiunge un buffer di sicurezza di circa 0,2 pollici (~14,4 pt). Il buffer assorbe la differenza tra il layout del viewport di Chrome e il reflow del layout di stampa. Senza di esso, printToPDF può sforare su una seconda pagina che PageImporter (solo pagina 0) ritaglierebbe. Viene imposta un’altezza minima del foglio di 0,1 pollici. I test ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault, ::renderAutoFitBufferIsAddedNotSubtracted e ::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight verificano questo comportamento.

Per i documenti a layout fisso (fatture, certificati), passare un’altezza esplicita in punti. Quando l’altezza è esplicita, non viene aggiunto alcun buffer e l’output corrisponde esattamente al formato del foglio richiesto (verificato da ::renderHonorsExplicitHeightWithoutAutoBuffer).

  • Costruire un renderer per worker; riutilizzarlo. BrowserPool riutilizza il browser attivo e si riavvia automaticamente al raggiungimento dei 100 rendering.
  • Chiamare close() all’arresto del worker e tra batch di grandi dimensioni se si desidera un nuovo processo Chrome prima del limite dei 100 rendering.
  • Il distruttore chiama close(), ma una chiamata esplicita a close() è deterministica ed è preferibile nei processi a lunga durata.
  • Gli avvisi di riavvio vengono registrati a livello notice insieme al conteggio dei rendering; impostare un alert in caso di frequenza di riavvio elevata, che indica documenti più pesanti del previsto.

Iniettare un logger PSR-3. Eventi emessi e relativi livelli:

EventoLivelloContesto
Inizio renderingdebugsize, width, height
Rendering completatodebugpdfSize, contentHeight
Avvio del browserinfobinary
Riavvio del browsernoticecount
Chiusura del browserdebugrenderCount

Non vengono registrati né HTML né byte PDF né testo estratto, in linea con le indicazioni di NIST SP 800-92 sul mantenere i payload fuori dai log operativi. Costruire gli SLO di latenza a partire dalla coppia start/complete e un alert sulla frequenza di riavvio a partire dagli eventi notice.

  • Chrome sidecar: eseguire Chrome nello stesso container del worker PHP; impostare chrome_binary. Effettuare il provisioning di un container in grado di supportare la sandbox — vedere /integrations/artisan/chrome-renderer-setup/.
  • Senza container / CLI: Artisan non dispone di un container DI. Utilizzare EInvoiceServiceFactory per i contratti di fatturazione elettronica Premium nei runner CLI; vedere /integrations/artisan/boot-and-discovery/.
  • Delimitazione delle risorse: abbinare render_timeout a un budget di richiesta a monte e a un cgroup/ulimit dell’host. Consultare il modello delle minacce su /integrations/artisan/security-and-operations/.
  • Un renderer interrotto durante il rendering chiude comunque la pagina Chrome (finally); il pool rimane utilizzabile.
  • Il riutilizzo di un singolo renderer tra threads/processes non è supportato; un renderer possiede un solo processo Chrome.
  • Il riavvio ogni 100 rendering è fisso; dimensionare i batch tenendone conto per mantenere prevedibili i picchi di latenza.

Il costo a regime è dato dal layout dell’input in Chrome più printToPDF, non dall’overhead del bridge. Il costo di avvio è ammortizzato da keepAlive. Prevedere un picco di latenza a ogni centesimo rendering (riavvio del processo) — evidenziarlo negli SLO anziché trattarlo come un incidente.

I percorsi di produzione sono quelli in cui arriva HTML non attendibile. Rileggere /integrations/artisan/security-and-operations/. Le barriere di rete restano efficaci indipendentemente dalla configurazione, ma no_sandbox: true rimuove l’isolamento del processo Chrome e aumenta il livello di attendibilità richiesto all’input.

Nei worker senza container, EInvoiceServiceFactory restituisce null quando Premium non è installato, quindi il percorso di rendering open source continua a essere eseguito senza modifiche; installare Pro/Enterprise per abilitare l’incorporamento e la convalida della fattura elettronica nel documento generato.

  • /integrations/artisan/quickstart/
  • /integrations/artisan/configuration/
  • /integrations/artisan/security-and-operations/
  • /integrations/artisan/chrome-renderer-setup/
  • /integrations/artisan/troubleshooting/