Gotenberg로 Office 문서를 PDF로 변환하기
한눈에 보기
섹션 제목: “한눈에 보기”Gotenberg 브리지는 Office 문서를 PDF로 변환합니다. HTTPS를 통해 문서를 Gotenberg 마이크로서비스로 보내고 PDF 바이트를 반환합니다. 불변 GotenbergConfig로 서비스를 정의하고, PSR-18 클라이언트와 PSR-17 팩토리를 GotenbergBridge에 연결한 뒤, 상태 점검으로 서비스를 프로브하고, 디스크 파일이나 메모리의 바이트를 변환합니다. 이 가이드는 파일 확장자 기반 형식 감지, 상태 점검 프로브, 형식화된 실패 계약, NextPDF 후처리로의 인계를 다룹니다.
먼저 확인할 사전 요구 사항:
- NextPDF 코어와
nextpdf/gotenberg가 설치되어 있습니다. - Gotenberg 서비스가 HTTPS로 접근 가능해야 합니다. 브리지는 어떤 요청도 프로세스를 떠나기 전에 평문
http://URL을 거부합니다. - 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/guzzleHTTPS로 접근 가능한 Gotenberg 서비스를 실행하고, 베어러 토큰은 시크릿 관리자나 주입된 환경 값에서 가져옵니다. 브리지는 환경 변수를 절대 읽지 않으며 HTTP 클라이언트도 절대 생성하지 않습니다. 두 가지 모두 사용자가 제공합니다.
개념 개요
섹션 제목: “개념 개요”GotenbergBridge::convertFile()는 디스크의 경로를 받습니다. 경로를 정규화해 경로 순회를 차단하고, 파일 확장자를 지원되는 형식으로 매핑하며, 크기와 파일 이름을 검사한 뒤, <apiUrl>/forms/libreoffice/convert로 멀티파트 요청을 보냅니다. convertString()는 이미 보유한 바이트에 대해 같은 작업을 수행합니다. 확장자를 감지할 수 있도록 원본 파일 이름을 사용합니다.
형식 감지는 확장자 기준입니다. 브리지는 .docx, .xlsx, .pptx, .odt, .ods, .odp를 해당 형식으로 매핑하고, 그 밖의 모든 입력은 네트워크 트래픽이 발생하기 전에 ValueError로 거부합니다. 결과 객체는 감지된 소스 형식을 enum 값으로 노출합니다.
브리지는 검증을 거치는 단일 동기 HTTP 왕복으로 동작합니다. 재시도, 큐잉, 캐싱, 속도 제한은 수행하지 않습니다. 이러한 책임은 브리지를 둘러싼 애플리케이션에 있습니다. 각 변환은 사용자가 운영하더라도 프로세스 내에서 제어하지는 않는 서비스에 대한 원격 호출로 취급하고, 그 지연 시간과 실패에 대비해 설계하십시오.
브리지는 실패를 형식화된 예외로 드러내며, 부분적이거나 검증되지 않은 결과는 절대 반환하지 않습니다:
- 응답 상태가
200이 아니거나,Content-Type에application/pdf가 없거나, 본문이%PDF로 시작하지 않으면 각각GotenbergConvertException을 발생시킵니다. 브리지는 세 가지 검사를 모두 통과한 경우에만 결과를 반환합니다. - 네트워크 실패나 시간 초과를 포함한 PSR-18 클라이언트 실패는 원래 예외를 원인으로 유지한 채
GotenbergConvertException으로 감쌉니다. - 검증 실패(비 HTTPS URL, 사설 또는 예약된 주소, 과대 입력, 안전하지 않은 파일 이름)는 네트워크 트래픽이 발생하기 전에
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 enum, isValid()(본문이 비어 있지 않고 %PDF로 시작할 때 true), 그리고 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()는 비어 있거나, 비 HTTPS이거나, 사설 주소인 URL에 대해, 그리고 모든 네트워크 오류에 대해 false를 반환하며 절대 예외를 던지지 않습니다. /health에서 500 미만의 상태가 반환되면 사용 가능하다는 뜻입니다.
코드 샘플 — 프로덕션
섹션 제목: “코드 샘플 — 프로덕션”프로덕션 변환에서는 각 실패 유형을 개별적으로 잡고, 올바른 조건에서만 재시도하며, 호출자 측에서 동시성을 제한합니다. 아래의 catch 순서는 완전합니다.
<?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입니다. 숫자가 필요하면 호출 시간을 직접 측정하십시오.
요청이 지속되는 동안 변환은 Gotenberg 측에서 연결 하나와 LibreOffice 워커 하나를 점유하며, Office 변환은 즉각적이지 않습니다. 실제 문서에서 측정한 변환 지연 시간을 기준으로 여유를 두고 timeout을 설정하십시오. 업스트림 게이트웨이나 PHP max_execution_time이 먼저 프로세스를 종료하기보다 브리지가 먼저 시간 초과되어 형식화된 예외를 받도록, 그 값을 이들보다 낮게 유지하십시오. 서비스 용량에 맞춰 크기를 정한 큐, 세마포어 또는 워커 풀로 동시성을 제한하십시오. 프로세스 내 캐시는 없습니다. 동일한 입력을 반복적으로 변환한다면 애플리케이션에 캐시를 추가하십시오.
보안 참고 사항
섹션 제목: “보안 참고 사항”- 전송 전 HTTPS 및 주소 검사. 브리지는 어떤 요청도 프로세스를 떠나기 전에 비 HTTPS URL과 사설 또는 예약된 주소 공간으로 해석되는 대상을 거부합니다. 재시도되는 각 호출은 그 검증을 다시 실행하므로, 재시도는 SSRF 가드를 우회할 수 없습니다.
- 요청 시 고정 전송. 응답 팩토리와 핀을 제공하면(또는 해석된 IP 집합이 있으면), 브리지는 연결을 해석된 주소에 바인딩하고, SPKI 고정을 적용하며, 피어와 호스트를 검증하고, 시간 초과를 적용하며, 리디렉션 추적을 비활성화합니다. 인증서 교체 전에 백업 핀을 구성하십시오.
- 업로드의 선언된 콘텐츠 유형을 신뢰하지 마십시오. 사용자 업로드를 수락할 때는 실제 파일 유형을 직접 검증하십시오. 확장자-형식 매핑은 진위성 검사가 아니라 라우팅 결정입니다.
- 시크릿은 가려지고 불변입니다.
apiKey에는#[SensitiveParameter]가 적용되어 있으며, 구성은final readonly입니다. 토큰은 시크릿 관리자에서 가져오십시오. 절대 커밋하지 마십시오. 기록되는 변환 항목에는 URL, 파일 이름, 형식, 콘텐츠 길이가 포함되며, 파일 내용이나 토큰은 절대 포함되지 않습니다. - 절대 빈
catch블록을 작성하지 마십시오. 각 예제는 특정 유형을 잡고 컨텍스트와 함께 기록합니다.
전체 보안 및 배포 모델은 Gotenberg 보안 및 운영 페이지를 참조하십시오. PSR-18 전송 계약과 콘텐츠 유형을 신뢰하지 말라는 지침은 업스트림 프로덕션 사용 페이지의 해당 절에 근거가 제시되어 있습니다.
적합성
섹션 제목: “적합성”이 가이드는 그 자체로 어떤 규범적 표준 주장도 하지 않습니다. 브리지의 PSR-18 전송 동작(클라이언트는 응답을 보내거나 구문 분석할 수 없을 때만 예외를 발생시키며, 4xx/5xx는 정상적인 반환 값입니다), 파일 업로드 검증 지침, TLS 고정 모델은 업스트림 Gotenberg 프로덕션 사용 및 구성 페이지에서 PSR-18, OWASP, RFC 7469에 근거를 두고 있습니다. 이 쿡북 페이지는 그 사용법을 다시 설명하며, 관련 인용은 해당 페이지들에 둡니다. 브리지는 PDF 바이트를 생성한 뒤 멈춥니다. 서명, PDF/A 프로파일, 워터마킹은 NextPDF 후처리 관심사이자 상용 에디션 기능이며, 이 브리지의 일부가 아닙니다.
함께 보기
섹션 제목: “함께 보기”- 컨트롤러에서 생성된 PDF 반환하기 — 변환된 PDF를 HTTP 응답으로 반환합니다.
- Gotenberg 빠른 시작 — 완전히 실행 가능한 변환 프로그램.
- Gotenberg 구성 — 모든 필드,
fromArray()맵, 전송 선택. - Gotenberg 프로덕션 사용 — 시크릿, 시간 초과, 재시도, 동시성, 후처리 경계.