Перейти к содержимому

Устранение неполадок NextPDF Gotenberg

Мост сообщает о сбоях явно и как можно раньше. Каждый сбой порождает типизированное исключение с сообщением, в котором указана причина. Используйте эту страницу как каталог: для каждого сбоя приведены тип исключения, фрагмент сообщения, которое вы увидите, точный триггер в пути выполнения кода и способ исправления.

Семейства исключений:

  • GotenbergConvertException — сбой на уровне преобразования (конфигурация, транспорт или ответ).
  • RuntimeException — сбой на уровне проверки, который политика безопасности порождает до любого сетевого трафика.
  • ValueError — нераспознанное расширение файла.
  • InvalidSpkiPinException — некорректная строка SubjectPublicKeyInfo (SPKI) pin для Transport Layer Security (TLS).
  • Тип: GotenbergConvertException
  • Триггер: вы вызвали convertFile() или convertString(), когда GotenbergConfig::isValid() возвращает false. Это происходит, если apiUrl — пустая строка.
  • Исправление: укажите непустой HTTPS URL. Если вы создаёте конфигурацию с помощью fromArray(), учтите: он молча подставляет '' вместо отсутствующего или не строкового api_url. Проверяйте источник конфигурации при загрузке.

Эти сбои возникают из-за политики безопасности, которая защищает от подделки запросов на стороне сервера (SSRF). Мост порождает их до отправки какого-либо запроса. Каждый из них — обычное RuntimeException.

  • Триггер: схема настроенного URL не равна https. Проверка не учитывает регистр, поэтому HTTPS:// принимается.
  • Исправление: разместите Gotenberg за TLS и настройте конечную точку HTTPS. Обычный HTTP отклоняется даже для локальной разработки.
  • Триггер: URL не удаётся разобрать на схему и хост.
  • Исправление: укажите синтаксически корректный абсолютный URL, например https://gotenberg.example.com:3000.

Сообщение: “Gotenberg API URL must not resolve to a private or reserved IP address”

Заголовок раздела «Сообщение: “Gotenberg API URL must not resolve to a private or reserved IP address”»
  • Триггер: хост является приватным или зарезервированным литералом Internet Protocol (IP) либо именем хоста, которое разрешается (по всем записям A/AAAA) в любой приватный или зарезервированный адрес. Это блокирует приватные диапазоны из Request for Comments (RFC) 1918, петлевые (loopback) и канально-локальные (link-local) адреса.
  • Исправление: направьте мост на маршрутизируемый публичный адрес или на правильно сегментированный адрес сервиса. Если ваш Gotenberg намеренно находится в приватной сети, защита моста от SSRF отклоняет его намеренно. Откройте к нему доступ через адрес, который защита принимает, затем защитите этот путь сетевыми средствами контроля и аутентификацией. См. /integrations/gotenberg/security-and-operations/.

Сообщение: “Gotenberg API URL DNS answer changed since validation — possible DNS rebinding attack”

Заголовок раздела «Сообщение: “Gotenberg API URL DNS answer changed since validation — possible DNS rebinding attack”»
  • Триггер: между первоначальной проверкой и запросом новое разрешение Domain Name System (DNS) вернуло адрес, которого не было в первоначально проверенном наборе.
  • Исправление: сработала защита time-of-check/time-of-use. Проверьте DNS для этого хоста. Допустимая причина — балансировщик нагрузки, который чередует адреса. Вредоносная причина — атака с подменой DNS (rebinding). Для конечной точки Gotenberg используйте стабильный адрес или имя со стабильным набором записей.

Политика безопасности порождает эти сбои до запроса. Каждый из них — обычное RuntimeException, если не указано иное.

  • Тип: GotenbergConvertException
  • Триггер: convertFile() не смог канонизировать путь, или разрешённый путь не указывает на обычный читаемый файл. Каталог тоже вызывает этот сбой.
  • Исправление: передайте путь к существующему читаемому файлу. Путь сначала канонизируется с помощью realpath(), что также блокирует обход каталогов.

Сообщение: “File size ( bytes) exceeds maximum allowed size ( bytes)”

Заголовок раздела «Сообщение: “File size ( bytes) exceeds maximum allowed size ( bytes)”»
  • Триггер: входные данные больше, чем maxFileSize (по умолчанию 52 428 800 байт = 50 MiB).
  • Исправление: увеличьте maxFileSize, если документу это действительно нужно, или отклоняйте загрузку выше по потоку. Держите предел настолько низким, насколько позволяют ваши реальные документы. Это единственное встроенное ограничение ресурсов моста.

Мост проверяет имя файла. При преобразовании файлов имя файла — это базовое имя разрешённого пути; для convertString() — имя, которое вы передаёте. Каждый такой сбой — RuntimeException:

