Salta ai contenuti

Convertire documenti Office in PDF con Gotenberg

Il bridge Gotenberg converte un documento Office in PDF: invia il documento a un microservizio Gotenberg tramite HTTPS e restituisce i byte del PDF. Per usarlo, descrivere il servizio con un oggetto immutabile GotenbergConfig, collegare a GotenbergBridge un client PSR-18 e factory PSR-17, verificare il servizio con un controllo di integrità e quindi convertire un file su disco o byte già in memoria. Questa guida copre il rilevamento del formato dall’estensione del file, il probe di integrità, il contratto di errore tipizzato e il passaggio al post-processing di NextPDF.

Prerequisiti da predisporre:

  • Il core di NextPDF e nextpdf/gotenberg sono installati.
  • Un servizio Gotenberg è raggiungibile tramite HTTPS. Il bridge rifiuta un URL http:// non cifrato prima che qualsiasi richiesta lasci il processo.
  • Sono installati un client PSR-18 e le factory PSR-17 per le richieste e i flussi. Per il pinning DNS e TLS, fornire anche una factory di risposta PSR-17.
  • L’input è uno dei sei formati Office riconosciuti: .docx, .xlsx, .pptx, .odt, .ods o .odp. Il bridge rifiuta qualsiasi altra estensione con un ValueError.

Questa guida ha taglio pratico. Per un programma completo ed eseguibile, leggere la guida introduttiva di Gotenberg.

Installare il bridge, un client PSR-18 e le factory PSR-17.

Terminal window
composer require nextpdf/gotenberg guzzlehttp/guzzle

Eseguire un servizio Gotenberg raggiungibile tramite HTTPS e recuperare l’eventuale token bearer da un gestore di segreti o da un valore di ambiente iniettato. Il bridge non legge mai variabili di ambiente e non costruisce mai un client HTTP; entrambi devono essere forniti dal codice chiamante.

GotenbergBridge::convertFile() accetta un percorso su disco. Canonicalizza il percorso per bloccare il path traversal, mappa l’estensione del file su un formato supportato, controlla dimensione e nome del file, quindi invia una richiesta multipart a <apiUrl>/forms/libreoffice/convert. convertString() fa lo stesso per i byte già disponibili; usa il nome di file originale per consentire il rilevamento dell’estensione.

Il rilevamento del formato avviene tramite l’estensione. Il bridge mappa .docx, .xlsx, .pptx, .odt, .ods e .odp ai rispettivi formati e rifiuta qualsiasi altro con un ValueError prima di qualsiasi traffico di rete. L’oggetto risultato espone il formato di origine rilevato come valore enum.

Il bridge consiste in un singolo round-trip HTTP sincrono racchiuso dalla validazione. Non esegue retry, accodamento, cache o rate limiting; queste funzioni spettano all’applicazione attorno al bridge. Trattare ogni conversione come una chiamata remota a un servizio gestito ma non controllato all’interno del processo e progettare tenendo conto della relativa latenza e dei possibili errori.

Il bridge espone gli errori come eccezioni tipizzate e non restituisce mai un risultato parziale o non validato:

  • Uno stato diverso da 200, un Content-Type senza application/pdf o un corpo che non inizia con %PDF sollevano ciascuno GotenbergConvertException. Il bridge restituisce un risultato solo se tutti e tre i controlli vanno a buon fine.
  • Un errore del client PSR-18, incluso un errore di rete o un timeout, viene incapsulato in GotenbergConvertException con l’eccezione originale come causa.
  • Gli errori di validazione (URL non HTTPS, indirizzo privato o riservato, input sovradimensionato, nome di file non sicuro) sollevano RuntimeException prima di qualsiasi traffico di rete.
  • Un’estensione di file non riconosciuta solleva ValueError prima di qualsiasi traffico di rete.
// 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): self
GotenbergConfig::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(): bool
GotenbergBridge::convertFile(string $path): GotenbergConvertResult
GotenbergBridge::convertString(string $bytes, string $originalFilename): GotenbergConvertResult

L’oggetto risultato espone pdfData, l’enum sourceFormat, isValid() (true quando il corpo non è vuoto e inizia con %PDF) e size(). Per il riferimento completo dei campi, la mappa delle chiavi di fromArray() e le regole di selezione del trasporto, consultare la pagina di configurazione di Gotenberg indicata in Vedere anche.

Definire il servizio, collegare il bridge, sondarlo e convertire un file.

convert-quickstart.php
<?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 classe è NextPDF\Gotenberg\GotenbergConfig (l’esempio precedente usa il namespace esatto che il codice deve importare). isAvailable() restituisce false e non solleva mai eccezioni per un URL vuoto, non HTTPS o con indirizzo privato, né per qualsiasi errore di rete; uno stato inferiore a 500 da /health indica disponibilità.

In produzione, una conversione intercetta separatamente ogni tipo di errore, esegue retry solo nelle condizioni corrette e limita la concorrenza dal lato chiamante. L’ordine dei catch riportato di seguito è esaustivo.

OfficeConverter.php
<?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;
}
}

