본문으로 건너뛰기
HexaTransfer
블로그로 돌아가기
기술 심층 분석

파일 전송 API 설계: RESTful 모범 사례

RESTful 모범 사례에 따른 견고한 파일 전송 API를 설계하세요. 인증, 속도 제한, 멀티파트 업로드, 오류 처리를 다룹니다.

잘 설계된 파일 전송 REST API는 다섯에서 여섯 개의 리소스(sessions, parts, shares, downloads, revocations)를 노출하고, HTTP 시맨틱을 정직하게 사용하며(POST로 생성, PUT으로 멱등 청크, DELETE로 취소), 실제 바이트 이동은 서명된 스토리지 URL에 위임해 서버가 대역폭 병목이 되지 않도록 합니다. 인증은 TLS 1.3 위에서 단기 베어러 토큰을 사용하고, 속도 제한은 생성과 메타데이터 읽기를 구분하며, 청크 업로드는 tus 재개 가능 패턴 또는 S3 멀티파트 시맨틱을 따르고, 오류는 RFC 7807 Problem Details 형식으로 클라이언트가 프로그램적으로 처리할 수 있게 합니다.

파일이 아닌 액션 중심으로 리소스 모델 설계

흔한 실수는 API를 파일 트리로 모델링하는 것입니다. 파일 전송 서비스는 세 가지 리소스로 모델링하는 것이 낫습니다.

  • /sessions — 진행 중인 업로드로 POST로 생성하고 청크 PUT으로 채웁니다
  • /shares — 완료되어 주소가 부여된 전송으로 만료일과 다운로드 제한이 있습니다
  • /downloads — 바이트를 가져오기 위한 단기 서명된 접근 핸들입니다

세션은 complete 액션으로 공유가 되고, 공유는 시간 또는 다운로드 제한으로 만료됩니다. 취소는 공유에 대한 PATCH 또는 취소 토큰이 있는 DELETE입니다. "파일"이나 "폴더"라는 명사는 없습니다. 그것들은 스토리지의 구현 세부 사항이지 공개 계약이 아닙니다.

이 구조는 API 표면을 작게 유지하고(10개 미만 엔드포인트), HTTP 캐싱에 깔끔하게 매핑되며(공유는 Cache-Control: private, max-age=60, 다운로드는 no-store), 스토리지 백엔드를 교체해도 클라이언트를 깨뜨리지 않습니다.

인증 및 인가 패턴

사용자 대상 API에는 EdDSA로 서명된 단기 JWT(15분 TTL)를 발급하고 보안 HttpOnly 쿠키로 갱신하세요. 토큰은 Authorization: Bearer에 넣고, 로그에 기록되는 쿼리 스트링에는 절대 넣지 마세요.

머신 간 통신에는 HMAC-SHA256 요청 서명이 베어러 토큰보다 낫습니다. 키를 전송하지 않고도 키 소유를 증명하기 때문입니다. AWS SigV4가 참조 설계입니다.

Authorization: HEX4-HMAC-SHA256 
  Credential=AKIA.../20261202/eu/transfer/hex4_request,
  SignedHeaders=host;x-hex-date;x-hex-content-sha256,
  Signature=...

서명된 다운로드 URL의 TTL은 시간이 아닌 분 단위로 설정하세요. 15분 창은 유출된 URL이 전달될 위험과 사용성 사이의 균형입니다. IP 잠금은 CGNAT 뒤의 사용자를 차단할 수 있어 보통 적용하지 않는 것이 좋습니다.

청크 업로드와 멀티파트 시맨틱

재개 가능한 청크 업로드에는 두 가지 신뢰할 만한 프로토콜이 있습니다. tus(IETF 초안, Upload-OffsetUpload-Length 헤더)와 S3 멀티파트(PartNumber, UploadId, ETag). 하나를 선택해 일관되게 사용하세요. 자체 프로토콜을 만들고 싶은 욕구가 생기지만, 부분 쓰기, 순서가 맞지 않는 청크, 포기된 세션을 처리해야 할 때 결국 좋지 않게 끝납니다.

REST 형식의 tus 패턴:

POST   /sessions              -> 201, Location: /sessions/abc
HEAD   /sessions/abc          -> 200, Upload-Offset: 104857600
PATCH  /sessions/abc          -> 204, body = next chunk
POST   /sessions/abc/complete -> 201, Location: /shares/xyz
DELETE /sessions/abc          -> 204

청크 크기 제한: S3 멀티파트에 맞는 최소 5 MB, 재시도 비용을 제한하는 최대 100 MB, 기본 8 MB. 광고된 오프셋과 맞지 않는 청크는 HTTP 409 Conflict와 예상 오프셋을 설명하는 Problem 문서로 거부하세요.

실제 남용을 반영한 속도 제한

속도 제한은 엔드포인트 비용에 따라 달라져야 합니다.

  • POST /sessions: IP당 시간당 20회 (고비용 — 스토리지 할당)
  • PATCH /sessions/:id: 세션당 시간당 10,000회 (저비용 — 바이트 쓰기)
  • GET /shares/:id: IP당 시간당 1,000회 (메타데이터 조회)
  • GET /shares/:id/download: IP당 시간당 100회 (이그레스 비용 발생)

엣지(Cloudflare, Fastly)에서 기본 보호용으로 제한을 적용하고, 애플리케이션 레이어(express-rate-limit, Fastify의 @fastify/rate-limit)에서 버스트 제어를 추가하세요. 429를 초 단위 Retry-After와 함께 반환하세요. 성공 응답에도 속도 제한 헤더를 포함하세요.