Фрагмент сообщенияТриггер
must not be emptyпустое имя файла
path traversal sequences (..)имя содержит ..
forward slashesимя содержит /
backslashesимя содержит \
null bytesимя содержит байт NUL
control charactersимя содержит управляющий символ ASCII (0–31)
  • Исправление: передайте чистое базовое имя. Для convertString() укажите простое имя, например report.docx. Оно используется для определения формата и как имя файла при многокомпонентной (multipart) загрузке, а не как путь.
  • Тип: ValueError
  • Триггер: расширение файла не входит в число docx, xlsx, pptx, odt, ods, odp (без учёта регистра; ведущая точка допускается).
  • Исправление: преобразуйте только шесть распознаваемых форматов. Мост не распознаёт устаревшие двоичные форматы (.doc, .xls, .ppt), .rtf, .csv, обычный текст или изображения. Преобразуйте эти входные данные в распознаваемый формат перед вызовом моста или направьте их по другому пути.

Все они — GotenbergConvertException.

  • Триггер: клиент PHP Standard Recommendation 18 (PSR-18) (или транспорт с привязкой через cURL) выбросил исключение при отправке запроса. Причина — отказ в соединении, тайм-аут, сбой TLS-рукопожатия или несоответствие pin.
  • Код исключения: код базового исключения клиента.
  • Причина: исходное исключение клиента PSR-18 присоединено как предыдущее исключение.
  • Исправление: проверьте четыре вещи: доступность сервиса с помощью isAvailable(), сетевой путь, цепочку TLS и, если настроена привязка (pinning), совпадение текущего SubjectPublicKeyInfo (SPKI) сервера с настроенным pin. Несоответствие pin после смены сертификата — классическая причина. См. процедуру смены в /integrations/gotenberg/security-and-operations/.
  • Триггер: curl_exec транспорта с привязкой через cURL завершился с ненулевым номером ошибки cURL или вернул не строковое тело.
  • Исправление: номер ошибки cURL указывает причину (TLS, разрешение, тайм-аут, pin). Сбой привязки проявляется здесь, когда CURLOPT_PINNEDPUBLICKEY отклоняет сертификат. Убедитесь, что настроенные pin и разрешённый адрес актуальны.
  • Триггер: статус ответа не был 200. Тело включается в сообщение, но усекается до первых 500 символов; если оно длиннее, добавляется многоточие.
  • Исправление: прочитайте включённое тело. Сообщение об ошибке Gotenberg объясняет, почему преобразование было отклонено: неподдерживаемое содержимое документа, внутренний сбой LibreOffice или отказ в аутентификации с 401 или 403. 401/403 означает, что apiKey отсутствует или неверен. 5xx — сбой на стороне сервиса и кандидат на ограниченную повторную попытку.

Сообщение: “Unexpected Content-Type from Gotenberg: (expected application/pdf)”

Заголовок раздела «Сообщение: “Unexpected Content-Type from Gotenberg: (expected application/pdf)”»
  • Триггер: статус был 200, но Content-Type ответа не содержал application/pdf.
  • Исправление: обычно это означает, что прокси или шлюз вернул HTML-страницу с ошибкой или перенаправлением со статусом 200. Мост намеренно отключает следование за перенаправлениями на привязанном транспорте, поэтому ответ 3xx не может молча увести запрос к непроверенному хосту. Тело с неверным Content-Type указывает, что между вами и Gotenberg что-то вмешивается. Проверьте сетевой путь.

Сообщение: “Response body does not start with %PDF header — invalid PDF data”

Заголовок раздела «Сообщение: “Response body does not start with %PDF header — invalid PDF data”»
  • Триггер: статус 200, Content-Type приемлем, но тело не начинается с сигнатуры %PDF.
  • Исправление: вышестоящий сервис вернул данные, которые не являются файлом Portable Document Format (PDF), несмотря на заголовки. Считайте ответ недоверенным и проверьте сервис. Не записывайте тело на диск. Мост отказывается возвращать его как результат.
  • Тип: InvalidSpkiPinException
  • Триггер: настроенная строка pin не начинается с sha256/ или sha256//.
  • Исправление: оформляйте каждый pin как sha256/<base64-encoded-spki-hash>. Транспорт также принимает нативную для cURL форму sha256//<base64>. Сформируйте значение из SubjectPublicKeyInfo сертификата сервера, а не из всего сертификата.

isAvailable() возвращает false без какого-либо сетевого вызова, когда URL пуст, не использует HTTPS или разрешается в private/reserved адрес. Он также возвращает false при любой сетевой ошибке или когда /health возвращает 500 или выше; в этих случаях он перехватывает ошибку, а не выбрасывает её. Проверьте по порядку:

  1. Настроенный URL непуст и использует HTTPS.
  2. Хост не разрешается в private/reserved адрес (защита от SSRF отклоняет его даже для проверки).
  3. <apiUrl>/health достижим с хоста приложения и возвращает статус ниже 500.
  • /integrations/gotenberg/configuration/ — все параметры и правила выбора транспорта.
  • /integrations/gotenberg/production-usage/ — политика повторных попыток и контракт обработки сбоев.
  • /integrations/gotenberg/security-and-operations/ — модель SSRF и смена pin.
  • /integrations/gotenberg/quickstart/ — исчерпывающий порядок перехвата в контексте.