Cloudflare ile uçta işleme ve yerel yedek
Bir bakışta
“Bir bakışta” başlıklı bölümCloudflare köprüsü, HTML’inizi bir Cloudflare Worker işleme uç noktasına gönderir ve PDF’yi döndürür. İşleme uçta çalıştığı için uzun ömürlü bir tarayıcı işlemi çalıştırmanız gerekmez. Yalnızca HTTPS kullanan bir yapılandırma oluşturur, bir PHP Standards Recommendation (PSR)-18 istemcisi ile PSR-17 fabrikalarını bağlar, render() çağrısı yapar ve erişilemeyen Worker’lar için yerel bir işleyici ekleyebilirsiniz. Bu kılavuz, işleme çağrısını, yedek yolunu ve köprünün herhangi bir istek PHP sürecinden çıkmadan önce uyguladığı Sunucu Tarafı İstek Sahteciliği (SSRF), Alan Adı Sistemi (DNS) yeniden bağlama ve Taşıma Katmanı Güvenliği (TLS) genel anahtar sabitleme denetimlerini gösterir.
Başlamadan önce:
- NextPDF core ve
nextpdf/cloudflarekurulu olmalıdır. - Bir Worker uç noktası, işleme sözleşmesini HTTPS üzerinden sunmalı ve bir taşıyıcı belirteci (bearer token) kabul etmelidir. Köprü, herhangi bir şey göndermeden önce HTTPS kullanmayan bir Worker URL’sini reddeder.
- Bir PSR-18 istemcisi (örneğin Guzzle 7) ile PSR-17 istek ve akış fabrikaları mevcut olmalıdır. Sabitlenmiş cURL taşıması için ayrıca bir PSR-17 yanıt fabrikası ve
ext-curlsağlayın. - Yerel yedek için
nextpdf/artisan(veya başka bir yerel işleyici) mevcut olmalıdır.
Bu bir nasıl-yapılır kılavuzudur. İlk çalışır işlemeniz için Cloudflare hızlı başlangıç kılavuzuyla başlayın.
Kurulum
“Kurulum” başlıklı bölümKöprüyü, bir PSR-18 istemcisini ve PSR-17 fabrikalarını kurun.
composer require nextpdf/cloudflare guzzlehttp/guzzleYerel yedek için köprünün çağırabileceği yerel bir işleyici kurun.
composer require nextpdf/artisanWorker taşıyıcı belirtecini ve tüm R2 kimlik bilgilerini ortam değişkenlerinden veya bir gizli dizi yöneticisinden yükleyin. Bunları asla işleme (commit) eklemeyin.
Kavramsal genel bakış
“Kavramsal genel bakış” başlıklı bölümCloudflareHtmlRenderer::render() HTML’i ve hedefi doğrular, Worker’a kimliği doğrulanmış bir POST gönderir ve yanıtı ayrıştırır. Worker, ham PDF baytlarını (Content-Type: application/pdf) veya base64 kodlu bir pdf alanı içeren bir JSON gövdesi döndürür. İşleyici, yanıtı bir final readonly CloudflareRenderResult nesnesine eşler; bu nesne baytları, istenen genişliği, yüksekliği, işleme konumunu (CF-Ray başlığından türetilir) ve işleme süresini taşır.
Köprü, başarısızlıkları iki net sınıfa ayırır:
CloudflareRenderException— Worker yanıt verdi ancak işleme başarısız oldu (bir HTTP hatası veya%PDFile başlamayan bir gövde). Bu bir işleme başarısızlığıdır ve yedekle asla yeniden denenmez.CloudflareNotAvailableException— uç noktasına erişilemedi ve kullanılabilir bir yedek mevcut değildi.
Yerel yedek, ikinci durumu kapsar. Worker’a erişilemediğinde ve fallbackToLocal değeri true olduğunda köprü, sağladığınız LocalRendererFactoryInterface arabirimini çağırır. Bunu tembel biçimde yapar: fabrikanın create() yöntemi yalnızca yedek yolunda çalışır. Yedekli bir işlemede sonucun renderLocation değeri, birebir local dizesidir.
Köprü, herhangi bir istek PHP sürecinden çıkmadan önce ağ sınırını korur. HTTPS olmayan bir Worker URL’sini reddeder. Yalnızca ilkini değil tüm A ve AAAA kayıtlarını denetleyerek, özel veya ayrılmış adres alanına çözümlenen bir Worker ana bilgisayarını reddeder. Ayrıca bağlanmadan hemen önce ana bilgisayarı yeniden çözümler; bu da DNS yeniden bağlamaya karşı time-of-check/time-of-use (TOCTOU) penceresini kapatır. Bir PSR-17 yanıt fabrikası ve çözümlenmiş bir IP kümesi ya da Subject Public Key Info (SPKI) sabitleri sağladığınızda köprü, sabitlenmiş bir cURL taşıması kullanır. Bu taşıma, bağlantıyı doğrulanmış IP’lere bağlar (CURLOPT_RESOLVE), TLS genel anahtar sabitlemeyi uygular (CURLOPT_PINNEDPUBLICKEY), eşi ve ana bilgisayarı doğrular ve yönlendirmeleri izlemez.
API yüzeyi
“API yüzeyi” başlıklı bölüm// Configuration (final readonly):new CloudflareRendererConfig( string $workerUrl, // required, must be HTTPS string $apiToken, // required, #[SensitiveParameter] int $renderTimeout = 30, string $defaultCss = '', int $maxHtmlSize = 5_000_000, ?string $r2FontBucket = null, bool $fallbackToLocal = true, list<string> $pinnedPublicKeys = [], // sha256/<base64> list<string> $backupPublicKeys = [],)CloudflareRendererConfig::fromArray(array $config): self
// The renderer:new CloudflareHtmlRenderer( CloudflareRendererConfig $config, ClientInterface $httpClient, // PSR-18 RequestFactoryInterface $requestFactory, // PSR-17 StreamFactoryInterface $streamFactory, // PSR-17 ?LoggerInterface $logger = null, // PSR-3 ?LocalRendererFactoryInterface $localRendererFactory = null, ?HtmlSecurityPolicyInterface $htmlSecurityPolicy = null, ?ResponseFactoryInterface $responseFactory = null, // enables pinned transport)CloudflareHtmlRenderer::render(string $html, float $widthPt = 595.28, float $heightPt = 0.0, list<string> $fontFiles = []): CloudflareRenderResultCloudflareHtmlRenderer::isAvailable(): boolrender() varsayılan olarak A4 genişliğini (595.28 punto) ve otomatik olarak algılanan yüksekliği (heightPt: 0) kullanır. Tam alan başvurusu ve fromArray() anahtar eşlemesi için Ayrıca bakınız bölümündeki Cloudflare yapılandırma sayfasına bakın.
Kod örneği — hızlı başlangıç
“Kod örneği — hızlı başlangıç” başlıklı bölümYapılandırmayı oluşturun, işleyiciyi kurun, işleyin ve baytları yazın.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;use GuzzleHttp\Psr7\HttpFactory;use NextPDF\Cloudflare\CloudflareHtmlRenderer;use NextPDF\Cloudflare\CloudflareRendererConfig;use NextPDF\Cloudflare\Exception\CloudflareNotAvailableException;use NextPDF\Cloudflare\Exception\CloudflareRenderException;
$config = new CloudflareRendererConfig( workerUrl: 'https://pdf-renderer.example.workers.dev/render', apiToken: getenv('CF_PDF_TOKEN') ?: throw new RuntimeException('CF_PDF_TOKEN not set'),);
$httpFactory = new HttpFactory();
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: new Client(), requestFactory: $httpFactory, streamFactory: $httpFactory, responseFactory: $httpFactory, // enables the pinned cURL transport);
try { $result = $renderer->render('<h1>Hello from the edge</h1>');
if (!$result->isValid()) { throw new RuntimeException('Worker did not return a valid PDF'); }
file_put_contents('output.pdf', $result->pdfData);} catch (CloudflareRenderException $exception) { // Worker answered but the render failed. Not retried with fallback. fwrite(STDERR, 'Render failed: ' . $exception->getMessage() . PHP_EOL); exit(1);} catch (CloudflareNotAvailableException $exception) { // Edge unreachable and no usable fallback. fwrite(STDERR, 'Edge unavailable: ' . $exception->getMessage() . PHP_EOL); exit(2);}Belirteç ortamdan okunur ve asla koda sabit olarak gömülmez. workerUrl HTTPS kullanmalıdır; köprü, herhangi bir istek göndermeden önce bir http:// URL’sini reddeder.
Kod örneği — üretim
“Kod örneği — üretim” başlıklı bölümÜretimde, erişilemeyen bir Worker nedeniyle isteğin başarısız olması yerine yedeğe geçmek için yerel bir işleyici fabrikası bağlayın. TLS sabitlerini bir yedek sabitle birlikte yapılandırın. Fabrikanın create() yöntemi yalnızca yedek yolunda çalışır.
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;use NextPDF\Cloudflare\Contract\LocalRendererFactoryInterface;use NextPDF\Cloudflare\Contract\LocalRendererInterface;
final readonly class ArtisanLocalRendererFactory implements LocalRendererFactoryInterface{ public function __construct(private ChromeHtmlRenderer $chrome) {}
public function create(): LocalRendererInterface { return new readonly class($this->chrome) implements LocalRendererInterface { public function __construct(private ChromeHtmlRenderer $chrome) {}
/** @param array<string, mixed> $options */ public function render(string $html, array $options = []): string { $widthPt = (float) ($options['widthPt'] ?? 595.28); // A4 width $heightPt = (float) ($options['heightPt'] ?? 0.0); // 0 = auto-fit
return $this->chrome->render($html, $widthPt, $heightPt)->getPdfData(); } }; }}Fabrikayı ve sabitleri işleyiciye bağlayın.
<?php
declare(strict_types=1);
use NextPDF\Cloudflare\CloudflareHtmlRenderer;use NextPDF\Cloudflare\CloudflareRendererConfig;
$config = CloudflareRendererConfig::fromArray([ 'worker_url' => getenv('CF_WORKER_URL') ?: '', 'api_token' => getenv('CF_PDF_TOKEN') ?: '', 'render_timeout' => 60, 'fallback_to_local' => true, 'pinned_public_keys' => ['sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg='], 'backup_public_keys' => ['sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys='],]);
$renderer = new CloudflareHtmlRenderer( config: $config, httpClient: $httpClient, requestFactory: $httpFactory, streamFactory: $httpFactory, logger: $logger, localRendererFactory: new ArtisanLocalRendererFactory($chrome), responseFactory: $httpFactory,);Yedek çalıştığında sonucun renderLocation değeri local, heightPt değeri ise 0.0 olur. Köprü, yedeği önce warning, ardından info düzeyinde günlüğe kaydeder. Sertifikayı döndürmeden önce her zaman bir yedek sabit yapılandırın; böylece planlı bir döndürme, köprünün uç noktasına erişimini kesmez.
Sınır durumlar ve dikkat edilecek noktalar
“Sınır durumlar ve dikkat edilecek noktalar” başlıklı bölüm- Bir Worker hatası, erişilebilirlik başarısızlığı değildir. Bir HTTP hatası veya hatalı biçimlendirilmiş gövde döndüren bir Worker,
CloudflareRenderExceptionoluşturur ve yedekle asla yeniden denenmez. Yalnızca erişilemeyen bir uç nokta yedeğe geçer. İki catch kolunu ayrı tutun. - Yedek için hem bayrak hem de bir fabrika gerekir.
fallbackToLocal: trueayarlı olduğunda ancak hiçbir fabrika bağlı olmadığında, erişilemeyen bir WorkerCloudflareNotAvailableExceptionoluşturur ve eksik fabrikanın adını belirtir. Fabrikayı bağlayın. isAvailable()bir ipucudur, garanti değildir. Kimliği doğrulanmış birHEADgönderir vetruedeğerini500altındaki bir durum için döndürür; sonrakiPOSTyine de başarısız olabilir. Bunu bir sözleşme olarak değerlendirmeyin.- Sabitleme isteğe bağlıdır. Boş bir sabit kümesi, sabitlemeyi devre dışı bırakır. Boş bir kümeyi yalnızca kararlı ve bilinen bir sertifika zinciriyle kullanın ve sabitleme yaptığınızda bir yedek sabit bulundurun.
fontFilesiçin bir R2 paketi (bucket) gerekir.fontFilesbağımsız değişkeni yalnızca yapılandırmar2FontBucketdeğerini ayarladığında önemlidir; aksi takdirde hiçbir etkisi yoktur.- Köprü imzalama yapmaz. PDF baytlarını döndürür. Uçta işleyin, ardından kendi işleminizde imzalayın; böylece imzalama anahtarı uç sınırını asla geçmez.
Performans
“Performans” başlıklı bölümUçta işleme, tarayıcı maliyetini kendi ana bilgisayarlarınızdan kaldırır. Yine de Worker’a yapılan bir HTTPS gidiş-dönüşünün ve Worker’ın işleme süresinin maliyetini üstlenirsiniz; sonuç bunu renderTimeMs olarak raporlar. Köprü, yapılandırılan zaman aşımını sabitlenmiş taşıma aracılığıyla uygular. Bunu, ölçülen Worker gecikmesine pay bırakacak şekilde ayarlayın ve herhangi bir üst akış ağ geçidi zaman aşımının altında tutun. Paket, yalnızca kendi uyguladığı sınırları belirtir. Cloudflare platformunun CPU, bellek veya istek gövdesi üst sınırları hakkında hiçbir iddiada bulunmaz. Bu sınırlar için Cloudflare belgelerine ve Worker’ınıza başvurun.
Güvenlik notları
“Güvenlik notları” başlıklı bölüm- Hedef, istek PHP sürecinden çıkmadan önce doğrulanır. HTTPS olmayan URL’ler reddedilir. Özel veya ayrılmış adres alanına çözümlenen bir ana bilgisayar, tüm A ve AAAA kayıtları boyunca reddedilir. DNS yeniden bağlamaya karşı savunmak için ana bilgisayar, bağlanmadan hemen önce yeniden çözümlenir.
- Sabitlenmiş taşıma, DNS ile TLS’yi bağlar. Bir yanıt fabrikası ve sabitler yapılandırıldığında köprü, bağlantıyı doğrulanmış IP’lere bağlar, SPKI sabitlemeyi uygular, eşi ve ana bilgisayarı doğrular ve doğrulanmamış bir ana bilgisayara yönlendirmeleri izlemeyi reddeder.
- Girdi sınırlandırılır.
maxHtmlSizedeğerini aşan HTML (varsayılan 5 MB), aşırı büyük bir base64 veri URI’si ve herhangi bir<meta http-equiv="refresh">etiketi, istek gönderilmeden önce reddedilir. - Gizli diziler maskelenir ve değişmezdir.
apiTokenve R2 anahtarları#[SensitiveParameter]taşır; bu nedenle yığın izleri (stack trace) bunları maskeler ve yapılandırma nesnelerifinal readonlyniteliğindedir. Gizli dizileri ortamdan veya bir gizli dizi yöneticisinden yükleyin; bunları asla işleme (commit) eklemeyin. - Asla boş bir
catchbloğu yazmayın. Her örnek, ilgili özel durum türünü yakalar ve günlüğe kaydeder veya tanımlı bir kodla çıkar.
Tam güvenlik modeli, Ayrıca bakınız bölümündeki Cloudflare güvenlik ve operasyonlar sayfasındadır. SSRF ve DNS yeniden bağlama savunmasını, sabitleme işlemlerini, gizli dizi yönetimini ve ilgili OWASP ile RFC 7469 maddelerini kapsar.
Uygunluk
“Uygunluk” başlıklı bölümBu kılavuz, tek başına hiçbir normatif standart iddiasında bulunmaz. Üst akıştaki Cloudflare güvenlik ve operasyonlar ile yapılandırma sayfalarında, köprünün tüm kayıtları kapsayan DNS çözümlemesi ve TOCTOU yeniden denetimi OWASP SSRF önleme kılavuzuna, TLS genel anahtar sabitlemesi ve yedek sabit kurtarması ise şu belgeye dayandırılır: RFC 7469. Bu yemek kitabı sayfası kullanımı yeniden açıklar ve bu atıfları söz konusu sayfalara bırakır. Köprü hiçbir imzalama yapmaz ve hiçbir imza uygunluğu iddiasında bulunmaz.
Ayrıca bakınız
“Ayrıca bakınız” başlıklı bölüm- Artisan Chrome işleyicisiyle HTML’i PDF’ye işleme — burada yerel yedek olarak kullanılan işlem içi işleyicidir.
- Cloudflare hızlı başlangıç — ilk uçta işleme akışı ve sonuç modeli.
- Cloudflare güvenlik ve operasyonlar — SSRF, DNS yeniden bağlama, sabitleme ve gizli dizi döndürme.
- Cloudflare üretim kullanımı — yedek bağlama, telemetri, R2 arşivleme ve API koruması.