RateLimit-Limit: 20
RateLimit-Remaining: 17
RateLimit-Reset: 2843

IETF RateLimit 초안을 따르면 잘 동작하는 클라이언트가 차단되는 대신 스스로 속도를 조절할 수 있습니다.

클라이언트가 처리할 수 있는 오류 응답

RFC 7807 Problem Details가 표준입니다. 모든 오류는 Content-Type: application/problem+json으로 반환합니다.

{
  "type": "https://api.example.com/errors/chunk-offset-mismatch",
  "title": "Chunk offset mismatch",
  "status": 409,
  "detail": "Server expected offset 5242880, received 4194304.",
  "expected_offset": 5242880,
  "session_id": "abc123"
}

type URI는 문서화되고 안정적이어야 합니다. 클라이언트가 패턴 매칭하는 것이 바로 이것입니다. title은 일반적이고, detail은 구체적입니다. 사용자 정의 필드는 머신이 처리할 수 있는 컨텍스트를 추가합니다. 오류에 스택 트레이스, 파일 경로, 내부 ID를 절대 노출하지 마세요.

HTTP 상태 코드를 정직하게 사용하세요. 400은 잘못된 입력, 401은 인증 없음, 403은 인증됐지만 권한 없음, 404는 리소스가 존재한 적 없을 때만(만료된 공유에는 410 Gone 사용), 413은 할당량 초과 페이로드, 429는 속도 제한, 500은 버그, 503은 유지보수.

콘텐츠 협상과 스트리밍

업로드 엔드포인트는 application/octet-stream을 수락하고 Content-Length를 요구해야 합니다. 청크 업로드에 multipart/form-data를 거부하세요. 파싱 오버헤드만 추가하고 이점이 없습니다. tus 스타일 부분 쓰기에는 Content-Range를 수락하세요.

다운로드 엔드포인트는 재개 가능한 다운로드를 위해 HTTP Range 요청(RFC 7233)을 지원해야 합니다.

GET /shares/xyz/blob
Range: bytes=104857600-209715199
-> 206 Partial Content
   Content-Range: bytes 104857600-209715199/2147483648

이것이 브라우저가 Wi-Fi 연결 끊김 후 2 GB 다운로드를 재개할 수 있게 하는 것입니다. 대부분의 S3 호환 스토어는 Range 요청을 네이티브로 제공합니다. API는 전달하거나 서명만 하면 됩니다.

부채 없는 버전 관리

헤더가 아닌 URL 경로로 버전을 관리하세요(/v1/sessions). 경로 버전 관리는 로그에서 보이고, 캐싱이 가능하며, Accept: application/vnd.example.v1+json보다 디버깅이 쉽습니다. v2가 출시된 후 최소 24개월 동안 v1을 유지하세요. 필드는 자유롭게 추가하고(클라이언트는 모르는 필드를 무시해야 합니다), 안정 버전에서는 필드를 제거하거나 이름을 바꾸지 마세요.

호환성을 깨는 변경이 필요한 경우 12개월 동안 v1과 v2를 병렬 운영하고, RFC 8594에 따라 v1 응답에 Sunset 헤더를 노출하며, 실제 이전·이후 예시가 있는 마이그레이션 가이드를 공개하세요.

관찰 가능성과 디버그 가능성

모든 응답에는 요청에서 전달받거나 생성된 상관관계 ID(X-Request-ID)가 포함되어야 합니다. ID, 클라이언트 IP(개인정보 민감 시 해시), 엔드포인트, 상태, 처리 시간을 구조화된 JSON으로 기록하세요. 요청 본문은 기록하지 마세요. 그것이 암호화 키가 Datadog에 남는 경로입니다.

엔드포인트별 메트릭을 수집하세요. 요청 수, p50/p95/p99 지연 시간, 오류율, 입출력 바이트. p99 지연 시간 급증과 기준 이상의 5xx율에 알림을 설정하세요. OpenTelemetry를 통한 추적으로 API, 스토리지, 데이터베이스 전반의 요청 흐름을 파악하세요.

HexaTransfer의 API는 위 패턴을 따릅니다. 간결한 리소스, tus 스타일 청크 업로드, Problem Details 오류, 15분 서명된 다운로드, RateLimit 헤더. hexatransfer.com에서 사용해보세요 — 무료, 계정 불필요, 최대 10 GB.

현실과 일치하는 문서화

서버 코드와 같은 저장소에 OpenAPI 3.1 스펙을 유지해 스키마 드리프트가 운영 환경 문제가 되기 전에 PR 리뷰에서 발견되도록 하세요. 스펙에서 최소 하나의 SDK(TypeScript 또는 Python)를 생성하고 자체 예시에 사용하세요. SDK 버그가 스펙 버그를 빠르게 드러냅니다. 모든 엔드포인트에 curl 예시, 실제 파일을 20줄 이내로 업로드하는 퀵스타트, 모든 오류 type URI를 문서화한 페이지를 포함하세요. 오후 한나절에 통합할 수 있는 API는 일주일의 리버스 엔지니어링이 필요한 API보다 10배 많은 제품에 채택됩니다.

엔드투엔드 암호화로 대용량 파일을 안전하게 전송

엔드투엔드 암호화로 최대 10GB의 파일을 무료로 전송하세요. 계정이 필요하지 않습니다. 업로드 전에 브라우저에서 파일이 암호화되어 다른 사람은 읽을 수 없습니다.

파일 보내기