Skip to content

Export API 사용 가이드

edward edited this page Apr 27, 2026 · 1 revision

Export API 사용 가이드 — HWP/HWPX 내보내기 + 서버 업로드

핵심 요약

rhwp 의 export API 는 bytes 를 반환 합니다. 로컬 저장 / 서버 업로드 / 다른 처리는 사용자 측 코드로 결정합니다.

const bytes = editor.exportHwp();    // Uint8Array

// 1) 로컬 저장
const blob = new Blob([bytes], { type: 'application/x-hwp' });
// File System Access API 또는 다운로드 링크

// 2) 서버 업로드
await fetch('/api/upload', {
    method: 'POST',
    body: bytes,
    headers: { 'Content-Type': 'application/x-hwp' },
});

// 3) 두 가지 동시 (로컬 + 서버)
await Promise.all([
    saveLocal(blob),
    fetch('/api/upload', { method: 'POST', body: bytes }),
]);

서버 업로드는 라이브러리 책임 영역이 아닙니다 — CORS 정책, 인증 (JWT / OAuth / 쿠키), 에러 처리, 진행률 등은 사용자 환경별로 다르므로 사용자가 직접 `fetch()` 등으로 처리합니다.


사용 가능한 export API

WASM API (브라우저 / Node.js)

API 반환 용도
exportHwp() Uint8Array HWP 바이너리
exportHwpx() Uint8Array HWPX (HWP 의 XML 형식)
getSourceFormat() 'hwp' | 'hwpx' 원본 파일 형식 확인

Rust native API (DocumentCore)

API 반환 용도
export_hwp_native() Result<Vec<u8>, HwpError> HWP bytes
export_hwpx_native() Result<Vec<u8>, HwpError> HWPX bytes
export_hwp_with_adapter() Result<Vec<u8>, HwpError> HWPX → HWP 어댑터 변환
export_selection_html_native() Result<String, HwpError> 선택 영역 HTML
export_selection_in_cell_html_native() Result<String, HwpError> 셀 내 선택 영역 HTML
render_page_svg_native(page_num) Result<String, HwpError> 페이지 SVG
build_page_render_tree(page_num) Result<PageRenderTree, HwpError> 페이지 render tree (native bridge)
get_bin_data(index) Option<&[u8]> 이미지 등 바이너리 데이터

CLI export (개발 / 검증)

rhwp export-svg sample.hwp -o output/svg/      # SVG
rhwp export-svg sample.hwp --embed-fonts       # 폰트 임베딩 SVG
rhwp dump-pages sample.hwp -p 0                # 페이지 덤프 (디버그)

사용 예시 — 시나리오별

1. 게시판 — 저장 시 서버 업로드

async function onSave() {
    const bytes = editor.exportHwp();

    const formData = new FormData();
    formData.append('file', new Blob([bytes], { type: 'application/x-hwp' }), 'document.hwp');
    formData.append('postId', currentPostId);

    const response = await fetch('/api/posts/attachment', {
        method: 'POST',
        body: formData,
        headers: { 'Authorization': `Bearer ${token}` },
    });

    if (!response.ok) {
        throw new Error(`업로드 실패: ${response.status}`);
    }
}

2. 로컬 + 서버 동시 저장

async function onSaveBoth() {
    const bytes = editor.exportHwp();
    const blob = new Blob([bytes], { type: 'application/x-hwp' });

    // 로컬 + 서버 동시
    const [_, uploadResult] = await Promise.all([
        saveToFileSystem(blob, 'document.hwp'),  // File System Access API
        fetch('/api/upload', { method: 'POST', body: bytes }),
    ]);

    if (!uploadResult.ok) {
        // 서버 실패 시 로컬은 이미 저장됨 → 사용자에게 알림
        console.warn('서버 업로드 실패, 로컬 저장은 완료');
    }
}

3. 진행률 표시 (큰 파일)

