تخطَّ إلى المحتوى

الاستخدام في الإنتاج — التراجع المحلي، والقياس عن بُعد، والأرشفة، والحماية

تتناول هذه الصفحة أربعة جوانب إنتاجية تتجاوز التصيير الأساسي: التراجع المحلي، والقياس عن بُعد من الحافة، والأرشفة إلى ⁨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: falseCloudflareNotAvailableException
الإعداد غير مكتمل، 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⁩، لا ضمانات من المنصّة.

يرفع 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.⁩

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.⁩ لبناء خط أنابيب توقيع إنتاجي، صيِّر عند الحافة، ثم وقّع البايتات المُرجَعة باستخدام المحرّك:

  1. render()CloudflareRenderResult::$pdfData.
  2. سلّم $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/⁩ — كل حقل وقيمته الافتراضية.