Eseguire retry solo su un GotenbergConvertException a livello di trasporto (un’eccezione del client PSR-18 incapsulata) e su errori del server idempotenti (502, 503, 504). Una risposta di classe 400 di solito significa che l’input è errato, quindi un retry fallirà nello stesso modo. Limitare il numero totale di tentativi e il tempo totale trascorso. Limitare il numero di conversioni in corso alla capacità sostenibile dal deployment di Gotenberg. Il bridge stesso è stateless e può essere usato in sicurezza da molti worker, ma il servizio ha una capacità di conversione finita.

  • Il rilevamento del formato avviene tramite l’estensione. Un file .docx rinominato in .txt viene rifiutato con ValueError; un file .txt rinominato in .docx viene inviato a Gotenberg e lì fallisce. Quando si accettano caricamenti, affidarsi al formato reale, non al nome.
  • fromArray() è tollerante per scelta progettuale. In caso di input malformato, ripiega silenziosamente sui valori predefiniti. Validare l’array di origine nel percorso di avvio in modo che un URL mancante emerga in anticipo come errore di configurazione e non come eccezione per ogni conversione.
  • Il limite di dimensione è applicato all’interno del processo. maxFileSize (predefinito 50 MiB) viene controllato prima dell’invio della richiesta, perciò un file sovradimensionato non consuma mai capacità del servizio. Abbassare il limite per adattarlo alle esigenze dei propri documenti; un limite inferiore è un controllo anti denial-of-service più economico.
  • Il probe non è gratuito. Chiamare isAvailable() da un endpoint di readiness o di integrità, non prima di ogni conversione. Eseguirlo a ogni conversione raddoppia la frequenza delle richieste verso il servizio senza alcun vantaggio.
  • Nessuna cache all’interno del processo. Se lo stesso documento viene convertito ripetutamente, memorizzare nella cache dell’applicazione il PDF risultante, indicizzato in base a un hash del contenuto dell’input.
  • renderTimeMs è a carico del codice chiamante. Il campo di temporizzazione del risultato è 0.0 a meno che la propria integrazione non lo misuri e lo imposti. Misurare la chiamata autonomamente se è necessario disporre del valore.

Per tutta la durata della richiesta, una conversione occupa una connessione e un worker LibreOffice sul lato Gotenberg, e la conversione Office non è istantanea. Impostare timeout in base alla latenza di conversione misurata per i propri documenti reali, con un margine. Mantenerlo al di sotto di qualsiasi gateway a monte o del max_execution_time di PHP, in modo che il bridge vada in timeout per primo e restituisca un’eccezione tipizzata invece di un processo interrotto. Limitare la concorrenza con una coda, un semaforo o un pool di worker dimensionato in base alla capacità del servizio. Non esiste una cache all’interno del processo; aggiungerne una nella propria applicazione se si converte ripetutamente lo stesso input.

  • HTTPS e controllo dell’indirizzo prima dell’invio. Il bridge rifiuta un URL non HTTPS e una destinazione che si risolve in uno spazio di indirizzi privato o riservato prima che qualsiasi richiesta lasci il processo. Ogni chiamata ritentata riesegue tale validazione, quindi un retry non può aggirare la protezione SSRF.
  • Trasporto con pinning su richiesta. Quando si fornisce una factory di risposta e dei pin (o è presente un set di IP risolti), il bridge vincola la connessione agli indirizzi risolti, applica il pinning SPKI, verifica peer e host, applica il timeout e non segue i redirect. Configurare un pin di backup prima di una rotazione del certificato.
  • Non fidarsi del tipo di contenuto dichiarato di un caricamento. Quando si accettano caricamenti utente, validare autonomamente il tipo di file reale; la mappatura da estensione a formato è una decisione di instradamento, non un controllo di autenticità.
  • I segreti sono oscurati e immutabili. apiKey dispone di #[SensitiveParameter] e la configurazione è final readonly. Recuperare il token da un gestore di segreti; non eseguirne mai il commit. La voce di conversione registrata nel log riporta l’URL, il nome di file, il formato e la lunghezza del contenuto, mai il contenuto del file né il token.
  • Non scrivere mai un blocco catch vuoto. Ogni esempio intercetta il tipo specifico e registra nel log il contesto.

Per il modello completo di sicurezza e deployment, consultare la pagina di sicurezza e operazioni di Gotenberg. Il contratto di trasporto PSR-18 e le indicazioni a non fidarsi del content type sono ancorati alle rispettive clausole nella pagina a monte sull’utilizzo in produzione.

Questa guida non avanza alcuna affermazione normativa di conformità agli standard. Il comportamento di trasporto PSR-18 del bridge (un client solleva un’eccezione solo quando non riesce a inviare o ad analizzare una risposta; un 4xx/5xx è un valore di ritorno normale), le indicazioni di validazione dei caricamenti di file e il modello di pinning TLS sono ancorati a PSR-18, OWASP e RFC 7469 nelle pagine di utilizzo in produzione e di configurazione di Gotenberg a monte. Questa pagina del cookbook ribadisce l’utilizzo e rimanda tali citazioni a quelle pagine. Il bridge produce i byte del PDF e si ferma. La firma, i profili PDF/A e l’applicazione di filigrane sono aspetti di post-processing di NextPDF e una funzionalità dell’edizione commerciale, non parte di questo bridge.