Преобразование документов Office в PDF с помощью Gotenberg
Краткий обзор
Заголовок раздела «Краткий обзор»Мост Gotenberg преобразует документы Office в PDF. Он отправляет документ в микросервис Gotenberg по HTTPS и возвращает байты PDF. Вы описываете службу неизменяемым объектом GotenbergConfig, подключаете клиент PSR-18 и фабрики PSR-17 к GotenbergBridge, проверяете доступность службы и преобразуете файл с диска или байты из памяти. В руководстве разобраны определение формата по расширению, проверка доступности, контракт типизированных отказов и передача результата на постобработку в NextPDF.
Сразу обозначим предварительные требования:
- Ядро NextPDF и
nextpdf/gotenbergустановлены. - Служба Gotenberg доступна по HTTPS. Мост отклоняет URL вида
http://до отправки любого запроса за пределы процесса. - Клиент PSR-18 и фабрики запросов и потоков PSR-17 установлены. Для закрепления DNS и TLS также предоставьте фабрику ответов PSR-17.
- Входные данные должны быть в одном из шести распознаваемых форматов Office:
.docx,.xlsx,.pptx,.odt,.odsили.odp. Любое другое расширение мост отклоняет с ошибкойValueError.
Это практическое руководство. Полную готовую к запуску программу см. в кратком руководстве по Gotenberg.
Установка
Заголовок раздела «Установка»Установите мост, клиент PSR-18 и фабрики PSR-17.
composer require nextpdf/gotenberg guzzlehttp/guzzleЗапустите службу Gotenberg, доступную по HTTPS. Bearer-токен получайте из менеджера секретов или из внедрённого значения окружения. Мост никогда не читает переменные окружения и никогда не создаёт 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с исходным исключением в качестве причины. - Сбои проверки (URL без HTTPS, частный или зарезервированный адрес, слишком большой входной файл, небезопасное имя файла) вызывают
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() (true, когда тело непустое и начинается с %PDF) и 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() возвращает false и никогда не выбрасывает исключение для пустого URL, URL без HTTPS, URL с частным адресом или при любой сетевой ошибке; ответ /health со статусом ниже 500 означает доступность.
Пример кода — продакшен
Заголовок раздела «Пример кода — продакшен»В продакшен-преобразовании каждый тип отказа перехватывается отдельно, повторная попытка выполняется только при подходящих условиях, а параллелизм ограничивается на стороне вызывающего кода. Ниже показан полный порядок перехвата.
<?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. Сам мост не хранит состояние и безопасен для использования из множества обработчиков, но пропускная способность преобразования у службы ограничена.
Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- Определение формата выполняется по расширению. Файл
.docx, переименованный в.txt, отклоняется с ошибкойValueError; файл.txt, переименованный в.docx, отправляется в Gotenberg и завершается ошибкой там. Принимая загружаемые файлы, проверяйте реальный формат, а не полагайтесь на имя. fromArray()по замыслу терпим к входным данным. Он молча подставляет значения по умолчанию для некорректных входных данных. Проверяйте исходный массив на этапе загрузки приложения, чтобы отсутствующий URL выявлялся рано как ошибка конфигурации, а не как исключение при каждом преобразовании.- Ограничение размера срабатывает внутри процесса.
maxFileSize(по умолчанию 50 MiB) проверяется до отправки запроса, поэтому слишком большой файл никогда не расходует пропускную способность службы. Снизьте ограничение под реальные потребности ваших документов; меньшее ограничение — более дешёвая защита от отказа в обслуживании. - Проверка работоспособности не бесплатна. Вызывайте
isAvailable()из эндпойнта готовности или работоспособности, а не перед каждым преобразованием. Её запуск при каждом преобразовании удваивает частоту запросов к службе без какой-либо пользы. - Кэширования внутри процесса нет. Если один и тот же документ преобразуется многократно, кэшируйте полученный PDF в своём приложении, используя в качестве ключа хеш содержимого входных данных.
renderTimeMsвы задаёте сами. В результате поле времени равно0.0, если ваша интеграция не измеряет и не устанавливает его. Замеряйте время вызова самостоятельно, если вам нужно это число.
Производительность
Заголовок раздела «Производительность»На время запроса преобразование удерживает одно соединение и один обработчик LibreOffice на стороне Gotenberg, а обработка Office-документов занимает время. Задавайте timeout с запасом, исходя из измеренной задержки преобразования для ваших реальных документов. Держите его ниже тайм-аута любого вышестоящего шлюза или PHP max_execution_time, чтобы тайм-аут сначала срабатывал в мосте и вы получали типизированное исключение вместо принудительно завершённого процесса. Ограничивайте параллелизм очередью, семафором или пулом обработчиков, размер которого соответствует пропускной способности службы. Кэша внутри процесса нет; добавьте его в своё приложение, если вы многократно преобразуете одни и те же входные данные.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»- Проверка HTTPS и фильтрация адресов до отправки. Мост отклоняет URL без HTTPS и адрес назначения, который разрешается в частное или зарезервированное адресное пространство, до выхода любого запроса за пределы процесса. При каждом повторном вызове эта проверка выполняется заново, поэтому повторная попытка не может обойти защиту от SSRF.
- Транспорт с закреплением по запросу. Когда вы предоставляете фабрику ответов и закрепления (или задан набор разрешённых IP), мост привязывает соединение к разрешённым адресам, применяет закрепление SPKI, проверяет узел и хост, применяет тайм-аут и отключает переход по перенаправлениям. Настройте резервное закрепление перед ротацией сертификата.
- Не доверяйте заявленному типу содержимого загружаемого файла. Принимая файлы от пользователей, проверяйте реальный тип файла самостоятельно; сопоставление расширения с форматом — это решение о маршрутизации, а не проверка подлинности.
- Секреты скрыты и неизменяемы.
apiKeyпомечен атрибутом#[SensitiveParameter], а конфигурация —final readonly. Получайте токен из менеджера секретов; никогда не сохраняйте его в системе контроля версий. Запись журнала о преобразовании содержит URL, имя файла, формат и длину содержимого — никогда содержимое файла или токен. - Никогда не пишите пустой блок
catch. Каждый пример перехватывает конкретный тип и ведёт журнал с контекстом.
Полную модель безопасности и развёртывания смотрите на странице безопасности и эксплуатации Gotenberg. Контракт транспорта PSR-18 и рекомендация не доверять типу содержимого разобраны в соответствующих пунктах на странице продакшен-использования.
Соответствие
Заголовок раздела «Соответствие»Это руководство не делает собственных нормативных заявлений о соответствии стандартам. Поведение транспорта PSR-18 в мосте (клиент выбрасывает исключение только тогда, когда не может отправить запрос или разобрать ответ; 4xx/5xx — это обычное возвращаемое значение), рекомендации по проверке загружаемых файлов и модель закрепления TLS привязаны к PSR-18, OWASP и RFC 7469 на вышестоящих страницах продакшен-использования и конфигурации Gotenberg. Эта страница сборника рецептов повторяет сценарий использования и оставляет эти ссылки на тех страницах. Мост производит байты PDF и на этом останавливается. Подписание, профили PDF/A и водяные знаки относятся к постобработке в NextPDF и являются возможностью коммерческой редакции, а не частью этого моста.
Смотрите также
Заголовок раздела «Смотрите также»- Возврат сгенерированного PDF из контроллера — возврат преобразованного PDF в качестве HTTP-ответа.
- Краткое руководство по Gotenberg — полная, готовая к запуску программа преобразования.
- Конфигурация Gotenberg — каждое поле, карта
fromArray()и выбор транспорта. - Продакшен-использование Gotenberg — секреты, тайм-ауты, повторные попытки, параллелизм и граница постобработки.