使用 Gotenberg 將 Office 文件轉換成 PDF
Gotenberg 橋接會將一份 Office 文件轉成 PDF。它會透過 HTTPS 將文件送到 Gotenberg 微服務,再回傳 PDF 位元組。先用不可變的 GotenbergConfig 描述服務,把一個 PSR-18 用戶端與 PSR-17 工廠接進 GotenbergBridge,以健康檢查探測服務狀態,再轉換磁碟上的檔案或記憶體中的位元組。本指南涵蓋依副檔名偵測格式、健康探測、型別化的失敗合約,以及交接給 NextPDF 後處理的流程。
先確認前置條件:
- 已安裝 NextPDF 核心與
nextpdf/gotenberg。 - 有一個可透過 HTTPS 存取的 Gotenberg 服務。在任何請求離開行程之前,橋接就會拒絕單純的
http://URL。 - 已安裝一個 PSR-18 用戶端,以及 PSR-17 的 request 與 stream 工廠。若要做 DNS 與 TLS 釘選,你還要再提供一個 PSR-17 response 工廠。
- 輸入是六種已辨識的 Office 格式之一:
.docx、.xlsx、.pptx、.odt、.ods或.odp。橋接會以ValueError拒絕任何其他副檔名。
這是一份操作指南。如果需要可直接執行的完整程式,請閱讀 Gotenberg 快速入門。
安裝橋接、一個 PSR-18 用戶端,以及 PSR-17 工廠。
composer require nextpdf/gotenberg guzzlehttp/guzzle啟動一個可透過 HTTPS 存取的 Gotenberg 服務,並從祕密管理器或注入的環境值取得任何 bearer token。橋接從不讀取環境變數,也不會自行建立 HTTP 用戶端;這兩者都由你提供。
概念說明
標題為「概念說明」的區段GotenbergBridge::convertFile() 接受磁碟上的路徑。它會正規化路徑以阻擋路徑穿越,把副檔名對映到支援的格式,檢查大小與檔名,再把一個 multipart 請求送到 <apiUrl>/forms/libreoffice/convert。convertString() 會對你手上已有的位元組執行同樣流程;它會使用原始檔名來偵測副檔名。
格式偵測是依副檔名判斷。橋接會把 .docx、.xlsx、.pptx、.odt、.ods 與 .odp 對映到各自的格式,並在任何網路流量之前就以 ValueError 拒絕其他任何格式。結果物件會以列舉值揭露偵測到的來源格式。
橋接本質上是一次同步的 HTTP 往返,外層加上驗證。它不會重試、排隊、快取或限流;那些責任屬於橋接外圍的應用程式。請把每次轉換都視為對一個由你營運、但無法在行程內掌控的服務發出的遠端呼叫,並針對它的延遲與失敗來設計。
橋接會以型別化的例外揭露失敗,絕不回傳部分結果或未經驗證的結果:
- 非
200狀態、Content-Type(其值不含application/pdf)不符,或內容開頭不是%PDF,都會各自引發GotenbergConvertException。只有當這三項檢查全部通過時,橋接才會回傳結果。 - PSR-18 用戶端失敗(包括網路失敗或逾時)會被包裝成
GotenbergConvertException,並以原始例外作為成因。 - 驗證失敗(非 HTTPS URL、私有或保留位址、輸入過大、不安全的檔名)會在任何網路流量之前就引發
RuntimeException。 - 無法辨識的副檔名會在任何網路流量之前就引發
ValueError。
API 介面
標題為「API 介面」的區段// Configuration (final readonly):new GotenbergConfig( string $apiUrl, // required, must be HTTPS int $timeout = 30, // hard transfer timeout, seconds int $maxFileSize = 52_428_800, // 50 MiB string $apiKey = '', // #[SensitiveParameter]; Bearer when non-empty list<string> $pinnedPublicKeys = [], // sha256/<base64> list<string> $backupPublicKeys = [],)GotenbergConfig::fromArray(array $config): selfGotenbergConfig::isValid(): bool
// The bridge:new GotenbergBridge( GotenbergConfig $config, ClientInterface $httpClient, // PSR-18 RequestFactoryInterface $requestFactory, // PSR-17 StreamFactoryInterface $streamFactory, // PSR-17 ?LoggerInterface $logger = null, // PSR-3 ?HtmlSecurityPolicyInterface $htmlSecurityPolicy = null, ?ResponseFactoryInterface $responseFactory = null, // enables pinned transport)GotenbergBridge::isAvailable(): boolGotenbergBridge::convertFile(string $path): GotenbergConvertResultGotenbergBridge::convertString(string $bytes, string $originalFilename): GotenbergConvertResult結果物件會揭露 pdfData、sourceFormat 列舉、isValid()(當內容非空且開頭為 %PDF 時為 true),以及 size()。完整的欄位參考、fromArray() 鍵對映,以及傳輸選擇規則,請見「另請參閱」中連結的 Gotenberg 組態頁面。
程式碼範例 — 快速開始
標題為「程式碼範例 — 快速開始」的區段描述服務、接好橋接、先探測服務,再轉換一個檔案。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Gotenberg\GotenbergBridge;use NextPDF\Gotenberg\GotenbergConfig;use NextPDF\Gotenberg\GotenbergConvertException;
$config = new GotenbergConfig( apiUrl: 'https://gotenberg.example.com', timeout: 60, apiKey: getenv('GOTENBERG_TOKEN') ?: '',);
$bridge = new GotenbergBridge( config: $config, httpClient: $httpClient, // your PSR-18 client requestFactory: $requestFactory, // your PSR-17 factory streamFactory: $streamFactory, // your PSR-17 factory responseFactory: $responseFactory, // enables the pinned transport);
// Probe before converting. The probe validates the URL with no network// traffic, then sends a HEAD to <apiUrl>/health.if (!$bridge->isAvailable()) { throw new RuntimeException('Gotenberg is not reachable.');}
try { $result = $bridge->convertFile('/path/to/report.docx');} catch (GotenbergConvertException $exception) { // Bad config, HTTP failure, non-200, wrong Content-Type, or non-PDF body. throw $exception;}
if (!$result->isValid()) { throw new RuntimeException('Result is not a valid PDF.');}
file_put_contents('/path/to/report.pdf', $result->pdfData);這個類別是 NextPDF\Gotenberg\GotenbergConfig(上面那行使用的正是你的程式碼必須匯入的命名空間)。isAvailable() 遇到空白、非 HTTPS 或私有位址的 URL,以及任何網路錯誤時,都會回傳 false,而且從不擲出例外;只要 /health 回傳低於 500 的狀態,就表示可用。
程式碼範例 — 正式環境
標題為「程式碼範例 — 正式環境」的區段正式環境的轉換流程會分別接住每一種失敗型別,只在正確條件下重試,並從呼叫端側限制並行量。下面的 catch 順序是完整窮舉的。
<?php
declare(strict_types=1);
use NextPDF\Gotenberg\GotenbergBridge;use NextPDF\Gotenberg\GotenbergConvertException;use Psr\Log\LoggerInterface;use RuntimeException;use ValueError;
final readonly class OfficeConverter{ public function __construct( private GotenbergBridge $bridge, private LoggerInterface $logger, ) {}
public function convert(string $path): string { try { $result = $this->bridge->convertFile($path); } catch (GotenbergConvertException $exception) { // Transport, non-200, wrong Content-Type, or non-PDF body. // Retry only on transport-level or 502/503/504 causes, with // bounded exponential backoff and jitter — never blind retries. $this->logger->error('gotenberg.convert.failed', [ 'path' => basename($path), 'exception' => $exception::class, ]); throw $exception; } catch (ValueError $exception) { // Extension is not one of the six recognized Office formats. $this->logger->warning('gotenberg.convert.unsupported_format', [ 'path' => basename($path), ]); throw $exception; } catch (RuntimeException $exception) { // Non-HTTPS URL, private address, oversized input, or unsafe name. $this->logger->error('gotenberg.convert.rejected', [ 'path' => basename($path), 'exception' => $exception::class, ]); throw $exception; }
if (!$result->isValid()) { throw new RuntimeException('Gotenberg returned an invalid PDF body.'); }
return $result->pdfData; }}只在傳輸層的 GotenbergConvertException(一個被包裝的 PSR-18 用戶端例外),以及等冪的伺服器錯誤(502、503、504)時重試。一個 400 級別的回應通常代表輸入有誤,所以重試也會以同樣方式失敗。請限制總嘗試次數與總牆鐘時間。把同時進行的轉換數量限制在你的 Gotenberg 部署可承受的容量內。橋接本身是無狀態的,可安全地從多個 worker 使用,但服務的轉換容量是有限的。
邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 格式偵測是依副檔名判斷。 一個
.docx被改名成.txt會以ValueError拒絕;而一個.txt被改名成.docx則會被送到 Gotenberg,並在那裡失敗。接受上傳時,請依據真正的格式,而不是檔名。 fromArray()在設計上是寬容的。 它會對格式錯誤的輸入靜默代入預設值。請在啟動流程中驗證來源陣列,讓缺少的 URL 早早以組態錯誤浮現,而不是變成每次轉換時才丟出的例外。- 大小上限是在行程內強制執行的。
maxFileSize(預設 50 MiB)會在請求送出之前就檢查,所以過大的檔案永遠不會消耗服務容量。請把上限降到剛好符合你的文件所需;較小的上限是更便宜的拒絕服務防護。 - 探測不是免費的。 從就緒或健康檢查端點呼叫
isAvailable(),而不是在每次轉換前都呼叫。每次轉換都跑一遍,會讓你對服務的請求率加倍,卻毫無好處。 - 行程內不做快取。 如果同一份文件會被反覆轉換,請在你的應用程式中以輸入內容雜湊為鍵,快取產出的 PDF。
renderTimeMs由你自己設定。 除非你的整合有測量並設定它,否則結果的計時欄位會是0.0。如果你需要這個數字,請自行為這次呼叫計時。
在請求期間,一次轉換會在 Gotenberg 端佔住一條連線與一個 LibreOffice worker,而 Office 轉換並非瞬間完成。請依真實文件量測到的轉換延遲來設定 timeout,並保留餘裕。把它設得低於任何上游閘道或 PHP max_execution_time,這樣橋接會先逾時,你會拿到一個型別化的例外,而不是被強制終止的行程。請用佇列、號誌或大小符合服務容量的 worker 池來限制並行量。行程內沒有快取;如果你會反覆轉換同一份輸入,請在你的應用程式中加上一層快取。
安全性須知
標題為「安全性須知」的區段- 送出前先做 HTTPS 與位址篩查。 在任何請求離開行程之前,橋接就會拒絕非 HTTPS URL,以及會 resolve(解析)到私有或保留位址空間的目的地。每次重試呼叫都會重新執行那套驗證,所以重試無法繞過 SSRF 防護。
- 依需求採用釘選傳輸。 當你提供 response 工廠與釘選(或已有一組解析後的 IP)時,橋接會把連線綁定到解析後的位址、強制執行 SPKI 釘選、驗證對端與主機、套用逾時,並停用跟隨重新導向。在憑證輪替之前,先設定好一個備援釘選。
- 不要信任上傳檔案所宣告的內容型別。 接受使用者上傳時,請自行驗證真正的檔案型別;副檔名對映到格式只是一個路由決策,不是真實性檢查。
- 祕密會被遮蔽且不可變。
apiKey帶有#[SensitiveParameter],而組態是final readonly。請從祕密管理器取得 token;絕不要把它提交進版本庫。記錄下來的轉換項目會帶有 URL、檔名、格式與內容長度 —— 絕不會有檔案內容或 token。 - 絕不要寫空的
catch區塊。 每個範例都會接住特定型別,並連同上下文記錄下來。
完整的安全性與部署模型,請見 Gotenberg 安全性與維運頁面。PSR-18 傳輸合約,以及「不要信任內容型別」的指引,都釘選到上游正式使用頁面上各自的條款。
符合性
標題為「符合性」的區段本指南本身不提出任何規範性的標準主張。橋接的 PSR-18 傳輸行為(用戶端只在無法送出或無法剖析回應時才引發例外;4xx/5xx 是正常的回傳值)、檔案上傳驗證指引,以及 TLS 釘選模型,都釘選到上游 Gotenberg 正式使用與組態頁面上的 PSR-18、OWASP 與 RFC 7469。這份 cookbook 頁面只重述用法,並把那些引用交給那些頁面處理。橋接會產出 PDF 位元組,然後就停下來。簽章、PDF/A 設定檔與浮水印,屬於 NextPDF 後處理的範疇,也是商業版能力,並非這個橋接的一部分。
另請參閱
標題為「另請參閱」的區段- 從控制器回傳產生的 PDF —— 把轉換後的 PDF 當成 HTTP 回應回傳。
- Gotenberg 快速入門 —— 完整可執行的轉換程式。
- Gotenberg 組態 —— 每個欄位、
fromArray()對映,以及傳輸選擇。 - Gotenberg 正式使用 —— 祕密、逾時、重試、並行量,以及後處理界線。