Sử dụng trong môi trường production — dự phòng, telemetry, lưu trữ và bảo vệ
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Trang này trình bày bốn vấn đề trong môi trường production bên cạnh thao tác render cơ bản: dự phòng cục bộ, telemetry tại edge, lưu trữ vào Cloudflare R2 và lớp bảo vệ application programming interface (API) cho lưu lượng đến. Mỗi phần tương ứng với hành vi của lớp đã được kiểm chứng.
Dự phòng cục bộ
Phần tiêu đề “Dự phòng cục bộ”Khi không kết nối được với Worker và fallbackToLocal là true, bridge sẽ ủy quyền việc render cho một renderer cục bộ. Hãy cung cấp renderer đó thông qua LocalRendererFactoryInterface. Bridge tạo nó theo cơ chế lazy, nên create() của factory chỉ chạy trên đường dẫn dự phòng.
<?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); } }; }}Gắn factory vào renderer như sau:
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);Khi dự phòng chạy, renderLocation của kết quả là chuỗi nguyên văn local, và heightPt là 0.0. Đường dẫn cục bộ không báo cáo vị trí edge hoặc chiều cao đo được. Bridge truyền chiều rộng được yêu cầu cho renderer cục bộ qua khóa tùy chọn widthPt.
Logic quyết định dự phòng
Phần tiêu đề “Logic quyết định dự phòng”Đọc trực tiếp từ CloudflareHtmlRenderer:
| Tình huống | Kết quả |
|---|---|
Cấu hình chưa đầy đủ, fallbackToLocal: false | CloudflareNotAvailableException |
Cấu hình chưa đầy đủ, fallbackToLocal: true, đã gắn factory | Render cục bộ |
| Worker ném lỗi transport, dự phòng đã bật, đã gắn factory | Render cục bộ, được ghi log ở mức warning rồi info |
| Worker ném lỗi, dự phòng đã bật, Artisan đã được cài đặt, không có factory | CloudflareNotAvailableException nêu tên factory bị thiếu |
| Worker ném lỗi, dự phòng đã bật, Artisan chưa được cài đặt | CloudflareNotAvailableException nêu tên package bị thiếu |
| Worker trả về lỗi Hypertext Transfer Protocol (HTTP) / phần thân không hợp lệ | CloudflareRenderException, không bao giờ dùng dự phòng |
Hàng cuối cùng rất quan trọng. Một Worker trả về lỗi là lỗi render, không phải lỗi không kết nối được. Bridge ném lại lỗi đó để mã của bạn có thể phân biệt một lần render hỏng với một edge không kết nối được.
Telemetry tại edge
Phần tiêu đề “Telemetry tại edge”Mỗi lần render thành công qua đường dẫn nhị phân đều kèm telemetry từ các header phản hồi:
$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(),]);Renderer đọc renderLocation từ header phản hồi CF-Ray và lấy phần sau dấu gạch nối cuối cùng. Với CF-Ray: 8abc123def456-TPE thì vị trí là TPE. Khi không có header này, vị trí là một chuỗi rỗng. Trên đường dẫn phản hồi JavaScript Object Notation (JSON), giá trị này được lấy từ trường JSON renderLocation. Hãy xem các giá trị này là tín hiệu observability từ Worker, không phải cam kết của nền tảng.
Lưu trữ vào R2
Phần tiêu đề “Lưu trữ vào R2”R2ArchiveManager tải các byte Portable Document Format (PDF) lên Cloudflare R2 qua API tương thích với Amazon Simple Storage Service (S3) và ký các yêu cầu bằng 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]);}Hành vi được kiểm chứng từ R2ArchiveManager và R2ObjectKey:
- Khóa đối tượng được phân vùng theo ngày ở dạng:
<pathPrefix><Y>/<m>/<d>/<sanitized-filename>, ví dụinvoices/2026/05/18/invoice-2026-0042.pdf. - Tên tệp được làm sạch:
basename()loại bỏ path traversal, sau đó các byte null và ký tự điều khiển (\x00–\x1f,\x7f) bị loại bỏ. Nếu kết quả rỗng, tên sẽ trở thànhdocument.pdf. - Metadata tùy chỉnh được gửi dưới dạng các header
x-amz-meta-<lowercased-key>và được đưa vào tập header được ký V4. - Các tệp lớn hơn
maxFileSizeBytes(mặc định104857600) bị từ chối trước khi gửi bất kỳ yêu cầu nào và trả về mộtR2UploadResultvớisuccess: false. R2UploadResult::isValid()yêu cầusuccess, mộtkeykhông rỗng và mộtetagkhông rỗng.
URL tải xuống được ký sẵn
Phần tiêu đề “URL tải xuống được ký sẵn”$url = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);generateSignedUrl() dựng một URL GET được ký qua query bằng AWS Signature V4 với một giá trị X-Amz-Expires do bạn kiểm soát (mặc định 3600 giây). Canonical request dùng giá trị sentinel content-hash UNSIGNED-PAYLOAD. URL đọc được ký qua query dùng dạng này vì phần thân không thuộc về yêu cầu được ký. Phần này mô tả hành vi ký đã triển khai của package, dựa trên những gì đọc được từ R2ArchiveManager. Tài liệu dịch vụ của Amazon định nghĩa AWS Signature Version 4, không phải một tiêu chuẩn của standards development organization (SDO), nên không có điều khoản chuẩn tắc nào được ghim ở đây. Các khóa truy cập đối tượng được đánh dấu #[SensitiveParameter]; hãy giữ chúng ngoài các log.
URL công khai
Phần tiêu đề “URL công khai”R2UploadResult::publicUrl($customDomain) trả về khóa dạng thuần khi bạn không cung cấp domain, hoặc https://<domain>/<key> khi bạn cung cấp. Nó thêm scheme Hypertext Transfer Protocol Secure (HTTPS) khi domain được cung cấp không có scheme. Nó không biến một bucket riêng tư thành công khai; việc đó vẫn thuộc về cấu hình bucket R2.
Bảo vệ API cho lưu lượng đến
Phần tiêu đề “Bảo vệ API cho lưu lượng đến”ApiProtection là lớp bạn áp dụng cho các yêu cầu render đi vào một PHP gateway đặt trước Worker. Nó kiểm tra theo một thứ tự cố định: API key, rồi kích thước payload, rồi giới hạn tần suất.
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;}Hành vi đã kiểm chứng:
- Thứ tự là API key → kích thước payload → giới hạn tần suất. Bước kiểm tra đầu tiên thất bại sẽ dừng ngay với một
denialReasoncụ thể. ApiKeyValidator::validate()dùnghash_equals()để so sánh an toàn về thời gian và từ chối khóa rỗng.validateHashed()so sánh với các hash Secure Hash Algorithm 256-bit (SHA-256) để lưu trữ khóa ở trạng thái nghỉ. Các tham số khóa mang#[SensitiveParameter].- Bộ lưu trữ giới hạn tần suất là trong bộ nhớ theo từng tiến trình. Nó theo dõi một cửa sổ theo phút (
rateLimitWindowSeconds, mặc định60) và một cửa sổ theo giờ (cố định3600giây). Nó không được lưu giữ xuyên qua nhiều worker hoặc các lần khởi động lại. Để chia sẻ các giới hạn giữa nhiều tiến trình, hãy đặt một bộ lưu trữ dùng chung phía trước nó. ApiProtectionResult::toHeaders()luôn thêmX-Content-Type-Options: nosniffvàX-Frame-Options: DENY, đồng thời gộp thêm các header giới hạn tần suất (X-RateLimit-Remaining,X-RateLimit-Reset, cộng thêmRetry-Afterkhi bị từ chối).
Render rồi ký
Phần tiêu đề “Render rồi ký”Bridge này không ký các tệp PDF. Để xây dựng một pipeline ký cho production, hãy render tại edge, rồi ký các byte trả về bằng engine:
render()→CloudflareRenderResult::$pdfData.- Chuyển
$pdfDatachonextpdf/core(hoặc NextPDF Pro để ký PDF Advanced Electronic Signatures (PAdES) B-B). Các profile long-term-validation là một khả năng của Enterprise; bridge core này không tuyên bố hỗ trợ khả năng nào trong hai khả năng đó.
Hãy giữ bước ký trong tiến trình riêng của bạn để khóa ký không bao giờ vượt qua ranh giới edge.
Xem thêm
Phần tiêu đề “Xem thêm”- /integrations/cloudflare/security-and-operations/ — pinning, chống server-side request forgery (SSRF), xoay vòng secret và runbook vận hành.
- /integrations/cloudflare/troubleshooting/ — danh mục các chế độ lỗi.
- /integrations/cloudflare/configuration/ — mọi trường và giá trị mặc định.