Convertir documentos de Office a PDF con Gotenberg
De un vistazo
Sección titulada «De un vistazo»El puente de Gotenberg convierte documentos de Office a PDF. Envía cada documento a un microservicio de Gotenberg por HTTPS y devuelve los bytes del PDF. Define el servicio con un GotenbergConfig inmutable, conecta un cliente PSR-18 y factorías PSR-17 a GotenbergBridge, comprueba la salud del servicio y, después, convierte un archivo desde disco o desde bytes en memoria. Esta guía cubre la detección del formato a partir de la extensión del archivo, la comprobación de salud, el contrato de fallos tipados y el traspaso al posprocesamiento de NextPDF.
Requisitos previos, indicados desde el inicio:
- El núcleo de NextPDF y
nextpdf/gotenbergestán instalados. - Hay un servicio de Gotenberg accesible por HTTPS. El puente rechaza una URL
http://en texto plano antes de que salga cualquier solicitud del proceso. - Un cliente PSR-18 y factorías PSR-17 de solicitud y de stream están instalados. Para el fijado de DNS y TLS, también se proporciona una factoría PSR-17 de respuesta.
- La entrada es uno de los seis formatos de Office reconocidos:
.docx,.xlsx,.pptx,.odt,.odso.odp. El puente rechaza cualquier otra extensión con unValueError.
Esta es una guía práctica. Para un programa completo y ejecutable, lee la guía de inicio rápido de Gotenberg.
Instalación
Sección titulada «Instalación»Instalar el puente, un cliente PSR-18 y factorías PSR-17.
composer require nextpdf/gotenberg guzzlehttp/guzzleEl servicio de Gotenberg debe estar accesible por HTTPS, y cualquier token de portador debe obtenerse desde un gestor de secretos o desde un valor de entorno inyectado. El puente nunca lee variables de entorno y nunca construye un cliente HTTP; ambos se proporcionan desde la aplicación.
Panorama conceptual
Sección titulada «Panorama conceptual»GotenbergBridge::convertFile() recibe una ruta en disco. Resuelve la ruta de forma canónica para bloquear el salto de directorios, asigna la extensión del archivo a un formato admitido, examina el tamaño y el nombre del archivo, y luego envía una solicitud multipart a <apiUrl>/forms/libreoffice/convert. convertString() hace lo mismo con bytes ya disponibles; usa el nombre de archivo original para que se pueda detectar la extensión.
La detección del formato se basa en la extensión. El puente asigna .docx, .xlsx, .pptx, .odt, .ods y .odp a sus formatos y rechaza cualquier otra cosa con un ValueError antes de cualquier tráfico de red. El objeto de resultado expone el formato de origen detectado como un valor de enum.
El puente realiza una única ida y vuelta HTTP síncrona, envuelta en validación. No reintenta, encola, cachea ni limita la tasa; eso corresponde a la aplicación que integra el puente. Cada conversión debe tratarse como una llamada remota a un servicio operado fuera del proceso, con latencia y fallos propios.
El puente expone los fallos como excepciones tipadas y nunca devuelve un resultado parcial o sin validar:
- Un estado distinto de
200, unContent-Typesinapplication/pdfo un cuerpo que no empieza por%PDFprovocan, cada uno, unGotenbergConvertException. El puente devuelve un resultado solo cuando pasan las tres comprobaciones. - Un fallo del cliente PSR-18, incluido un fallo de red o un tiempo de espera agotado, se envuelve en
GotenbergConvertExceptioncon la excepción original como causa. - Los fallos de validación (URL que no es HTTPS, dirección privada o reservada, entrada de tamaño excesivo, nombre de archivo inseguro) lanzan
RuntimeExceptionantes de cualquier tráfico de red. - Una extensión de archivo no reconocida lanza
ValueErrorantes de cualquier tráfico de red.
Superficie de la API
Sección titulada «Superficie de la 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): GotenbergConvertResultEl objeto de resultado expone pdfData, el enum sourceFormat, isValid() (verdadero cuando el cuerpo no está vacío y empieza por %PDF) y size(). Para la referencia completa de campos, el mapa de claves de fromArray() y las reglas de selección de transporte, consultar la página de configuración de Gotenberg enlazada en Véase también.
Ejemplo de código — Inicio rápido
Sección titulada «Ejemplo de código — Inicio rápido»El flujo consiste en describir el servicio, conectar el puente, sondearlo y convertir un archivo.
<?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);La clase es NextPDF\Gotenberg\GotenbergConfig (la línea anterior usa el namespace exacto que debe importar el código). isAvailable() devuelve false y nunca lanza una excepción ante una URL vacía, que no es HTTPS o de dirección privada, ni ante cualquier error de red; un estado por debajo de 500 de /health significa disponible.
Ejemplo de código — Producción
Sección titulada «Ejemplo de código — Producción»Una conversión de producción captura cada tipo de fallo por separado, reintenta solo bajo las condiciones adecuadas y acota la concurrencia desde el lado de la aplicación llamadora. El orden de captura siguiente es exhaustivo.
<?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; }}Reintentar solo ante un GotenbergConvertException de nivel de transporte (una excepción del cliente PSR-18 envuelta) y ante errores idempotentes del servidor (502, 503, 504). Una respuesta de clase 400 suele significar que la entrada es incorrecta, así que un reintento falla de forma idéntica. Limitar el total de intentos y el tiempo total transcurrido. Acotar el número de conversiones en curso a la capacidad que el despliegue de Gotenberg pueda sostener. El puente en sí mismo no tiene estado y es seguro usarlo desde varios workers, pero el servicio tiene una capacidad de conversión finita.
Casos límite y trampas
Sección titulada «Casos límite y trampas»- La detección del formato se basa en la extensión. Un
.docxrenombrado a.txtse rechaza conValueError; un.txtrenombrado a.docxse envía a Gotenberg y falla allí. Al aceptar cargas, hay que basarse en el formato real, no en el nombre. fromArray()es tolerante por diseño. Ante una entrada malformada, aplica en silencio los valores predeterminados. Validar el array de origen en la ruta de arranque permite que una URL faltante aparezca pronto como un error de configuración, no como una excepción por cada conversión.- El límite de tamaño se aplica dentro del proceso.
maxFileSize(50 MiB de forma predeterminada) se comprueba antes de enviar la solicitud, de modo que un archivo de tamaño excesivo nunca consume capacidad del servicio. Reducir el límite para que coincida con lo que requieren los documentos; un límite más pequeño es un control de denegación de servicio más barato. - La sonda no es gratis. Llamar a
isAvailable()desde un endpoint de disponibilidad o de salud, no antes de cada conversión. Ejecutarla por cada conversión duplica la tasa de solicitudes contra el servicio sin aportar ningún beneficio. - Sin caché dentro del proceso. Si el mismo documento se convierte repetidamente, guardar en caché el PDF resultante en la aplicación, indexado por un hash de contenido de la entrada.
renderTimeMsse define en la integración. El campo de tiempo del resultado es0.0a menos que la integración lo mida y lo defina. Medir la llamada directamente si se necesita ese número.
Rendimiento
Sección titulada «Rendimiento»Mientras dura la solicitud, una conversión retiene una conexión y un worker de LibreOffice en el lado de Gotenberg, y la conversión de Office no es instantánea. Configurar timeout a partir de la latencia de conversión medida para los documentos reales, con margen. Mantenerlo por debajo de cualquier gateway aguas arriba o del max_execution_time de PHP, para que el puente agote el tiempo de espera primero y se obtenga una excepción tipada en lugar de un proceso terminado abruptamente. Acotar la concurrencia con una cola, un semáforo o un pool de workers dimensionado a la capacidad del servicio. No hay caché dentro del proceso; añadir una en la aplicación si se convierte la misma entrada repetidamente.
Notas de seguridad
Sección titulada «Notas de seguridad»- HTTPS y examen de direcciones antes de enviar. El puente rechaza una URL que no es HTTPS y un destino que se resuelve en espacio de direcciones privado o reservado antes de que salga cualquier solicitud del proceso. Cada llamada reintentada vuelve a ejecutar esa validación, así que un reintento no puede eludir la protección contra SSRF.
- Transporte fijado bajo demanda. Cuando se proporciona una factoría de respuesta y fijaciones (o hay un conjunto de IP resueltas), el puente vincula la conexión a las direcciones resueltas, aplica el fijado SPKI, verifica el par y el host, aplica el tiempo de espera y desactiva el seguimiento de redirecciones. Configurar una fijación de respaldo antes de una rotación de certificado.
- No confiar en el tipo de contenido declarado de una carga. Al aceptar cargas de usuarios, validar directamente el tipo de archivo real; el mapa de extensión a formato es una decisión de enrutamiento, no una comprobación de autenticidad.
- Los secretos se ocultan y son inmutables.
apiKeylleva#[SensitiveParameter], y la configuración esfinal readonly. Obtener el token desde un gestor de secretos; no subirlo nunca al repositorio. La entrada de conversión registrada incluye la URL, el nombre de archivo, el formato y la longitud del contenido, nunca el contenido del archivo ni el token. - Nunca escribir un bloque
catchvacío. Cada ejemplo captura el tipo específico y registra la información con contexto.
Para el modelo completo de seguridad y despliegue, consultar la página de seguridad y operaciones de Gotenberg. El contrato de transporte PSR-18 y la guía sobre no confiar en el tipo de contenido están vinculados a sus cláusulas en la página correspondiente de uso en producción.
Conformidad
Sección titulada «Conformidad»Esta guía no hace ninguna afirmación normativa de estándares por sí misma. El comportamiento de transporte PSR-18 del puente (un cliente lanza una excepción solo cuando no puede enviar o analizar una respuesta; un 4xx/5xx es un valor de retorno normal), la guía de validación de carga de archivos y el modelo de fijado de TLS están vinculados a PSR-18, OWASP y RFC 7469 en las páginas correspondientes de uso en producción y de configuración de Gotenberg. Esta página del cookbook reformula el uso y delega esas citas a esas páginas. El puente produce bytes de PDF y se detiene. La firma, los perfiles PDF/A y las marcas de agua son cuestiones de posprocesamiento de NextPDF y una capacidad de la edición comercial, no parte de este puente.
Véase también
Sección titulada «Véase también»- Devolver un PDF generado desde un controlador — devuelve un PDF convertido como una respuesta HTTP.
- Inicio rápido de Gotenberg — el programa de conversión completo y ejecutable.
- Configuración de Gotenberg — cada campo, el mapa de
fromArray()y la selección de transporte. - Uso en producción de Gotenberg — secretos, tiempos de espera, reintentos, concurrencia y el límite del posprocesamiento.