async function onSaveWithProgress() {
    const bytes = editor.exportHwp();
    const blob = new Blob([bytes]);

    // XMLHttpRequest 로 진행률 추적
    const xhr = new XMLHttpRequest();
    xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
            const percent = (e.loaded / e.total) * 100;
            updateProgressBar(percent);
        }
    });
    xhr.open('POST', '/api/upload');
    xhr.setRequestHeader('Content-Type', 'application/x-hwp');
    xhr.send(blob);
}

4. 인증 처리

async function uploadWithAuth() {
    const bytes = editor.exportHwp();

    const response = await fetch('/api/upload', {
        method: 'POST',
        body: bytes,
        credentials: 'include',  // 쿠키 인증
        headers: {
            'Content-Type': 'application/x-hwp',
            'X-CSRF-Token': csrfToken,
            // 또는 'Authorization': `Bearer ${jwtToken}`
        },
    });

    return response.json();
}

5. 다른 형식 (HWPX)

const sourceFormat = editor.getSourceFormat();
const bytes = sourceFormat === 'hwpx' ? editor.exportHwpx() : editor.exportHwp();
const mimeType = sourceFormat === 'hwpx' ? 'application/hwp+zip' : 'application/x-hwp';

await fetch('/api/upload', {
    method: 'POST',
    body: bytes,
    headers: { 'Content-Type': mimeType },
});

6. Rust native (서버 사이드)

use rhwp::wasm_api::HwpDocument;

let bytes = std::fs::read("input.hwp")?;
let mut doc = HwpDocument::from_bytes(&bytes)?;
// ... 편집 ...
let exported: Vec<u8> = doc.export_hwp_native()?;

// HTTP client (예: reqwest) 로 업로드
let client = reqwest::Client::new();
client.post("https://api.example.com/upload")
    .body(exported)
    .header("Content-Type", "application/x-hwp")
    .send()
    .await?;

라이브러리가 직접 업로드 API 를 제공하지 않는 이유

다음 항목은 사용자 환경별 정책이 다르므로 라이브러리에 포함하면 오히려 유연성을 잃습니다:

항목 사용자 환경별 차이
서버 URL 게시판 / SaaS / 자체 서버 등
인증 JWT / OAuth / 쿠키 / API Key 등
CORS 정책 서버 측 설정 의존
에러 처리 UI 알림 / 로그 / 재시도 정책 등
진행률 표시 사용자 UX 결정
응답 처리 JSON / 리다이렉트 / 메타데이터 등
HTTP client fetch / axios / ky / Rust reqwest 등

→ rhwp 는 bytes 반환 까지만 책임. 그 후 처리는 사용자가 자기 환경에 맞게 자유롭게 작성.


자주 묻는 질문

Q. "업로드 API" 가 없다고 하셨는데 어떻게 서버에 보내나요?

A. 위 예시처럼 `exportHwp()` 로 bytes 를 받은 후 `fetch()` 등 표준 웹 API 로 업로드합니다. 라이브러리 자체 업로드 API 는 의도적으로 제공하지 않습니다 (위 표 참고).

Q. PDF 라이브러리들은 업로드 API 가 있던데요?

A. PDF 라이브러리도 일반적으로 bytes / Blob 을 반환하는 형태입니다. 일부가 helper 형태로 `saveAs()` 등 sugar API 를 제공하기도 하지만, 결국 사용자 측 fetch 로 직접 호출하는 코드가 대부분입니다. rhwp 는 sugar 없이 bytes 를 직접 반환하여 사용자가 자기 환경에 맞게 자유롭게 처리하도록 합니다.

Q. CORS 에러가 발생합니다.

A. CORS 는 서버 측 설정 문제입니다 — 업로드 받는 서버에서 `Access-Control-Allow-Origin` 헤더 설정이 필요합니다. rhwp 는 사용자 측에서 `fetch()` 를 호출할 뿐이고, CORS 정책은 서버가 결정합니다.

Q. 큰 파일 (수십 MB) 업로드 시 진행률 표시가 필요합니다.

A. 위 예시 3 의 `XMLHttpRequest` 또는 fetch 의 `ReadableStream` 으로 진행률 추적 가능합니다. 또는 청크 단위 업로드 (multipart) 도 사용자 측에서 자유롭게 구현 가능.


관련 문서

Clone this wiki locally