الاستخدام في الإنتاج — التراجع المحلي، والقياس عن بُعد، والأرشفة، والحماية
نظرة سريعة
قسم بعنوان «نظرة سريعة»تتناول هذه الصفحة أربعة جوانب إنتاجية تتجاوز التصيير الأساسي: التراجع المحلي، والقياس عن بُعد من الحافة، والأرشفة إلى Cloudflare R2، وطبقة حماية application programming interface (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 خطأ Hypertext Transfer Protocol (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(),]);يقرأ المُصيِّر قيمة renderLocation من ترويسة الاستجابة CF-Ray ويأخذ الجزء الواقع بعد الواصلة الأخيرة. بالنسبة إلى CF-Ray: 8abc123def456-TPE يكون الموقع هو TPE. عند غياب الترويسة، يكون الموقع سلسلة فارغة. في مسار استجابة JavaScript Object Notation (JSON)، تأتي القيمة بدلًا من ذلك من حقل renderLocation في JSON. تعامَل مع هذه القيم بوصفها إشارات قابلية رصد صادرة عن Worker، لا ضمانات من المنصّة.
الأرشفة إلى R2
قسم بعنوان «الأرشفة إلى R2»يرفع R2ArchiveManager بايتات Portable Document Format (PDF) إلى Cloudflare R2 عبر واجهة API المتوافقة مع Amazon Simple Storage Service (S3)، ويوقّع الطلبات باستخدام 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]);}سلوك مُتحقَّق منه في 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 = $r2->generateSignedUrl('invoices/2026/05/18/invoice-2026-0042.pdf', 900);تُنشئ generateSignedUrl() رابط GET موقَّعًا عبر معاملات الاستعلام باستخدام AWS Signature V4 مع قيمة X-Amz-Expires تتحكّم بها (الافتراضي 3600 ثانية). يستخدم الطلب القياسي قيمة UNSIGNED-PAYLOAD الدالّة على تجزئة المحتوى. ويستخدم رابط القراءة الموقَّع عبر معاملات الاستعلام هذه الصيغة لأنّ الجسم ليس جزءًا من الطلب الموقَّع. يصف ذلك سلوك التوقيع المُطبَّق في الحزمة، كما هو مقروء من R2ArchiveManager. توثيق خدمة Amazon هو ما يُعرّف AWS Signature Version 4، لا معيار صادر عن منظّمة لتطوير المعايير (standards development organization، SDO)، لذلك لا يُثبَّت هنا أي بند معياري. مفاتيح الوصول إلى الكائنات موسومة بـ #[SensitiveParameter]؛ فأبقِها خارج السجلّات.
الروابط العامة
قسم بعنوان «الروابط العامة»تُرجِع R2UploadResult::publicUrl($customDomain) المفتاح المجرّد عندما لا توفّر نطاقًا، أو https://<domain>/<key> عندما توفّره. وتضيف مخطّط Hypertext Transfer Protocol Secure (HTTPS) عندما لا يتضمّن النطاق المُقدَّم أي مخطّط. لا تجعل حاوية خاصة عامة؛ فذلك يبقى من شأن إعداد حاوية R2.
حماية API الواردة
قسم بعنوان «حماية API الواردة»ApiProtection هي الطبقة التي تطبّقها على طلبات التصيير الواردة إلى بوّابة PHP أمام Worker. تنفّذ التحقّقات بترتيب ثابت: مفتاح 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()مع تجزئات Secure Hash Algorithm 256-bit (SHA-256) لتخزين المفاتيح أثناء السكون. تحمل معاملات المفتاح وسم#[SensitiveParameter]. - مخزن حدّ المعدّل في الذاكرة لكل عملية. يتتبّع نافذة لكل دقيقة (
rateLimitWindowSeconds، الافتراضي60) ونافذة لكل ساعة (ثابتة عند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 لتوقيع PDF Advanced Electronic Signatures (PAdES) B-B). ملفات تعريف التحقّق طويل الأمد إمكانية في إصدار Enterprise؛ ولا يدّعي جسر core هذا أيًّا من الإمكانيتين.
أبقِ خطوة التوقيع داخل عمليتك الخاصة كي لا يعبر مفتاح التوقيع حدود الحافة أبدًا.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- /integrations/cloudflare/security-and-operations/ — التثبيت، والدفاع ضد تزوير الطلب من جانب الخادم (server-side request forgery، SSRF)، وتدوير الأسرار، ودليل التشغيل.
- /integrations/cloudflare/troubleshooting/ — كتالوج أنماط الإخفاق.
- /integrations/cloudflare/configuration/ — كل حقل وقيمته الافتراضية.