Uso in produzione — fallback, telemetria, archiviazione e protezione
In sintesi
Sezione intitolata “In sintesi”Questa pagina tratta quattro aspetti di produzione gestiti dal pacchetto oltre al semplice rendering: fallback locale, telemetria edge, archiviazione su R2 e livello di protezione delle API in ingresso. Ogni sezione descrive un comportamento verificato nelle classi.
Fallback locale
Sezione intitolata “Fallback locale”Quando il Worker non è raggiungibile e fallbackToLocal è true, il bridge delega a un renderer locale. Fornire il renderer locale tramite LocalRendererFactoryInterface. Il bridge chiama la factory in modalità lazy, quindi il metodo create() della factory viene eseguito solo nel percorso di fallback.
<?php
declare(strict_types=1);
use NextPDF\Cloudflare\Contract\LocalRendererFactoryInterface;use NextPDF\Cloudflare\Contract\LocalRendererInterface;
final class ArtisanLocalRendererFactory implements LocalRendererFactoryInterface{ public function __construct( private readonly \NextPDF\Artisan\ChromeHtmlRenderer $chrome, ) {}
public function create(): LocalRendererInterface { return new readonly class($this->chrome) implements LocalRendererInterface { public function __construct( private \NextPDF\Artisan\ChromeHtmlRenderer $chrome, ) {}
/** @param array<string, mixed> $options */ public function render(string $html, array $options = []): string { // Delegate to the local Chrome renderer; return raw PDF bytes. return $this->chrome->renderToString($html, $options); } }; }}Collegare la factory al renderer:
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);Quando viene eseguito il fallback, il valore renderLocation del risultato è la stringa letterale local e heightPt è 0.0. Il percorso locale non riporta una posizione edge né un’altezza misurata. Il bridge passa al renderer locale la larghezza richiesta tramite la chiave di opzione widthPt.
Logica di decisione del fallback
Sezione intitolata “Logica di decisione del fallback”La logica, letta direttamente da CloudflareHtmlRenderer, è la seguente:
| Situazione | Esito |
|---|---|
Configurazione incompleta, fallbackToLocal: false | CloudflareNotAvailableException |
Configurazione incompleta, fallbackToLocal: true, factory collegata | Rendering locale |
| Il Worker genera un errore di trasporto, fallback abilitato, factory collegata | Rendering locale, con log a livello warning e poi info |
| Il Worker genera un’eccezione, fallback abilitato, Artisan installato, nessuna factory | CloudflareNotAvailableException che indica la factory mancante |
| Il Worker genera un’eccezione, fallback abilitato, Artisan non installato | CloudflareNotAvailableException che indica il pacchetto mancante |
| Il Worker restituisce un errore HTTP / un corpo malformato | CloudflareRenderException, non ricorre mai al fallback |
L’ultima riga rappresenta la distinzione fondamentale. Un Worker che risponde con un errore costituisce un guasto del rendering, non un problema di raggiungibilità. L’errore viene rilanciato, in modo che il codice possa distinguere un rendering interrotto da un edge irraggiungibile.
Telemetria edge
Sezione intitolata “Telemetria edge”Ogni rendering riuscito nel percorso binario include telemetria derivata dagli header della risposta:
$result = $renderer->render($html);
$logger->info('edge render', [ 'edge' => $result->renderLocation, // e.g. 'TPE', 'NRT' 'render_time_ms' => $result->renderTimeMs, 'content_px' => $result->contentHeightPx, 'pdf_bytes' => $result->size(),]);Il renderer legge renderLocation dall’header di risposta CF-Ray, usando il segmento dopo il trattino finale. Per CF-Ray: 8abc123def456-TPE la posizione è TPE. Quando l’header è assente, la posizione è una stringa vuota. Nel percorso di risposta JSON il valore proviene invece dal campo JSON renderLocation. Considerare questi valori come segnali di osservabilità emessi dal Worker, non come garanzie della piattaforma.
Archiviazione su R2
Sezione intitolata “Archiviazione su R2”R2ArchiveManager carica i byte del PDF su Cloudflare R2 tramite l’API compatibile con S3, firmando le richieste con AWS Signature V4.
use NextPDF\Cloudflare\R2ArchiveConfig;use NextPDF\Cloudflare\R2ArchiveManager;
$r2 = new R2ArchiveManager( config: new R2ArchiveConfig( bucketName: 'pdf-archive', accountId: getenv('CF_ACCOUNT_ID') ?: '', accessKeyId: getenv('R2_ACCESS_KEY_ID') ?: '', secretAccessKey: getenv('R2_SECRET_ACCESS_KEY') ?: '', pathPrefix: 'invoices/', ), httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory,);
$upload = $r2->upload($result->pdfData, 'invoice-2026-0042.pdf', [ 'tenant' => 'acme',]);
if (!$upload->success) { $logger->error('r2 upload failed', ['error' => $upload->error]);}Comportamento verificato in R2ArchiveManager e R2ObjectKey:
- La chiave dell’oggetto è partizionata per data:
<pathPrefix><Y>/<m>/<d>/<sanitized-filename>, ad esempioinvoices/2026/05/18/invoice-2026-0042.pdf. - Il nome del file viene sanificato: viene applicato
basename()(rimozione del path traversal), quindi vengono eliminati i byte nulli e i caratteri di controllo (\x00–\x1f,\x7f). Se il risultato è vuoto, diventadocument.pdf. - I metadati personalizzati vengono inviati come header
x-amz-meta-<lowercased-key>, inclusi nell’insieme degli header firmati V4. - Un upload superiore a
maxFileSizeBytes(predefinito104857600) viene rifiutato prima di qualsiasi richiesta, restituendo unR2UploadResultconsuccess: false. R2UploadResult::isValid()richiedesuccess, unakeynon vuota e unetagnon vuoto.
URL di download pre-firmati
Sezione intitolata “URL di download pre-firmati”$url = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);generateSignedUrl() costruisce un URL GET firmato in query con AWS Signature V4 e un X-Amz-Expires configurabile (predefinito 3600 secondi). La richiesta canonica utilizza il valore sentinella dell’hash del contenuto UNSIGNED-PAYLOAD. Un URL di lettura firmato in query utilizza questa forma perché il corpo non fa parte della richiesta firmata. Questo descrive il comportamento di firma implementato dal pacchetto, come risulta da R2ArchiveManager. La documentazione del servizio Amazon definisce AWS Signature Version 4, non uno standard SDO, perciò qui non viene citata alcuna clausola normativa. Le chiavi di accesso agli oggetti sono #[SensitiveParameter]; tenerle fuori dai log.
URL pubblici
Sezione intitolata “URL pubblici”R2UploadResult::publicUrl($customDomain) restituisce la sola chiave quando non viene fornito alcun dominio, oppure https://<domain>/<key> in caso contrario. Forza uno schema HTTPS quando il dominio fornito ne è privo. Non rende pubblico un bucket privato. È un aspetto della configurazione del bucket R2.
Protezione delle API in ingresso
Sezione intitolata “Protezione delle API in ingresso”ApiProtection è il livello da applicare alle richieste di rendering che arrivano a un gateway PHP davanti al Worker. Esegue tre controlli in un ordine fisso: chiave API, quindi dimensione del payload, quindi limite di frequenza.
use NextPDF\Cloudflare\ApiKeyValidator;use NextPDF\Cloudflare\ApiProtection;use NextPDF\Cloudflare\ApiProtectionConfig;
$protection = new ApiProtection( config: new ApiProtectionConfig( maxRequestsPerMinute: 30, maxRequestsPerHour: 500, maxPayloadSizeBytes: 5_000_000, requireApiKey: true, ), keyValidator: new ApiKeyValidator([getenv('GATEWAY_API_KEY') ?: '']),);
$decision = $protection->checkRequest( clientId: $clientIp, payloadSize: strlen($requestBody), apiKey: $request->getHeaderLine('X-Api-Key'),);
if (!$decision->allowed) { http_response_code(429); foreach ($decision->toHeaders() as $name => $value) { header("{$name}: {$value}"); } echo $decision->denialReason; exit;}Comportamento verificato:
- L’ordine è chiave API → dimensione del payload → limite di frequenza. Il primo controllo che fallisce interrompe subito la valutazione con un
denialReasonspecifico. ApiKeyValidator::validate()usahash_equals()per un confronto a tempo costante e rifiuta una chiave vuota.validateHashed()esegue il confronto con hash SHA-256 per archiviare le chiavi a riposo. I parametri delle chiavi recano#[SensitiveParameter].- Lo store del limite di frequenza è in memoria per processo. Tiene traccia di una finestra al minuto (
rateLimitWindowSeconds, predefinito60) e di una finestra all’ora (fissa a3600secondi). Non persiste tra i worker né tra i riavvii. Per applicare un limite condiviso tra processi, anteporre uno store condiviso. ApiProtectionResult::toHeaders()aggiunge sempreX-Content-Type-Options: nosniffeX-Frame-Options: DENY, e unisce gli header del limite di frequenza (X-RateLimit-Remaining,X-RateLimit-Reset, oltre aRetry-Afterquando la richiesta viene negata).
Rendering e poi firma
Sezione intitolata “Rendering e poi firma”Questo bridge non firma i PDF. Una pipeline di firma di produzione esegue il rendering all’edge, quindi firma i byte restituiti con il motore scelto:
render()→CloudflareRenderResult::$pdfData.- Passare
$pdfDataanextpdf/core(oppure NextPDF Pro per la firma PAdES B-B). I profili di validazione a lungo termine sono una funzionalità Enterprise; questo bridge core non dichiara il supporto di nessuna delle due funzionalità.
Mantenere la fase di firma nel proprio processo, in modo che la chiave di firma non oltrepassi mai il confine dell’edge.
Vedere anche
Sezione intitolata “Vedere anche”- /integrations/cloudflare/security-and-operations/ — pinning, difesa SSRF, rotazione dei segreti, runbook operativo.
- /integrations/cloudflare/troubleshooting/ — catalogo delle modalità di errore.
- /integrations/cloudflare/configuration/ — ogni campo e valore predefinito.