Docling을 기반으로 한 REST API 서버로, Office 문서를 Markdown으로 변환하고 문서 내의 이미지, 표, 다이어그램 등을 자동으로 추출하여 저장합니다.
- 다양한 문서 형식 지원: PDF, DOCX, XLSX, PPTX, HTML, Markdown, CSV, 이미지 등
- 다양한 출력 형식: Markdown 또는 HTML로 변환
- 네 가지 변환 모드:
- Markdown 빠른 모드 (
/convert/markdown): Markdown만 빠르게 변환 (이미지 추출 없음) - Markdown 완전 모드 (
/convert/markdown/with-images): 이미지 추출과 함께 Markdown 변환 - HTML 빠른 모드 (
/convert/html): HTML만 빠르게 변환 (이미지 추출 없음) - HTML 완전 모드 (
/convert/html/with-images): 이미지 추출과 함께 HTML 변환
- Markdown 빠른 모드 (
- 자동 이미지 추출: 문서 내의 이미지, 표, 다이어그램을 자동으로 추출하여 별도 파일로 저장
- IP 기반 접근 제어: 허용된 IP에서만 API 호출 가능 (마이크로서비스 아키텍처에 최적화)
- 보안 이미지 서빙: 화이트리스트 IP에서만 추출된 이미지에 접근 가능
- REST API: 간단한 HTTP API로 어디서든 사용 가능
- Docker 지원: Docker Compose로 한 번에 실행
- Docker
- Docker Compose
# 저장소 클론 또는 프로젝트 디렉토리로 이동
cd docling-server
# Docker Compose로 서버 시작
docker-compose up -d서버가 시작되면:
- REST API 서버:
http://localhost:8001- 문서 변환 API
- 이미지 파일 서빙
- IP 화이트리스트 기반 접근 제어
# API 서버 상태 확인
curl http://localhost:8001/health응답:
{
"status": "healthy",
"service": "document-converter"
}# API 엔드포인트 목록 확인
curl http://localhost:8001/응답에서 사용 가능한 모든 엔드포인트와 허용된 IP 목록을 확인할 수 있습니다
서버는 다음 엔드포인트를 제공합니다 (모든 엔드포인트는 IP 화이트리스트 검증 필요):
POST /convert/markdown- Markdown만 반환 (빠른 변환, 이미지 추출 없음)POST /convert/markdown/with-images- 이미지 추출과 함께 Markdown 반환 (완전한 변환)POST /convert- (Deprecated)/convert/markdown/with-images와 동일
POST /convert/html- HTML만 반환 (빠른 변환, 이미지 추출 없음)POST /convert/html/with-images- 이미지 추출과 함께 HTML 반환 (완전한 변환)
GET /images/{filename}- 추출된 이미지 조회 (IP 화이트리스트 검증)GET /figures/{filename}- 추출된 이미지 조회 (레거시 엔드포인트, IP 화이트리스트 검증)
Endpoint: POST /convert/markdown
이미지를 추출하지 않고 Markdown 텍스트만 빠르게 변환합니다.
요청:
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/markdown응답 예시:
{
"status": "success",
"original_filename": "document.pdf",
"markdown": "# 문서 제목\n\n본문 내용...\n\n<!-- image -->\n\n...",
"figures": [],
"figures_count": 0
}Endpoint: POST /convert/markdown/with-images
문서 내의 이미지를 추출하여 별도 파일로 저장하고, Markdown에 이미지 URL을 포함합니다.
요청:
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/markdown/with-images응답 예시:
{
"status": "success",
"original_filename": "document.pdf",
"output_format": "markdown",
"markdown": "\n\n## 문서 제목\n\n본문 내용...",
"figures": [
{
"id": "abc123",
"filename": "document_figure_0_abc123.png",
"path": "/app/static/figures/document_figure_0_abc123.png",
"url": "http://localhost:8001/images/document_figure_0_abc123.png",
"type": "image",
"caption": "Figure 1"
}
],
"figures_count": 1
}Endpoint: POST /convert/html
이미지를 추출하지 않고 HTML로만 빠르게 변환합니다.
요청:
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/html응답 예시:
{
"status": "success",
"original_filename": "document.pdf",
"output_format": "html",
"html": "<!DOCTYPE html>\n<html>\n<head>...</head>\n<body>\n<h1>문서 제목</h1>\n<p>본문 내용...</p>\n</body>\n</html>",
"figures": [],
"figures_count": 0
}Endpoint: POST /convert/html/with-images
문서 내의 이미지를 추출하여 별도 파일로 저장하고, HTML에 이미지 태그를 포함합니다.
요청:
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/html/with-images응답 예시:
{
"status": "success",
"original_filename": "document.pdf",
"output_format": "html",
"html": "<!DOCTYPE html>\n<html>\n<head>...</head>\n<body>\n<figure><img src=\"http://localhost:8001/images/document_figure_0_abc123.png\" alt=\"Figure 1\"/><figcaption>Figure 1</figcaption></figure>\n<h1>문서 제목</h1>\n<p>본문 내용...</p>\n</body>\n</html>",
"figures": [
{
"id": "abc123",
"filename": "document_figure_0_abc123.png",
"path": "/app/static/figures/document_figure_0_abc123.png",
"url": "http://localhost:8001/images/document_figure_0_abc123.png",
"type": "image",
"caption": "Figure 1"
}
],
"figures_count": 1
}import requests
# 방법 1: Markdown만 빠르게 변환
with open('document.pdf', 'rb') as f:
files = {'file': f}
response = requests.post('http://localhost:8001/convert/markdown', files=files)
result = response.json()
print(f"변환 상태: {result['status']}")
print(f"\nMarkdown 내용:\n{result['markdown']}")
# 방법 2: 이미지 추출과 함께 Markdown 변환
with open('document.pdf', 'rb') as f:
files = {'file': f}
response = requests.post('http://localhost:8001/convert/markdown/with-images', files=files)
result = response.json()
print(f"변환 상태: {result['status']}")
print(f"추출된 이미지 수: {result['figures_count']}")
print(f"\nMarkdown 내용:\n{result['markdown']}")
# 방법 3: HTML로 변환
with open('document.pdf', 'rb') as f:
files = {'file': f}
response = requests.post('http://localhost:8001/convert/html', files=files)
result = response.json()
print(f"HTML 내용:\n{result['html']}")
# 방법 4: 이미지 추출과 함께 HTML 변환
with open('document.pdf', 'rb') as f:
files = {'file': f}
response = requests.post('http://localhost:8001/convert/html/with-images', files=files)
result = response.json()
print(f"추출된 이미지 수: {result['figures_count']}")
print(f"\nHTML 내용:\n{result['html']}")
# 이미지 URL 출력
for figure in result['figures']:
print(f"이미지: {figure['url']}")const FormData = require("form-data");
const fs = require("fs");
const axios = require("axios");
// 방법 1: Markdown만 빠르게 변환
const form1 = new FormData();
form1.append("file", fs.createReadStream("document.pdf"));
axios
.post("http://localhost:8001/convert/markdown", form1, {
headers: form1.getHeaders(),
})
.then((response) => {
const result = response.data;
console.log(`변환 상태: ${result.status}`);
console.log(`\nMarkdown 내용:\n${result.markdown}`);
})
.catch((error) => {
console.error("에러:", error.response?.data || error.message);
});
// 방법 2: 이미지 추출과 함께 Markdown 변환
const form2 = new FormData();
form2.append("file", fs.createReadStream("document.pdf"));
axios
.post("http://localhost:8001/convert/markdown/with-images", form2, {
headers: form2.getHeaders(),
})
.then((response) => {
const result = response.data;
console.log(`변환 상태: ${result.status}`);
console.log(`추출된 이미지 수: ${result.figures_count}`);
console.log(`\nMarkdown 내용:\n${result.markdown}`);
result.figures.forEach((figure) => {
console.log(`이미지: ${figure.url}`);
});
})
.catch((error) => {
console.error("에러:", error.response?.data || error.message);
});
// 방법 3: 이미지 추출과 함께 HTML 변환
const form3 = new FormData();
form3.append("file", fs.createReadStream("document.pdf"));
axios
.post("http://localhost:8001/convert/html/with-images", form3, {
headers: form3.getHeaders(),
})
.then((response) => {
const result = response.data;
console.log(`변환 상태: ${result.status}`);
console.log(`추출된 이미지 수: ${result.figures_count}`);
console.log(`\nHTML 내용:\n${result.html}`);
result.figures.forEach((figure) => {
console.log(`이미지: ${figure.url}`);
});
})
.catch((error) => {
console.error("에러:", error.response?.data || error.message);
});# Markdown만 빠르게 변환하여 저장
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/markdown \
| jq -r '.markdown' > output.md
# 이미지 추출과 함께 Markdown 변환하여 JSON으로 저장
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/markdown/with-images \
-o result.json
# 이미지 추출과 함께 Markdown 변환하여 Markdown만 저장
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/markdown/with-images \
| jq -r '.markdown' > output.md
# HTML로 변환하여 저장
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/html/with-images \
| jq -r '.html' > output.html이 API 서버는 IP 화이트리스트 기반 접근 제어를 사용합니다. 이는 마이크로서비스 아키텍처에서 API 게이트웨이나 인증 서버가 별도로 존재하는 경우에 적합합니다.
- 모든 API 엔드포인트는 요청자의 IP 주소를 검증합니다
- 허용된 IP 목록에 있는 IP만 API를 호출할 수 있습니다
- Docker 컨테이너 네트워크, 프록시, 로드밸런서 등을 고려하여
X-Forwarded-For헤더도 확인합니다
기본적으로 다음 IP 범위가 허용됩니다:
127.0.0.1- 로컬호스트 (IPv4)::1- 로컬호스트 (IPv6)172.16.0.0/12- Docker 네트워크 범위192.168.0.0/16- 사설 네트워크 범위10.0.0.0/8- 사설 네트워크 범위
환경 변수 ALLOWED_IPS를 사용하여 허용 IP를 설정할 수 있습니다:
# .env 파일에 추가
ALLOWED_IPS=127.0.0.1,::1,192.168.1.0/24,10.0.0.5
# 또는 docker-compose.yaml에서
environment:
- ALLOWED_IPS=127.0.0.1,::1,192.168.1.0/24,10.0.0.5# docker-compose.yaml
environment:
- ALLOWED_IPS=10.0.1.10,10.0.1.11,10.0.1.12# Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: docling-config
data:
ALLOWED_IPS: "10.244.0.0/16" # Pod network CIDRNginx가 X-Forwarded-For 헤더를 설정하도록 구성:
location /api/ {
proxy_pass http://docling-api:8001/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}허용되지 않은 IP에서 접근 시:
{
"detail": "Access forbidden - IP 203.0.113.1 is not in whitelist"
}자세한 내용은 IP_ACCESS_CONTROL.md 문서를 참조하세요
| 형식 | 확장자 |
|---|---|
.pdf |
|
| Word | .docx |
| Excel | .xlsx |
| PowerPoint | .pptx |
| HTML | .html, .htm |
| Markdown | .md |
| CSV | .csv |
| 이미지 | .png, .jpg, .jpeg, .tiff, .bmp, .webp |
docling-server/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 애플리케이션
│ ├── converter.py # 문서 변환 로직
│ ├── config.py # 설정
│ └── ip_whitelist.py # IP 화이트리스트 검증
├── static/
│ └── figures/ # 추출된 이미지 저장 위치
├── uploads/ # 임시 업로드 디렉토리
├── web_server.py # 정적 파일 서버 (deprecated)
├── requirements.txt # Python 의존성
├── Dockerfile
├── docker-compose.yaml
└── README.md
REST API 서버 (포트 8001)
- 문서 변환 로직 처리
- Markdown 및 HTML 생성
- 이미지 추출 및 저장
- IP 화이트리스트 기반 접근 제어 (보안)
- 이미지 파일 서빙
- CORS 지원
이 구성에서는 모든 기능이 하나의 API 서버에 통합되어 있으며, IP 화이트리스트를 통해 허용된 서버만 API를 호출할 수 있습니다. 사용자 인증은 별도의 API 게이트웨이나 인증 서버에서 처리하는 것을 권장합니다.
[사용자] → [API 게이트웨이/인증 서버] → [Docling 변환 서버]
↓ 인증 처리 ↓ IP 검증
JWT/세션 관리 문서 변환 & 이미지 추출
보안 모델:
- API 게이트웨이/인증 서버가 사용자 인증 처리
- 인증된 요청만 Docling 서버로 전달
- Docling 서버는 API 게이트웨이의 IP만 허용
- 최종 사용자는 Docling 서버에 직접 접근 불가
docker-compose.yaml 또는 .env 파일에서 다음 환경 변수를 설정할 수 있습니다:
| 변수 | 기본값 | 설명 |
|---|---|---|
API_HOST |
0.0.0.0 |
API 서버 호스트 |
API_PORT |
8001 |
API 서버 포트 |
API_SERVER_URL |
http://localhost:8001 |
API 서버 URL (이미지 URL 생성에 사용) |
ALLOWED_IPS |
127.0.0.1,::1,172.16.0.0/12,192.168.0.0/16,10.0.0.0/8 |
허용된 IP 주소 목록 (쉼표로 구분, CIDR 표기 지원) |
MAX_UPLOAD_SIZE |
52428800 (50MB) |
최대 업로드 파일 크기 (바이트) |
UPLOAD_DIR |
/app/uploads |
임시 업로드 디렉토리 |
FIGURES_DIR |
/app/static/figures |
추출된 이미지 저장 디렉토리 |
이 API 서버는 IP 화이트리스트를 사용하여 접근을 제어합니다.
- IP 화이트리스트: 허용된 IP만 API 접근 가능
- CIDR 표기 지원: IP 범위를 유연하게 설정 가능
- 프록시 지원: X-Forwarded-For 헤더를 통한 실제 클라이언트 IP 확인
- 경로 탐색 방지: Directory traversal 공격 차단
프로덕션 환경에서는 특정 서버의 IP만 허용하도록 설정하세요:
# .env 파일에 추가
ALLOWED_IPS=10.0.1.10,10.0.1.11,192.168.1.0/24
# 또는 docker-compose.yaml에서
environment:
- ALLOWED_IPS=10.0.1.10,10.0.1.11,192.168.1.0/24프로덕션에서는 CORS를 특정 도메인으로 제한하세요. app/main.py에서:
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"], # 실제 도메인으로 변경
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"],
)프로덕션에서는 반드시 HTTPS를 사용하고, Nginx나 AWS ALB 같은 리버스 프록시를 통해 SSL 종료를 처리하세요.
Nginx 리버스 프록시 예시:
location /api/ {
proxy_pass http://docling-api:8001/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}# 로컬호스트에서 API 호출
curl -X POST \
-F "file=@document.pdf" \
http://localhost:8001/convert/markdown허용되지 않은 IP에서 접근 시 다음과 같은 응답을 받습니다:
{
"detail": "Access forbidden - IP 203.0.113.1 is not in whitelist"
}Docker Compose로 실행 중인 경우, 컨테이너 간 통신을 테스트:
# API 컨테이너 내부에서 테스트
docker exec -it docling-api curl http://localhost:8001/health개발 중 코드 변경시 자동 재시작을 원한다면:
docker-compose.yaml에서 volumes 섹션의 주석을 해제:
volumes:
- ./static/figures:/app/static/figures
- ./app:/app/app # 이 줄의 주석 해제- 컨테이너 재시작:
docker-compose down
docker-compose up -dDocker 없이 로컬에서 개발하려면:
# 가상환경 생성 및 활성화
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 의존성 설치
pip install -r requirements.txt
# 서버 실행
python -m app.main
# 또는
uvicorn app.main:app --reloaddocker-compose.yaml에서 포트를 변경:
ports:
- "8080:8000" # 호스트 포트를 8080으로 변경Docker Desktop 설정에서 메모리 할당을 늘려주세요 (최소 4GB 권장).
일부 PDF 문서의 경우 이미지 추출이 제한될 수 있습니다. 이런 경우:
- PDF가 암호화되어 있는지 확인
- PDF의 이미지가 임베디드 형식인지 확인
app/config.py에서 최대 파일 크기 조정:
MAX_UPLOAD_SIZE = 100 * 1024 * 1024 # 100MB스캔된 PDF의 텍스트를 추출하려면 app/converter.py에서 OCR 활성화:
pipeline_options.do_ocr = True그리고 requirements.txt에 OCR 라이브러리 추가:
docling[easyocr]==2.10.0
서버 실행 후 다음 URL에서 자동 생성된 API 문서를 확인할 수 있습니다:
- Swagger UI: http://localhost:8001/docs
- ReDoc: http://localhost:8001/redoc
이 프로젝트는 MIT 라이선스를 따릅니다.
버그 리포트나 기능 제안은 GitHub Issues를 통해 제출해 주세요.
- FastAPI: 현대적이고 빠른 Python 웹 프레임워크
- Docling: IBM Research의 문서 처리 라이브러리
- Docker: 컨테이너화 및 배포
- Python 3.11: 최신 Python 기능 활용