Convertire documenti Office in PDF con Gotenberg
In sintesi
Sezione intitolata “In sintesi”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/gotenbergsono 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,.odso.odp. Il bridge rifiuta qualsiasi altra estensione con unValueError.
Questa guida ha taglio pratico. Per un programma completo ed eseguibile, leggere la guida introduttiva di Gotenberg.
Installazione
Sezione intitolata “Installazione”Installare il bridge, un client PSR-18 e le factory PSR-17.
composer require nextpdf/gotenberg guzzlehttp/guzzleEseguire 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.
Panoramica concettuale
Sezione intitolata “Panoramica concettuale”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, unContent-Typesenzaapplication/pdfo un corpo che non inizia con%PDFsollevano ciascunoGotenbergConvertException. 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
GotenbergConvertExceptioncon l’eccezione originale come causa. - Gli errori di validazione (URL non HTTPS, indirizzo privato o riservato, input sovradimensionato, nome di file non sicuro) sollevano
RuntimeExceptionprima di qualsiasi traffico di rete. - Un’estensione di file non riconosciuta solleva
ValueErrorprima di qualsiasi traffico di rete.
Superficie dell’API
Sezione intitolata “Superficie dell’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): GotenbergConvertResultL’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.
Esempio di codice — Avvio rapido
Sezione intitolata “Esempio di codice — Avvio rapido”Definire il servizio, collegare il bridge, sondarlo e convertire un file.
<?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à.
Esempio di codice — Produzione
Sezione intitolata “Esempio di codice — Produzione”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.
<?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.
Casi limite e insidie
Sezione intitolata “Casi limite e insidie”- Il rilevamento del formato avviene tramite l’estensione. Un file
.docxrinominato in.txtviene rifiutato conValueError; un file.txtrinominato in.docxviene 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.0a meno che la propria integrazione non lo misuri e lo imposti. Misurare la chiamata autonomamente se è necessario disporre del valore.
Prestazioni
Sezione intitolata “Prestazioni”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.
Note sulla sicurezza
Sezione intitolata “Note sulla sicurezza”- 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.
apiKeydispone 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
catchvuoto. 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.
Conformità
Sezione intitolata “Conformità”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.
Vedere anche
Sezione intitolata “Vedere anche”- Restituire un PDF generato da un controller — restituire un PDF convertito come risposta HTTP.
- Guida introduttiva di Gotenberg — il programma di conversione completo ed eseguibile.
- Configurazione di Gotenberg — ogni campo, la mappa di
fromArray()e la selezione del trasporto. - Utilizzo di Gotenberg in produzione — segreti, timeout, retry, concorrenza e il confine del post-processing.