本番運用:フォールバック、テレメトリ、アーカイブ、保護
このページでは、パッケージが基本的なレンダー処理に加えて扱う 4 つの本番運用上の関心事、つまりローカルフォールバック、エッジテレメトリ、R2 アーカイブ、インバウンド API 保護レイヤーを取り上げます。各セクションは、検証済みのクラス動作に対応しています。
ローカルフォールバック
「ローカルフォールバック」という見出しのセクションWorker に到達できず、fallbackToLocal が true の場合、ブリッジはローカルレンダラーに委譲します。このローカルレンダラーは LocalRendererFactoryInterface を通じて提供されます。ブリッジはファクトリを遅延呼び出しするため、ファクトリの create() はフォールバック経路でのみ実行されます。
<?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); } }; }}ファクトリをレンダラーに配線します。
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);フォールバックが実行されると、結果の renderLocation はリテラル文字列 local となり、heightPt は 0.0 になります。ローカル経路では、エッジロケーションも測定済みの高さも報告されません。ブリッジは、要求された幅を widthPt オプションキーを通じてローカルレンダラーに渡します。
フォールバック判定ロジック
「フォールバック判定ロジック」という見出しのセクション以下は CloudflareHtmlRenderer から直接読み取った動作です。
| 状況 | 結果 |
|---|---|
設定が不完全、fallbackToLocal: false | CloudflareNotAvailableException |
設定が不完全、fallbackToLocal: true、ファクトリ配線済み | ローカルレンダー |
| Worker がトランスポートエラーをスロー、フォールバック有効、ファクトリ配線済み | ローカルレンダー、warning でログ記録後 info |
| Worker がスロー、フォールバック有効、Artisan インストール済み、ファクトリなし | 欠落しているファクトリを示す CloudflareNotAvailableException |
| Worker がスロー、フォールバック有効、Artisan が未インストール | 欠落しているパッケージを示す CloudflareNotAvailableException |
| Worker が HTTP エラー/不正な本文を返す | CloudflareRenderException、決してフォールバックしない |
最後の行が重要な分岐です。エラーで応答する Worker はレンダー失敗であり、到達可能性の失敗ではありません。これは再スローされるため、コード側ではレンダー失敗と到達不能なエッジを区別できます。
エッジテレメトリ
「エッジテレメトリ」という見出しのセクションバイナリ経路で成功したすべてのレンダーには、レスポンスヘッダーから導出されたテレメトリが付随します。
$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(),]);レンダラーは CF-Ray レスポンスヘッダーから renderLocation を読み取り、最後のハイフン以降のセグメントを取得します。CF-Ray: 8abc123def456-TPE の場合、ロケーションは TPE です。ヘッダーが存在しない場合、ロケーションは空文字列になります。JSON レスポンス経路では、代わりに JSON の renderLocation フィールドから値を取得します。これらは Worker からの可観測性シグナルとして扱い、プラットフォームの保証としては扱わないでください。
R2 アーカイブ
「R2 アーカイブ」という見出しのセクションR2ArchiveManager は、S3 互換 API を通じて PDF のバイト列を Cloudflare R2 にアップロードし、リクエストを 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]);}以下は、R2ArchiveManager と R2ObjectKey から検証された動作です。
- オブジェクトキーは日付でパーティション分割されます。形式は
<pathPrefix><Y>/<m>/<d>/<sanitized-filename>です。例えばinvoices/2026/05/18/invoice-2026-0042.pdfのようになります。 - ファイル名はサニタイズされます。
basename()が適用されてパストラバーサルを除去し、続いてヌルバイトと制御文字(\x00–\x1f、\x7f)を除去します。結果が空になった場合はdocument.pdfになります。 - カスタムメタデータは
x-amz-meta-<lowercased-key>ヘッダーとして送信され、V4 の署名対象ヘッダーセットに含まれます。 maxFileSizeBytes(デフォルト104857600)より大きいアップロードは、リクエスト送信前に拒否され、R2UploadResultをsuccess: falseで返します。R2UploadResult::isValid()は、success、空でないkey、空でないetagがそろっていることを必要とします。
事前署名付きダウンロード URL
「事前署名付きダウンロード URL」という見出しのセクション$url = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);generateSignedUrl() は、AWS Signature V4 でクエリ署名した GET URL を構築します。この URL には、呼び出し側が制御する X-Amz-Expires(デフォルト 3600 秒)が含まれます。正規リクエストでは、コンテンツハッシュのセンチネルとして UNSIGNED-PAYLOAD を使用します。クエリ署名された読み取り URL では、本文が署名対象リクエストの一部ではないため、この形式を使用します。これは、R2ArchiveManager から読み取った、パッケージ実装の署名動作を記述したものです。Amazon のサービスドキュメントは AWS Signature Version 4 を定義していますが、SDO 標準ではないため、ここでは規範的な条項をピン留めしていません。オブジェクトアクセスキーは #[SensitiveParameter] です。ログに残さないでください。
公開 URL
「公開 URL」という見出しのセクションR2UploadResult::publicUrl($customDomain) は、ドメインが指定されていない場合は素のキーを返し、指定されている場合は https://<domain>/<key> を返します。指定されたドメインにスキームがない場合は、HTTPS スキームを強制します。これはプライベートバケットを公開状態にするものではありません。公開状態は R2 バケットの構成で決まります。
インバウンド API 保護
「インバウンド API 保護」という見出しのセクションApiProtection は、Worker の手前にある PHP ゲートウェイに到着するレンダーリクエストに適用するレイヤーです。固定された順序で 3 つのチェックを実行します。API キー、ペイロードサイズ、レート制限の順です。
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;}検証済みの動作は次のとおりです。
- 順序は API キー → ペイロードサイズ → レート制限です。最初に失敗したチェックで処理を短絡し、固有の
denialReasonを返します。 ApiKeyValidator::validate()は、タイミングセーフな比較のためにhash_equals()を使用し、空のキーを拒否します。validateHashed()は、保管用の SHA-256 ハッシュと比較します。キーのパラメータには#[SensitiveParameter]が付与されています。- レート制限ストアはプロセスごとのインメモリです。1 分単位のウィンドウ(
rateLimitWindowSeconds、デフォルト60)と 1 時間単位のウィンドウ(固定3600秒)を追跡します。ワーカーや再起動をまたいで永続化されることはありません。プロセス間で共有される制限が必要な場合は、共有ストアを前段に配置してください。 ApiProtectionResult::toHeaders()は常にX-Content-Type-Options: nosniffとX-Frame-Options: DENYを追加し、レート制限ヘッダー(X-RateLimit-Remaining、X-RateLimit-Reset、拒否時には加えてRetry-After)をマージします。
レンダー後の署名
「レンダー後の署名」という見出しのセクションこのブリッジは PDF に署名しません。本番の署名パイプラインでは、エッジでレンダーした後、返されたバイト列をエンジンで署名します。
render()→CloudflareRenderResult::$pdfData。$pdfDataをnextpdf/core(または NextPDF Pro、PAdES B-B 署名用)に渡します。長期検証プロファイルは Enterprise の機能です。このコアブリッジは、それらの機能を主張しません。
署名鍵がエッジ境界を越えないように、署名ステップは自身のプロセス内に保持してください。
- /integrations/cloudflare/security-and-operations/ — ピン留め、SSRF 防御、シークレットローテーション、運用ランブック。
- /integrations/cloudflare/troubleshooting/ — 障害モードのカタログ。
- /integrations/cloudflare/configuration/ — すべてのフィールドとデフォルト。