Uso en producción: respaldo, telemetría, archivado y protección
De un vistazo
Sección titulada «De un vistazo»Esta página cubre cuatro aspectos de producción que el paquete gestiona más allá de un renderizado básico: respaldo local, telemetría de borde, archivado en R2 y la capa de protección de la API entrante. Cada sección se corresponde con un comportamiento verificado en la clase correspondiente.
Respaldo local
Sección titulada «Respaldo local»Cuando no se puede acceder al Worker y fallbackToLocal es true, el puente delega en un renderizador local. Ese renderizador local se proporciona mediante LocalRendererFactoryInterface. El puente llama a la factoría de forma diferida, por lo que el create() de la factoría solo se ejecuta en la ruta de respaldo.
<?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); } }; }}Conectar la factoría al renderizador:
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);Cuando se ejecuta el respaldo, el renderLocation del resultado es la cadena literal local y heightPt es 0.0. La ruta local no informa de ninguna ubicación de borde ni de una altura medida. El puente pasa el ancho solicitado al renderizador local mediante la clave de opción widthPt.
Lógica de decisión del respaldo
Sección titulada «Lógica de decisión del respaldo»Leído directamente desde CloudflareHtmlRenderer:
| Situación | Resultado |
|---|---|
Configuración incompleta, fallbackToLocal: false | CloudflareNotAvailableException |
Configuración incompleta, fallbackToLocal: true, factoría configurada | Renderizado local |
| El Worker lanza un error de transporte, respaldo habilitado, factoría configurada | Renderizado local, registrado en warning y luego en info |
| El Worker lanza una excepción, respaldo habilitado, Artisan instalado, sin factoría | CloudflareNotAvailableException que nombra la factoría ausente |
| El Worker lanza una excepción, respaldo habilitado, Artisan no instalado | CloudflareNotAvailableException que nombra el paquete ausente |
| El Worker devuelve un error HTTP / cuerpo mal formado | CloudflareRenderException, nunca recurre al respaldo |
La última fila marca la distinción crítica. Un Worker que responde con un error es un fallo de renderizado, no un fallo de accesibilidad. Se vuelve a lanzar para que el código pueda distinguir un renderizado roto de un borde inaccesible.
Telemetría de borde
Sección titulada «Telemetría de borde»Cada renderizado correcto que pasa por la ruta binaria incluye telemetría derivada de las cabeceras de respuesta:
$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(),]);El renderizador lee renderLocation de la cabecera de respuesta CF-Ray y toma el segmento posterior al último guion. Para CF-Ray: 8abc123def456-TPE la ubicación es TPE. Cuando la cabecera está ausente, la ubicación es una cadena vacía. En la ruta de respuesta JSON, en cambio, el valor proviene del campo JSON renderLocation. Estos valores deben tratarse como señales de observabilidad del Worker, no como garantías de la plataforma.
Archivado en R2
Sección titulada «Archivado en R2»R2ArchiveManager sube los bytes del PDF a Cloudflare R2 mediante la API compatible con S3 y firma las solicitudes 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]);}Comportamiento verificado en R2ArchiveManager y R2ObjectKey:
- La clave del objeto se particiona por fecha:
<pathPrefix><Y>/<m>/<d>/<sanitized-filename>, por ejemploinvoices/2026/05/18/invoice-2026-0042.pdf. - El nombre de archivo se sanea: se aplica
basename()(eliminación del path traversal) y, después, se eliminan los bytes nulos y los caracteres de control (\x00–\x1f,\x7f). Un resultado vacío se convierte endocument.pdf. - Los metadatos personalizados se envían como cabeceras
x-amz-meta-<lowercased-key>, incluidas en el conjunto de cabeceras firmadas de V4. - Una subida mayor que
maxFileSizeBytes(por defecto104857600) se rechaza antes de realizar cualquier solicitud y devuelve unR2UploadResultconsuccess: false. R2UploadResult::isValid()requieresuccess, unakeyno vacía y unetagno vacío.
URLs de descarga pre-firmadas
Sección titulada «URLs de descarga pre-firmadas»$url = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);generateSignedUrl() construye una URL GET con firma en la consulta mediante AWS Signature V4 y con un X-Amz-Expires controlado por quien llama (por defecto 3600 segundos). La solicitud canónica usa el centinela de hash de contenido UNSIGNED-PAYLOAD. Una URL de lectura con firma en la consulta usa esa forma porque el cuerpo no forma parte de la solicitud firmada. Esto describe el comportamiento de firma implementado por el paquete, tal como se desprende de R2ArchiveManager. La documentación del servicio de Amazon define AWS Signature Version 4, no un estándar de un SDO, por lo que aquí no se fija ninguna cláusula normativa. Las claves de acceso a los objetos son #[SensitiveParameter]; deben mantenerse fuera de los registros.
URLs públicas
Sección titulada «URLs públicas»R2UploadResult::publicUrl($customDomain) devuelve únicamente la clave cuando no se indica ningún dominio, o https://<domain>/<key> en caso contrario. Fuerza un esquema HTTPS cuando el dominio proporcionado no tiene ninguno. No convierte un bucket privado en público; eso depende de la configuración del bucket de R2.
Protección de la API entrante
Sección titulada «Protección de la API entrante»ApiProtection es la capa que se aplica a las solicitudes de renderizado que llegan a una pasarela PHP situada delante del Worker. Ejecuta tres comprobaciones en un orden fijo: clave de API, después tamaño de la carga útil y, por último, límite de tasa.
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;}Comportamiento verificado:
- El orden es clave de API → tamaño de la carga útil → límite de tasa. La primera comprobación fallida hace cortocircuito con un
denialReasonespecífico. ApiKeyValidator::validate()usahash_equals()para una comparación segura contra ataques de temporización y rechaza una clave vacía.validateHashed()compara contra hashes SHA-256 para el almacenamiento de claves en reposo. Los parámetros de clave llevan#[SensitiveParameter].- El almacén de límite de tasa es en memoria por proceso. Rastrea una ventana por minuto (
rateLimitWindowSeconds, por defecto60) y una ventana por hora (fija en3600segundos). No persiste entre workers ni reinicios. Para aplicar un límite compartido entre procesos, se debe anteponer un almacén compartido. ApiProtectionResult::toHeaders()siempre añadeX-Content-Type-Options: nosniffyX-Frame-Options: DENY, y combina las cabeceras de límite de tasa (X-RateLimit-Remaining,X-RateLimit-Reset, másRetry-Aftercuando se deniega).
Renderizar y luego firmar
Sección titulada «Renderizar y luego firmar»Este puente no firma archivos PDF. Una canalización de firma de producción renderiza en el borde y luego firma los bytes devueltos con el motor:
render()→CloudflareRenderResult::$pdfData.- Entregar
$pdfDataanextpdf/core(o NextPDF Pro para la firma PAdES B-B). Los perfiles de validación a largo plazo son una capacidad Enterprise; este puente core no reivindica ninguna de esas capacidades.
Mantener el paso de firma en su propio proceso para que la clave de firma nunca cruce el límite del borde.
Véase también
Sección titulada «Véase también»- /integrations/cloudflare/security-and-operations/ — fijado (pinning), defensa frente a SSRF, rotación de secretos, manual operativo.
- /integrations/cloudflare/troubleshooting/ — catálogo de modos de fallo.
- /integrations/cloudflare/configuration/ — cada campo y su valor por defecto.