Uso em produção: fallback, telemetria, arquivamento e proteção
Visão geral
Seção intitulada “Visão geral”Esta página cobre quatro preocupações de produção além da renderização básica: fallback local, telemetria de edge, arquivamento no Cloudflare R2 e a camada de proteção de application programming interface (API) de entrada. Cada seção corresponde a um comportamento de classe verificado.
Fallback local
Seção intitulada “Fallback local”Quando o Worker está inacessível e fallbackToLocal é true, a bridge delega a renderização a um renderizador local. Forneça esse renderizador por meio de LocalRendererFactoryInterface. A bridge o cria de forma lazy; por isso, o create() da factory é executado apenas no caminho de 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); } }; }}Conecte a factory ao renderizador:
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);Quando o fallback é executado, o renderLocation do resultado é a string literal local, e heightPt é 0.0. O caminho local não informa uma localização de edge nem uma altura medida. A bridge passa a largura solicitada ao renderizador local por meio da chave de opção widthPt.
Lógica de decisão do fallback
Seção intitulada “Lógica de decisão do fallback”Lido diretamente de CloudflareHtmlRenderer:
| Situação | Resultado |
|---|---|
Config incompleta, fallbackToLocal: false | CloudflareNotAvailableException |
Config incompleta, fallbackToLocal: true, factory conectada | Renderização local |
| Worker lança um erro de transporte, fallback habilitado, factory conectada | Renderização local, registrada em warning e depois em info |
| Worker lança, fallback habilitado, Artisan instalado, sem factory | CloudflareNotAvailableException nomeando a factory ausente |
| Worker lança, fallback habilitado, Artisan não instalado | CloudflareNotAvailableException nomeando o pacote ausente |
| Worker retorna um erro Hypertext Transfer Protocol (HTTP) / corpo malformado | CloudflareRenderException, nunca faz fallback |
A última linha é crítica. Um Worker que retorna um erro representa uma falha de renderização, não uma falha de acessibilidade. A bridge relança essa falha para que o código consiga distinguir uma renderização quebrada de um edge inacessível.
Telemetria de edge
Seção intitulada “Telemetria de edge”Toda renderização bem-sucedida no caminho binário inclui telemetria nos headers de resposta:
$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(),]);O renderizador lê renderLocation do header de resposta CF-Ray e usa o segmento após o último hífen. Para CF-Ray: 8abc123def456-TPE, a localização é TPE. Quando o header está ausente, a localização é uma string vazia. No caminho de resposta JavaScript Object Notation (JSON), o valor vem do campo JSON renderLocation. Trate esses valores como sinais de observabilidade do Worker, não como garantias da plataforma.
Arquivamento no R2
Seção intitulada “Arquivamento no R2”R2ArchiveManager faz upload de bytes Portable Document Format (PDF) para o Cloudflare R2 por meio da API compatível com Amazon Simple Storage Service (S3) e assina as requisições com Amazon Web Services (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 verificado a partir de R2ArchiveManager e R2ObjectKey:
- A chave do objeto é particionada por data no formato:
<pathPrefix><Y>/<m>/<d>/<sanitized-filename>, por exemploinvoices/2026/05/18/invoice-2026-0042.pdf. - O nome do arquivo é sanitizado:
basename()remove o path traversal e, em seguida, os null bytes e caracteres de controle (\x00–\x1f,\x7f) são removidos. Um resultado vazio torna-sedocument.pdf. - Metadados personalizados são enviados como headers
x-amz-meta-<lowercased-key>e incluídos no conjunto de signed-header da V4. - Arquivos maiores que
maxFileSizeBytes(padrão104857600) são rejeitados antes de qualquer requisição, retornando umR2UploadResultcomsuccess: false. R2UploadResult::isValid()exigesuccess, umakeynão vazia e umetagnão vazio.
URLs de download pré-assinadas
Seção intitulada “URLs de download pré-assinadas”$url = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);generateSignedUrl() constrói uma URL GET assinada via query com AWS Signature V4 e um valor X-Amz-Expires que você controla (padrão 3600 segundos). A requisição canônica usa o sentinela de content-hash UNSIGNED-PAYLOAD. Uma URL de leitura assinada via query usa esse formato porque o corpo não faz parte da requisição assinada. Isto descreve o comportamento de assinatura implementado pelo pacote, conforme lido de R2ArchiveManager. A documentação de serviço da Amazon define o AWS Signature Version 4, não um padrão de standards development organization (SDO); portanto, nenhuma cláusula normativa é fixada aqui. As chaves de acesso a objetos são #[SensitiveParameter]; mantenha-as fora dos logs.
URLs públicas
Seção intitulada “URLs públicas”R2UploadResult::publicUrl($customDomain) retorna a chave nua quando você não fornece um domínio, ou https://<domain>/<key> quando você fornece. Ela adiciona um esquema Hypertext Transfer Protocol Secure (HTTPS) quando o domínio informado não tem nenhum. Ela não torna público um bucket privado; isso continua sendo uma preocupação de configuração do bucket R2.
Proteção de API de entrada
Seção intitulada “Proteção de API de entrada”ApiProtection é a camada que você aplica às requisições de renderização que chegam a um gateway PHP na frente do Worker. Ela verifica em uma ordem fixa: API key, depois tamanho do payload, depois rate limit.
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 verificado:
- A ordem é API key → tamanho do payload → rate limit. A primeira verificação que falha interrompe o fluxo com um
denialReasonespecífico. ApiKeyValidator::validate()usahash_equals()para comparação resistente a timing e rejeita uma chave vazia.validateHashed()compara hashes Secure Hash Algorithm 256-bit (SHA-256) para armazenamento de chaves em repouso. Os parâmetros de chave carregam#[SensitiveParameter].- O store de rate-limit é em memória por processo. Ele rastreia uma janela por minuto (
rateLimitWindowSeconds, padrão60) e uma janela por hora (fixa em3600segundos). Ele não persiste entre workers nem entre reinicializações. Para compartilhar limites entre processos, coloque um store compartilhado à frente dele. ApiProtectionResult::toHeaders()sempre adicionaX-Content-Type-Options: nosniffeX-Frame-Options: DENY, e mescla os headers de rate-limit (X-RateLimit-Remaining,X-RateLimit-Reset, maisRetry-Afterquando a requisição é negada).
Renderizar e depois assinar
Seção intitulada “Renderizar e depois assinar”Esta bridge não assina PDFs. Para montar um pipeline de assinatura em produção, renderize no edge e depois assine os bytes retornados com o engine:
render()→CloudflareRenderResult::$pdfData.- Entregue
$pdfDataaonextpdf/core(ou ao NextPDF Pro para assinatura PDF Advanced Electronic Signatures (PAdES) B-B). Os perfis de long-term-validation são um recurso do Enterprise; esta bridge core não declara suporte a nenhum dos dois recursos.
Mantenha a etapa de assinatura no próprio processo para que a chave de assinatura nunca cruze o limite do edge.
Veja também
Seção intitulada “Veja também”- /integrations/cloudflare/security-and-operations/ — pinning, defesa contra server-side request forgery (SSRF), rotação de segredos e o runbook operacional.
- /integrations/cloudflare/troubleshooting/ — catálogo de modos de falha.
- /integrations/cloudflare/configuration/ — cada campo e padrão.