Kết xuất HTML thành PDF bằng trình kết xuất Chrome của Artisan
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Cầu nối Artisan kết xuất HTML bằng một tiến trình Chrome headless, rồi nhập kết quả vào tài liệu NextPDF dưới dạng Form XObject vector. Văn bản vẫn chọn và tìm kiếm được thay vì bị raster hóa. Bạn gắn một ChromeRendererConfig, gọi writeHtmlChrome() trên tài liệu hoặc dùng trực tiếp ChromeHtmlRenderer, để Chrome xử lý bố cục. Hướng dẫn này trình bày lời gọi kết xuất, cô lập mạng, định cỡ trang, chiều cao nội dung và vòng đời của một trình kết xuất chạy lâu trong worker.
Điều kiện cần có:
- Đã cài đặt NextPDF core và
nextpdf/artisan. - Một tệp nhị phân Chrome hoặc Chromium đã được cài đặt, và người dùng worker có thể chạy nó ở chế độ headless. Hãy kiểm tra bằng
chromium --headless --dump-dom about:blanktrước khi bắt đầu. Trang thiết lập trình kết xuất Chrome trong mục Xem thêm trình bày cách cung cấp tệp nhị phân và quyết định về sandbox của container.
Hướng dẫn này giả định bạn có thể chạy tiến trình Chrome gần ứng dụng. Để có ví dụ chạy được đầu tiên, hãy đọc phần khởi động nhanh Artisan.
Cài đặt
Phần tiêu đề “Cài đặt”Cài đặt cầu nối cùng với core.
composer require nextpdf/artisanCài đặt bản dựng Chrome hoặc Chromium mà người dùng worker có thể chạy. Trên Debian hoặc Ubuntu, hãy dùng gói của bản phân phối.
apt-get install -y chromiumXác nhận tệp nhị phân chạy được ở chế độ headless khi đăng nhập bằng người dùng worker.
chromium --headless --dump-dom about:blankMã thoát 0 cùng một document object model (DOM) rỗng nghĩa là tệp nhị phân và các thư viện chia sẻ của nó đều có mặt. Mã thoát khác 0 là lỗi mà cầu nối sẽ báo dưới dạng ChromeRenderException. Hãy khắc phục tại đây trước.
Tổng quan khái niệm
Phần tiêu đề “Tổng quan khái niệm”writeHtmlChrome() là một phương thức trên Document của NextPDF core. Phương thức này xác thực đầu vào, phân giải trình kết xuất Artisan, gửi HTML đến Chrome qua Chrome DevTools Protocol (CDP), phân tích cú pháp PDF trả về và nhúng trang 0 dưới dạng Form XObject tại vị trí con trỏ hiện tại. Chrome chạy như một tiến trình con của PHP worker. Cầu nối điều khiển Chrome qua CDP thay vì kết nối tới một tiến trình Chrome riêng qua cổng gỡ lỗi, nên không có điểm cuối mạng nào cần để lộ hay xác thực.
Cầu nối kết xuất trong mô hình mạng mặc định từ chối. Mỗi lần kết xuất đều dùng một Content-Security-Policy từ chối mọi nguồn gốc tài nguyên (default-src 'none') và chỉ cho phép hình ảnh nội tuyến (img-src data:). Cầu nối cũng chặn mọi URL tài nguyên con ở lớp truyền tải CDP bằng Network.setBlockedURLs(['*']). Vì vậy, hình ảnh, stylesheet, phông chữ, script hoặc iframe từ xa trong HTML của bạn sẽ không tải. Hãy nội tuyến mọi asset dưới dạng một URI data:. Đây là cách cầu nối xử lý rủi ro server-side request forgery (SSRF) khi kết xuất HTML có thể không đáng tin; cơ chế này áp dụng bất kể cấu hình ra sao.
Mô hình kích cỡ trang có hai chế độ. Khi bạn cung cấp cả chiều rộng và chiều cao, tính bằng điểm PDF, Chrome in ra đúng kích cỡ giấy đó. Khi chiều cao bị bỏ qua hoặc là null, cầu nối đo chiều cao nội dung đã kết xuất trong Chrome, chuyển sang điểm, rồi thêm một vùng đệm an toàn nhỏ khoảng 14,4 điểm cho việc dồn lại bố cục. Nhờ vậy, printToPDF không tràn sang trang thứ hai mà bộ nhập chỉ lấy trang 0 sẽ cắt bỏ.
Bề mặt API
Phần tiêu đề “Bề mặt API”// On a NextPDF core Document (the HasTextOutput concern):writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static
// The standalone renderer:new ChromeHtmlRenderer(ChromeRendererConfig $config, ?LoggerInterface $logger = null)ChromeHtmlRenderer::render(string $html, float $widthPt, float $heightPt = 0.0): ChromeRenderResultChromeHtmlRenderer::close(): void
// The configuration value object (final readonly):new ChromeRendererConfig( ?string $chromeBinaryPath = null, int $renderTimeout = 30, string $defaultCss = '', int $maxHtmlSize = 5_000_000, bool $noSandbox = false,)ChromeRendererConfig::fromArray(array $config): selfChromeRendererConfig là điểm cấu hình duy nhất. Nó bất biến, nên hãy tạo một thực thể mới để thay đổi một giá trị. ChromeRenderResult::getPdfData() trả về các byte PDF. Trang cấu hình Artisan trong mục Xem thêm liệt kê đầy đủ tham chiếu tùy chọn và các cờ khởi chạy Chrome cố định.
Mã mẫu — khởi động nhanh
Phần tiêu đề “Mã mẫu — khởi động nhanh”Gắn cấu hình vào tài liệu, kết xuất HTML đáng tin cậy, rồi lưu lại.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Core\Document;
$config = new ChromeRendererConfig( chromeBinaryPath: '/usr/bin/chromium',);
$document = Document::createStandalone();$document->setChromeRendererConfig($config);$document->addPage();
$document->writeHtmlChrome(' <div style="display: flex; gap: 20px; font-family: sans-serif;"> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Revenue</h2> <p style="font-size: 2em; color: #2563eb;">$124,500</p> </div> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Orders</h2> <p style="font-size: 2em; color: #16a34a;">1,847</p> </div> </div>');
$document->save('/tmp/report.pdf');Chrome xử lý bố cục flex; các con số vẫn chọn được trong kết quả vì trang được nhúng dưới dạng Form XObject vector, chứ không phải hình ảnh raster. Để vừa với một trang A4 cố định, hãy truyền chiều rộng và chiều cao tính bằng điểm.
$document->writeHtmlChrome($html, width: 595.28, height: 841.89);Mã mẫu — môi trường sản xuất
Phần tiêu đề “Mã mẫu — môi trường sản xuất”Trong môi trường sản xuất, hãy tạo một trình kết xuất cho mỗi worker, truyền vào một PSR-3 logger, bắt riêng hai loại ngoại lệ khác nhau và giải phóng tiến trình Chrome theo cách xác định khi tắt.
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Artisan\Exception\ChromeNotAvailableException;use NextPDF\Artisan\Exception\ChromeRenderException;use Psr\Log\LoggerInterface;
final class ReportRenderer{ private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger) { $config = ChromeRendererConfig::fromArray([ 'chrome_binary' => getenv('CHROME_BINARY') ?: null, 'render_timeout' => 45, 'max_html_size' => 2_000_000, 'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'), ]);
$this->renderer = new ChromeHtmlRenderer($config, $logger); }
public function render(string $html, float $widthPt, float $heightPt = 0.0): string { try { return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData(); } catch (ChromeNotAvailableException $exception) { // Deployment fault: the Chrome runtime is missing. Page on-call. throw $exception; } catch (ChromeRenderException $exception) { // Render-time fault: timeout, crash, or empty output. Retryable once. throw $exception; } }
public function shutdown(): void { $this->renderer->close(); }}Hãy tạo trình kết xuất một lần, rồi tái sử dụng nó. Browser pool bên dưới giữ một tiến trình Chrome luôn chạy và khởi động lại nó sau mỗi 100 lần kết xuất để giới hạn mức tăng bộ nhớ. Hai nhánh catch tách lỗi triển khai, chẳng hạn như thiếu runtime, ra khỏi lỗi trong lúc kết xuất mà bạn có thể thử lại một lần. Không để trống bất kỳ khối catch nào. Hãy gọi shutdown() khi worker tắt để giải phóng tiến trình Chrome thay vì chờ destructor.
Hãy dựng cấu hình từ mảng cấu hình của framework để dùng các khóa kiểu snake-case, và ghim chromeBinaryPath trong môi trường sản xuất để tệp nhị phân được xác định rõ.
Trường hợp đặc biệt & điểm cần lưu ý
Phần tiêu đề “Trường hợp đặc biệt & điểm cần lưu ý”- HTML rỗng là thao tác không làm gì.
writeHtmlChrome('')trả về tài liệu không thay đổi. - Chưa có trang nào. Nếu tài liệu chưa có trang nào,
writeHtmlChrome()sẽ thêm một trang trước khi kết xuất. - Asset từ xa không tải — đây là chủ ý thiết kế.
<img src="https://...">kết xuất trống. Hãy nội tuyến mọi asset dưới dạng một URIdata:. Đây là cơ chế cô lập mạng, không phải lỗi. - Chỉ trang 0 được nhập. Chiều cao tự khớp sẽ thêm vùng đệm dồn lại bố cục để tạo ra một trang duy nhất. Với chiều cao được chỉ định rõ ràng, không có vùng đệm nào được thêm vào và kết quả khớp chính xác với kích cỡ giấy đã yêu cầu, vì vậy hãy định kích cỡ chiều cao sao cho vừa với nội dung của bạn.
- Thiếu cầu nối. Nếu
nextpdf/artisanchưa được cài đặt, core sẽ ném ra một ngoại lệ bố cục thay vì lỗi nghiêm trọng. Nếu thiếu thư việnchrome-php/chrome, cầu nối sẽ ném raChromeNotAvailableExceptionkèm theo lệnh cài đặt. defaultCssvà</style>. Mọi chuỗi</style>trongdefaultCssđều bị loại bỏ trước khi tiêm vào, như một biện pháp phòng vệ chống thoát khỏi khối style. Hãy tính đến điều đó nếu bạn dùng template cho CSS.
Hiệu năng
Phần tiêu đề “Hiệu năng”Lần kết xuất đầu tiên phải chịu chi phí khởi động và dựng bố cục của Chrome. Các lần kết xuất sau tái sử dụng tiến trình Chrome đang chạy, nên hiếm khi phải chịu chi phí khởi động. Hãy tạo một trình kết xuất cho mỗi worker và tái sử dụng nó. Đừng tạo một trình kết xuất cho mỗi yêu cầu. Hãy lường trước một đợt tăng độ trễ vào mỗi lần kết xuất thứ 100, khi cầu nối khởi động lại tiến trình Chrome để giới hạn bộ nhớ. Hãy tính đến điều đó trong các mục tiêu độ trễ của bạn thay vì coi nó là sự cố. Hãy kết hợp renderTimeout với một ngân sách yêu cầu ở thượng nguồn trên mọi đường dẫn mà đầu vào không đáng tin có thể tiếp cận.
Lưu ý về bảo mật
Phần tiêu đề “Lưu ý về bảo mật”- Cô lập mạng là biện pháp kiểm soát chính. Cầu nối hoàn toàn không cho phép bất kỳ lượt tải tài nguyên con ra ngoài nào: CSP
default-src 'none'cộng với một lệnh chặn mọi URL ở cấp truyền tải CDP. Nó không triển khai danh sách miền được phép vì không cần. Hãy nội tuyến asset dưới dạng các URIdata:. - Đầu vào bị giới hạn trước khi liên hệ với Chrome. Cầu nối từ chối HTML vượt quá
maxHtmlSize(mặc định 5 MB), một data URI base64 quá lớn (biện pháp chống bom giải nén), và mọi thẻ<meta http-equiv="refresh">(vốn có thể điều hướng tới một điểm cuối nội bộ). Hãy giữmaxHtmlSizeở giá trị mặc định trừ khi một khối lượng công việc đã biết cần nhiều hơn. Tăng giá trị này sẽ mở rộng bề mặt gây cạn kiệt tài nguyên. - Sandbox của Chrome là một biện pháp kiểm soát riêng. Đặt
noSandbox: truesẽ khởi chạy Chrome với--no-sandbox, vốn loại bỏ sự cô lập tiến trình của Chrome. Đó là một sự suy giảm thực sự về khả năng cô lập, không phải một cờ chỉ mang tính hình thức. Hãy để nó làfalsekhi ở ngoài container. Khi sandbox của container không thể khởi tạo, hãy chạy Chrome với một người dùng không phải root trong một container bị giới hạn, và xem việc triển khai như một yêu cầu tin cậy cao hơn đối với đầu vào. - Nhật ký chỉ mang siêu dữ liệu. Hãy truyền vào một PSR-3 logger. Cầu nối ghi nhật ký độ dài byte, kích thước và các sự kiện vòng đời, không bao giờ ghi HTML, byte PDF hay văn bản đã trích xuất.
- Đừng bao giờ để lộ một cổng gỡ lỗi từ xa của Chrome. Cầu nối không dùng đến cổng đó, và một cổng CDP mở là kênh điều khiển không được xác thực.
Mô hình mối đe dọa đầy đủ, bao gồm biện pháp phòng vệ SSRF, ranh giới sandbox được nêu rõ và danh mục các chế độ lỗi, có trên trang bảo mật và vận hành Artisan được liên kết trong mục Xem thêm. Trang đó ghim các thuật ngữ OWASP, CWE và NIST liên quan.
Sự tuân thủ
Phần tiêu đề “Sự tuân thủ”Bản thân hướng dẫn này không đưa ra tuyên bố tuân thủ tiêu chuẩn quy phạm nào. Trang bảo mật và vận hành Artisan ở thượng nguồn ánh xạ các biện pháp kiểm soát về mạng, cô lập và cạn kiệt tài nguyên của cầu nối tới OWASP ASVS, CWE Top 25 (SSRF / tiêu thụ tài nguyên không kiểm soát) và NIST SP 800-53 SC-7. Trang cookbook này chỉ nhắc lại cách dùng và nhường các trích dẫn quy phạm đó cho trang kia. Cầu nối không thực hiện thao tác mã hóa nào; việc ký và mã hóa là phần việc của core hoặc phiên bản thương mại và không bị Artisan tác động.
Xem thêm
Phần tiêu đề “Xem thêm”- Kết xuất tại edge với Cloudflare — kết xuất HTML tại edge với phương án dự phòng cục bộ.
- Khởi động nhanh Artisan — ví dụ tối giản cho lần kết xuất đầu tiên.
- Thiết lập trình kết xuất Chrome — cung cấp tệp nhị phân, quyết định về sandbox của container và probe kiểm tra tình trạng.
- Bảo mật và vận hành Artisan — mô hình cô lập mạng, ranh giới sandbox và các chế độ lỗi.