Użycie produkcyjne — rezerwa lokalna, telemetria, archiwizacja i ochrona
W skrócie
Dział zatytułowany „W skrócie”Strona omawia cztery zagadnienia produkcyjne wykraczające poza podstawowe renderowanie: rezerwę lokalną, telemetrię z brzegu sieci, archiwizację w Cloudflare R2 oraz warstwę ochrony przychodzącego interfejsu programowania aplikacji (API). Każda sekcja odwołuje się do zweryfikowanego zachowania klas.
Rezerwa lokalna
Dział zatytułowany „Rezerwa lokalna”Jeśli Worker jest nieosiągalny, a fallbackToLocal ma wartość true, mostek deleguje renderowanie do lokalnego mechanizmu renderującego. Udostępnij lokalny mechanizm renderujący przez LocalRendererFactoryInterface. Mostek tworzy go leniwie, więc metoda create() fabryki jest wywoływana tylko na ścieżce rezerwowej.
<?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); } }; }}Podłącz fabrykę do mechanizmu renderującego:
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);Gdy uruchamia się ścieżka rezerwowa, renderLocation w wyniku jest dosłownym ciągiem local, a heightPt wynosi 0.0. Ścieżka lokalna nie raportuje lokalizacji brzegu sieci ani zmierzonej wysokości. Mostek przekazuje żądaną szerokość do lokalnego mechanizmu renderującego za pomocą klucza opcji widthPt.
Logika decyzji o rezerwie
Dział zatytułowany „Logika decyzji o rezerwie”Odczytane bezpośrednio z CloudflareHtmlRenderer:
| Sytuacja | Wynik |
|---|---|
Niekompletna konfiguracja, fallbackToLocal: false | CloudflareNotAvailableException |
Niekompletna konfiguracja, fallbackToLocal: true, fabryka podłączona | Renderowanie lokalne |
| Worker zgłasza błąd transportu, rezerwa włączona, fabryka podłączona | Renderowanie lokalne, logowane na poziomie warning, a następnie info |
| Worker zgłasza błąd, rezerwa włączona, Artisan zainstalowany, brak fabryki | CloudflareNotAvailableException wskazujący brakującą fabrykę |
| Worker zgłasza błąd, rezerwa włączona, Artisan nie jest zainstalowany | CloudflareNotAvailableException wskazujący brakujący pakiet |
| Worker zwraca błąd protokołu Hypertext Transfer Protocol (HTTP) / zniekształconą treść | CloudflareRenderException, nigdy nie przechodzi do rezerwy |
Ostatni wiersz jest kluczowy. Worker zwracający błąd oznacza niepowodzenie renderowania, a nie problem z osiągalnością. Mostek zgłasza ten błąd ponownie, aby kod mógł odróżnić nieudane renderowanie od nieosiągalnego brzegu sieci.
Telemetria z brzegu sieci
Dział zatytułowany „Telemetria z brzegu sieci”Każde udane renderowanie na ścieżce binarnej zawiera telemetrię z nagłówków odpowiedzi:
$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(),]);Mechanizm renderujący odczytuje renderLocation z nagłówka odpowiedzi CF-Ray i pobiera segment po ostatnim łączniku. Dla CF-Ray: 8abc123def456-TPE lokalizacją jest TPE. Jeśli nagłówka nie ma, lokalizacją jest pusty ciąg. Na ścieżce odpowiedzi JavaScript Object Notation (JSON) wartość pochodzi natomiast z pola JSON renderLocation. Traktuj te wartości jako sygnały obserwowalności pochodzące z Workera, a nie jako gwarancje platformy.
Archiwizacja w R2
Dział zatytułowany „Archiwizacja w R2”R2ArchiveManager przesyła bajty w formacie Portable Document Format (PDF) do Cloudflare R2 przez API zgodne z Amazon Simple Storage Service (S3) i podpisuje żądania za pomocą 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]);}Zachowanie zweryfikowane na podstawie R2ArchiveManager i R2ObjectKey:
- Klucz obiektu jest partycjonowany według daty, w postaci:
<pathPrefix><Y>/<m>/<d>/<sanitized-filename>, na przykładinvoices/2026/05/18/invoice-2026-0042.pdf. - Nazwa pliku jest oczyszczana:
basename()usuwa elementy przechodzenia po ścieżce, a następnie usuwane są bajty zerowe i znaki sterujące (\x00–\x1f,\x7f). Pusty wynik staje siędocument.pdf. - Niestandardowe metadane są wysyłane jako nagłówki
x-amz-meta-<lowercased-key>i wchodzą w skład zestawu podpisanych nagłówków V4. - Pliki większe niż
maxFileSizeBytes(domyślnie104857600) są odrzucane przed wysłaniem jakiegokolwiek żądania; metoda zwracaR2UploadResultzsuccess: false. R2UploadResult::isValid()wymagasuccess, niepustegokeyoraz niepustegoetag.
Wstępnie podpisane adresy URL do pobierania
Dział zatytułowany „Wstępnie podpisane adresy URL do pobierania”$url = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);generateSignedUrl() buduje adres URL GET podpisany w parametrach zapytania z użyciem AWS Signature V4 oraz kontrolowaną przez ciebie wartością X-Amz-Expires (domyślnie 3600 sekund). Żądanie kanoniczne używa znacznika skrótu treści UNSIGNED-PAYLOAD. Adres URL do odczytu podpisany w parametrach zapytania używa tej postaci, ponieważ treść nie jest częścią podpisanego żądania. Opis ten odpowiada zachowaniu podpisywania zaimplementowanemu w pakiecie i odczytanemu z R2ArchiveManager. AWS Signature Version 4 definiuje dokumentacja usługowa Amazona, a nie norma organizacji opracowującej standardy (SDO), dlatego nie przypisano tutaj żadnej klauzuli normatywnej. Klucze dostępu do obiektów są oznaczone jako #[SensitiveParameter]; nie zapisuj ich w dziennikach.
Publiczne adresy URL
Dział zatytułowany „Publiczne adresy URL”R2UploadResult::publicUrl($customDomain) zwraca sam klucz, jeśli nie podasz domeny, albo https://<domain>/<key>, jeśli ją podasz. Dodaje schemat Hypertext Transfer Protocol Secure (HTTPS), jeśli podana domena go nie zawiera. Nie sprawia, że prywatny zasobnik staje się publiczny; pozostaje to kwestią konfiguracji zasobnika R2.
Ochrona przychodzącego API
Dział zatytułowany „Ochrona przychodzącego API”ApiProtection to warstwa stosowana do żądań renderowania, które trafiają do bramy PHP przed Workerem. Kontrole wykonuje w stałej kolejności: klucz API, następnie rozmiar ładunku, następnie limit szybkości.
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;}Zweryfikowane zachowanie:
- Kolejność to klucz API → rozmiar ładunku → limit szybkości. Pierwsza nieudana kontrola powoduje natychmiastowe przerwanie z określonym
denialReason. ApiKeyValidator::validate()używahash_equals()do porównania odpornego na ataki czasowe i odrzuca pusty klucz.validateHashed()porównuje klucze ze skrótami Secure Hash Algorithm 256-bit (SHA-256) na potrzeby przechowywania kluczy w stanie spoczynku. Parametry kluczy są oznaczone atrybutem#[SensitiveParameter].- Magazyn limitu szybkości jest utrzymywany w pamięci oddzielnie dla każdego procesu. Śledzi okno minutowe (
rateLimitWindowSeconds, domyślnie60) oraz okno godzinowe (stałe3600sekund). Nie jest utrwalany między procesami roboczymi ani po ponownym uruchomieniu. Aby współdzielić limity między procesami, umieść przed nim współdzielony magazyn. ApiProtectionResult::toHeaders()zawsze dodajeX-Content-Type-Options: nosnifforazX-Frame-Options: DENYi łączy je z nagłówkami limitu szybkości (X-RateLimit-Remaining,X-RateLimit-ResetorazRetry-Afterw przypadku odmowy).
Najpierw renderowanie, potem podpisywanie
Dział zatytułowany „Najpierw renderowanie, potem podpisywanie”Ten mostek nie podpisuje plików PDF. Aby zbudować produkcyjny potok podpisywania, renderuj na brzegu sieci, a następnie podpisz zwrócone bajty przy użyciu silnika:
render()→CloudflareRenderResult::$pdfData.- Przekaż
$pdfDatadonextpdf/core(lub NextPDF Pro w celu podpisywania PDF Advanced Electronic Signatures (PAdES) B-B). Profile długoterminowej weryfikacji są funkcją Enterprise; ten mostek core nie udostępnia żadnej z tych funkcji.
Zachowaj krok podpisywania we własnym procesie, aby klucz podpisujący nigdy nie przekraczał granicy brzegu sieci.
Zobacz także
Dział zatytułowany „Zobacz także”- /integrations/cloudflare/security-and-operations/ — przypinanie, ochrona przed fałszowaniem żądań po stronie serwera (SSRF), rotacja sekretów oraz operacyjny podręcznik procedur.
- /integrations/cloudflare/troubleshooting/ — katalog trybów awarii.
- /integrations/cloudflare/configuration/ — każde pole i wartość domyślna.