node-print
node-print
In sintesi
Motore di stampa headless del tenant ditta: trasforma HTML arbitrario in documenti PDF o immagini raster (PNG, JPEG, WebP) tramite un pool di istanze Chromium pilotate da Puppeteer. Espone API sincrone per generazioni leggere, API batch per fan-out concorrenti, API asincrone con job tracking e progress in SSE per stampe lunghe, e una destinazione webhook per consegnare l'artefatto a un sistema terzo. Le sue uscite tipiche sono fatture, bolle di consegna, etichette, report e ricevute, che a monte vengono assemblate come HTML da node-renderer e a valle vengono archiviate su node-storage o trasmesse via webhook.
Funzionalità principali
- Generazione PDF da HTML con controllo di formato (
A4,A5,Letter, …), orientamento, margini, header/footer template, sfondo e CSS print - Generazione immagini (PNG, JPEG, WebP) con viewport configurabile, qualità, ritaglio e full-page screenshot — pensata per etichette, badge e anteprime
- Modalità output triplici: stream binario (
generate-pdf), payload Base64 incapsulato in JSON (generate-pdf-base64), oppure consegna webhook in formatomultipart/form-datao JSON con buffer base64 - Batch processing con concurrency manager: una singola richiesta accetta N pagine HTML e restituisce un manifest dei file generati, con stato per item e gestione del fallimento parziale
- Job asincroni con coda interna, rate limiting per utente, retry configurabili, statistiche aggregate e notifiche SSE/WebSocket di avanzamento (
/jobs/:id/status,/jobs/statistics) - Template validation e HTML toolkit: endpoint dedicati per preview, minify, clean, analisi e ottimizzazione print del markup prima della stampa
- Browser pool: istanze Chromium riutilizzate, contesto isolato per richiesta, timeout
waitUntilconfigurabile (domcontentloadeddi default,networkidle0per template con asset esterni) - Integrazione orchestrator: lo stesso servizio si registra come modulo verso
node-orchestratoresponendo i propri task come step di workflow
Architettura
Stack: Fastify v5 - Puppeteer (Chromium headless) - Inversify (DI con constructor injection) - @pzeta/fastify-utils (security, errorHandler, healthcheck, metrics, openapi, tracing, logging) - @pzeta/log per logging strutturato - @pzeta/orchestrator-node (ModuleSDK) per registrazione come modulo di workflow - axios + form-data per la consegna webhook - Zod per la validazione di request e response.
Layout DDD (src/):
| Layer | Contenuto |
|---|---|
domain/ | DocumentGenerationDomainService, DocumentRenderingService, HtmlOptimizationService, port IBrowserEnginePort, value object (JobResult, WebhookConfig, …), errori di dominio |
application/ | Facade DocumentGenerationService + servizi specializzati (DocumentGenerationApplicationService, DocumentMetricsApplicationService), BatchGenerationService con BatchConcurrencyManager, AsyncJobOrchestrationService + AsyncJobStorage, TemplateValidationService, HtmlOptimizationApplicationService, WebhookDeliveryService |
infrastructure/ | PuppeteerBrowserAdapter (implementazione di IBrowserEnginePort), BrowserPool, BrowserPageWrapper, OrchestratorAdapter con handler modulari (CoreHandlers, BatchHandlers, AsyncHandlers, TemplateHandlers, HtmlHandlers, ConfigHandlers), ServiceFactory (composition root Inversify), ConfigManagerAdapter |
presentation/ | DocumentsRoutes (sync), BatchRoutes, AsyncRoutes, TemplateRoutes, HtmlOptimizationRoutes, ConfigurationRoutes, controller DocumentGeneratorController, schemi Zod request/response |
Pattern adottati: Facade su DocumentGenerationService, Adapter sul browser engine, Object Pool sulle istanze Chromium, Strategy sulla WebhookDeliveryService (multipart vs JSON+Base64), Factory in ServiceFactory, Coordinator + Direct SDK Integration nell'OrchestratorAdapter.
Modello di rendering: il servizio è puramente HTML-driven, non interpreta DSL proprietarie. La separazione di competenze nel cluster è:
node-rendererriceve un template + dati e produce un blob HTML- il caller (frontend, orchestrator, scheduler) gira quell'HTML a
node-print node-printlancia una page Chromium, ne ottiene il PDF/immagine e lo restituisce inline, in webhook, o lo lascia raccogliere dal job storage- il caller archivia poi l'artefatto su
node-storage(o lo offre in download al browser)
Non esiste una chiamata diretta node-print → node-storage: il dipendency node-storage nel frontmatter modella il flusso end-to-end tipico, non un'invocazione HTTP del servizio.
Casi d'uso
- Stampa fattura singola: il frontend Vue genera l'HTML della fattura tramite
node-renderere inviaPOST /print/generate-pdfricevendo il PDF in streaming, pronto al download - Etichetta termica capi: l'app gestionale chiede
POST /print/generate-imagecon viewport ridotto e formato PNG per produrre etichette da inviare a stampanti di rete via driver lato client - Batch documenti di chiusura giornata: a fine turno il backend chiama
POST /print/batch/pdfscon N ricevute HTML; il servizio le genera in parallelo limitando la concorrenza, ritorna ilbatchIde l'esito di ciascun item viaGET /print/batch/:id/status - Stampa massiva da workflow:
node-orchestratoresegue un workflow di chiusura mese che, per ogni cliente, invoca i taskgenerate-pdfogenerate-pdf-base64dinode-printregistrati come modulo; il workflow inoltra poi gli output anode-storageper archiviazione fiscale - Generazione asincrona con notifica esterna:
POST /print/async/generate-pdfaccoda un job condestination: webhook; al termine ilWebhookDeliveryServiceconsegna il PDF (multipart o JSON+base64) all'URL del consumer, che riceve la notifica senza polling - Validazione preventiva del template: prima di stampare in produzione, l'integratore chiama
/print/templates/validate-quicko/print/html/analysisper individuare problemi (asset mancanti, CSS non print-safe, dimensioni eccessive)
Identità & esposizione
| Campo | Valore |
|---|---|
| Categoria | document-output |
| Versione cluster | 1.0.0 |
| Image | gitea.pzetatouch.it/pzeta_touch/node-print:1.0.28 |
| URL pubblico | https://ditta.pzeta.it/print |
| Path regex ingress | `/print(/ |
| Rewrite a backend | /$2 |
| DNS interno | node-print-ditta.ditta.svc.cluster.local:3000 |
| Auth nginx | auth_request → node-user-auth |
| Repository | node-print |
| Endpoint REST | 29 (vedi sezione "API reference") |
Endpoint operazionali
Endpoint convenzionali esposti da tutti i microservizi PZeta basati su @pzeta/fastify-utils:
| Path pubblico | Scopo |
|---|---|
https://ditta.pzeta.it/print/health | liveness probe |
https://ditta.pzeta.it/print/ready | readiness probe |
https://ditta.pzeta.it/print/metrics | metriche Prometheus |
https://ditta.pzeta.it/print/api-docs.json | spec OpenAPI runtime (richiede OPENAPI_EXPOSE_IN_PRODUCTION=true) |
https://ditta.pzeta.it/print/api-docs | Swagger UI (solo in NODE_ENV !== production) |
Configurazione
Variabili d'ambiente che un integratore deve conoscere (la lista completa è in src/infrastructure/config/schema.ts del repo):
| Variabile | Ruolo |
|---|---|
HOST / PORT | Bind di ascolto del processo Fastify (porta interna 3000) |
REVERSE_PROXY | A true quando il servizio è dietro nginx, per la corretta gestione di X-Forwarded-* |
ENABLE_CORS / CORS_ALLOWED_ORIGINS | Politica CORS in produzione |
PUPPETEER_EXECUTABLE_PATH | Override del binario Chromium (utile in container) |
PUPPETEER_TIMEOUT | Timeout massimo di rendering di una page |
PUPPETEER_ARGS | Flag aggiuntivi del browser (es. --no-sandbox in container) |
BROWSER_WAIT_UNTIL | Strategia di attesa caricamento page: domcontentloaded (default, più veloce), networkidle0 (richiesto se il template carica asset esterni) |
BASE64_MAX_FILE_SIZE / BASE64_MEMORY_THRESHOLD | Tetti di memoria per la modalità Base64; oltre il threshold il servizio rifiuta il job per non saturare l'heap |
WEBHOOK_MAX_BODY_BYTES | Dimensione massima del payload webhook recapitato (default 50 MB) |
ASYNC_JOBS_ENABLED / ASYNC_JOBS_MAX_CONCURRENT / ASYNC_JOBS_DEFAULT_TTL | Abilita la coda asincrona e ne limita concorrenza e durata |
ASYNC_JOBS_MAX_RETRIES / ASYNC_JOBS_RETRY_DELAY | Politica di retry sui job falliti |
ASYNC_JOBS_RATE_LIMIT_WINDOW / ASYNC_JOBS_RATE_LIMIT_MAX | Rate limit per utente sull'accodamento di nuovi job |
ASYNC_JOBS_NOTIFICATION_TYPE | Canale di notifica progress: sse (default) o websocket |
ORCHESTRATOR_ENABLED / ORCHESTRATOR_URL / ORCHESTRATOR_API_KEY | Se true, registra node-print come modulo presso node-orchestrator esponendo i task di stampa come step di workflow |
MODULE_ID / MODULE_NAME / MODULE_VERSION | Override del manifest pubblicato all'orchestrator (default presi da package.json) |
ENABLE_METRICS | Pubblica le metriche Prometheus su /metrics |
LOG_LEVEL | Livello di logging (debug / info / warn / error) |
Swagger UI è abilitato solo in NODE_ENV !== production. Lo spec OpenAPI runtime su /api-docs.json è esposto solo se OPENAPI_EXPOSE_IN_PRODUCTION=true.
Note eventing NATS
node-print non si sottoscrive a NATS in modo diretto: lo scanner statico riportato in nats.json non rileva publish o subscribe propri del servizio. La comunicazione asincrona avviene su due canali distinti, entrambi gestiti fuori dal codice del servizio:
- Modulo dell'orchestrator: quando
ORCHESTRATOR_ENABLED=true, l'OrchestratorAdapterusa@pzeta/orchestrator-node(ModuleSDK) per registrarsi pressonode-orchestrator. Le invocazioni dei task (generate-pdf,generate-image,generate-pdf-base64,generate-image-base64, batch, async, template, html, config) viaggiano su subject NATS interni all'SDK, derivati dal manifest del modulo, con request/reply tipizzati. Da fuori il servizio appare quindi come consumer di task dell'orchestrator, non come publisher di eventi di dominio. - Notifiche progress dei job asincroni: il canale è SSE (o WebSocket, selezionabile via
ASYNC_JOBS_NOTIFICATION_TYPE), non NATS. Il client si sottoscrive a/jobs/:id/statusper ricevere gli aggiornamenti incrementali.
Per consegnare l'artefatto a sistemi esterni node-print usa il WebhookDeliveryService su HTTP — anch'esso non passa per NATS. La tabella subject auto-renderizzata sotto sarà quindi vuota: è il comportamento atteso per questo servizio.
Dipendenze e dipendenti
Dipende da (servizi che questo servizio chiama):
Consumato da (chi chiama questo servizio):
frontend Vuenode-orchestrator
Infrastruttura (PostgreSQL, NATS, Redis, MinIO) non è elencata qui — vedi sezione Architettura del singolo servizio.