파일 업로드 (PUT)
파일을 R2 스토리지에 업로드합니다. 파일 크기에 따라 자동으로 단일 업로드 또는 스트리밍 멀티파트 업로드로 분기됩니다.
요청
요청 형식
=== "multipart/form-data"
```
Content-Type: multipart/form-data
file: (바이너리 파일 데이터)
```
`file` 필드에 파일을 첨부합니다.
=== "application/octet-stream"
```
Content-Type: application/octet-stream
X-Filename: document.pdf
(바이너리 파일 데이터)
```
요청 body에 파일을 직접 포함하고, `X-Filename` 헤더로 파일명을 전달합니다.
업로드 자동 분기
| 파일 크기 | 방식 | 설명 |
|---|---|---|
| 10MB 미만 | 단일 업로드 | 파일을 메모리에 로드 후 한 번에 업로드 |
| 10MB 이상 | 스트리밍 멀티파트 업로드 | 파일을 10MB 청크 단위로 스트리밍 업로드 |
5단계 검증 (단일 업로드)
업로드 전 다음 검증을 순서대로 수행합니다.
| 단계 | 검증 항목 | 실패 시 |
|---|---|---|
| 1 | HTTP 메서드 - 토큰의 method와 요청 메서드 일치 |
400 "HTTP 메서드 검증 실패" |
| 2 | 파일 데이터 존재 - 파일이 비어있지 않은지 | 400 "요청에 파일 데이터가 없습니다" |
| 3 | 파일명 - 토큰의 originalFilename과 일치 |
400 "파일명 검증 실패" |
| 4 | MIME 타입 - 토큰의 meta.mime과 일치 (설정된 경우) |
400 "MIME 타입 검증 실패" |
| 5 | 파일 크기 - 토큰의 meta.size와 오차 범위 내 (설정된 경우) |
400 "파일 크기 검증 실패" |
파일 크기 검증 오차 허용
허용 오차 = max(1KB, 예상 크기 × 1%)
- 절대 오차: 1,024 바이트 (1KB)
- 상대 오차: 예상 크기의 1%
- 두 값 중 큰 쪽을 허용 범위로 사용
멀티파트 업로드 (10MB 이상)
멀티파트 업로드 시에는 파일을 메모리에 올리지 않고 스트리밍으로 처리합니다.
사전 검증
- HTTP 메서드 검증
- 파일명 검증
- MIME 타입 검증
스트리밍 처리
- R2 멀티파트 업로드 세션 생성
- ReadableStream에서 데이터를 읽으며 10MB 단위로 파트 업로드
- 마지막 파트는 5MB 미만 허용 (R2 최소 요구사항)
- 모든 파트 업로드 완료 후 병합
사후 검증
- 스트리밍 완료 후 전체 파일 크기를 토큰의
meta.size와 비교 - 검증 실패 시 업로드된 파일을 R2에서 즉시 삭제
에러 발생 시
멀티파트 업로드 도중 에러가 발생하면 multipart.abort()를 호출하여 불완전한 파트를 정리합니다.
Firebase 알림 및 롤백
업로드 성공 후 Firebase Cloud Function에 메타데이터를 전송합니다.
Firebase 페이로드
{
"path": "companies/...",
"company_id": "comp_123",
"related_doc_id": "doc_456",
"site_id": "site_789",
"doc_type": "contract",
"doc_page": "1",
"tags": {},
"filename": "document.pdf",
"target_date": "2024-01-01",
"size": 5242880,
"mime": "application/pdf",
"uid": "user_id"
}
롤백 메커니즘
Firebase 알림이 실패하면 보상 트랜잭션(Compensating Transaction) 패턴을 적용합니다.
- Firebase 알림 실패 감지
- R2에서 방금 업로드한 파일 삭제
- 에러 응답 반환
응답
성공 (200)
멀티파트 업로드 시:
에러
| HTTP 상태 | 메시지 | 원인 |
|---|---|---|
| 400 | HTTP 메서드 검증 실패 | 토큰 method와 불일치 |
| 400 | 요청에 파일 데이터가 없습니다 | 파일 누락 또는 크기 0 |
| 400 | 파일명 검증 실패 | 파일명 불일치 |
| 400 | MIME 타입 검증 실패 | MIME 타입 불일치 |
| 400 | 파일 크기 검증 실패 | 크기 오차 범위 초과 |
| 400 | multipart/form-data에서 'file'을 찾을 수 없습니다 | form-data 파싱 실패 |
| 500 | R2 멀티파트 업로드 실패 | R2 업로드 에러 |
| 500 | Firebase 업데이트 실패 (R2 롤백 완료) | Firebase 응답 실패 |
| 500 | Firebase 연결 실패 (R2 롤백 완료) | Firebase 연결 에러 |