[PonsWarp 개념 교실 23] presigned URL — 서버를 거치지 않고 R2에 올리는 법
쉽게 말하면, presigned URL은 서버가 파일 대신 '잠깐 쓸 수 있는 업로드 권한이 들어간 주소'를 발급하는 방식이다.

Cloud Drop에서는 브라우저가 파일을 서버로 보내고 서버가 다시 R2로 올리지 않는다. 서버가 R2에 올릴 수 있는 URL을 만들고, 브라우저가 그 URL로 직접 PUT한다.
흐름
브라우저 → create share → 서버
브라우저 ← presigned URL ← 서버
브라우저 ───── PUT ─────→ R2
브라우저 → complete ───→ 서버
서버는 권한 발급과 매니페스트만 관리한다. 실제 파일 바이트는 R2로 직접 간다.
작은 파일과 Multipart
작은 파일은 단일 PUT으로 충분하다. 큰 파일은 여러 파트로 나눈다.
- 파트마다 presigned upload URL 발급
- 브라우저가 병렬 PUT
- 각 파트의 ETag 수집
- complete 요청에서 파트 목록 제출
- 서버가 R2 Multipart completion 실행
PonsWarp는 512MB 이상 파일을 Multipart 경로로 보내고, 파트 크기는 64MB를 기준으로 계산한다.
rust// ponswarp-signaling-rs/src/handlers/cloud_share.rs use aws_sdk_s3::presigning::PresigningConfig;
왜 ETag가 필요한가
Multipart Upload는 R2가 어떤 파트들이 정상 도착했는지 알아야 완성할 수 있다. 브라우저가 각 PUT 응답의 ETag를 모아 서버에 보내는 이유다.
complete 요청의 파트 목록이 생성된 대상과 다르면 서버는 완료를 거부한다. 파일 수·크기·파트 수를 다시 확인하는 단계다.
PonsWarp 적용 지점
| 항목 | 구현 | 증거 |
|---|---|---|
| presign | S3/R2 presigning config | cloud_share.rs:6,693-707 |
| Multipart 기준 | 512MB 이상 | cloud_share.rs의 threshold 테스트 |
| 파트 크기 | 64MB 기준 | cloud_share.rs:454-463 |
| 완료 | 파트 ETag 검증 후 complete | cloud_share.rs:785-868 |
| 취소 | Abort Multipart | cloud_share.rs:289-305 |
presigned URL의 권한 경계
presigned URL은 URL을 가진 사람에게 일정 시간 동안 특정 object key에 특정 HTTP method를 사용할 권한을 위임한다. 따라서 핵심은 URL을 만드는 코드보다 무엇을 얼마나 오래 허용했는지다. key, content type, content length, expiry를 manifest와 연결하지 않으면 사용자는 다른 파일을 같은 권한으로 덮어쓸 수 있다.
textcreate share ──► object key + size + expiry 저장 │ └─ presigned PUT 발급 │ 브라우저 ─────────────┴────► R2 │ └─ complete ──► 서버가 ETag/size/manifest 확인
서버는 파일 바이트를 보지 않지만 upload intent와 완료 결과는 관리한다. 작은 파일은 단일 PUT으로 끝낼 수 있고, 큰 파일은 Multipart upload의 part 목록과 완료 요청을 별도로 추적한다. complete를 받았다고 곧바로 성공 처리하지 말고 R2가 반환한 ETag와 선언된 size를 비교해야 한다.
그냥 서버가 파일을 받아 R2에 올리면 안 되나?
서버 중계는 검증 지점이 단순하지만 파일 크기만큼 서버 ingress·egress와 디스크 임시 공간이 필요하다. presigned 방식은 브라우저에서 R2로 직접 보내 비용과 병목을 줄이는 대신, URL 유출·잘못된 key·만료·완료 검증을 설계해야 한다. 즉 presigned URL은 인증을 없애는 것이 아니라 권한을 짧은 주소로 쪼개는 방식이다.
| 단계 | 서버가 확인하는 것 | 외부에 노출되는 것 |
|---|---|---|
| create | 사용자·key·size·정책 | 제한된 URL |
| PUT | R2 서명 조건 | object bytes |
| complete | ETag·size·manifest | 성공 상태 |
| expiry | 만료 시간 | 이후 사용 거부 |
재구축 시에는 complete가 누락된 upload를 정리하는 만료 작업, 같은 key의 덮어쓰기 정책, content-length 제한을 함께 둔다. URL이 유효하다는 사실과 파일이 애플리케이션 데이터로 승인됐다는 사실은 서로 다른 상태다.
업로드 완료와 사용자 노출은 분리한다
R2에 object가 존재한다는 사실만으로 share가 완성된 것은 아니다. complete 단계에서 manifest와 size를 확인하고, 그 결과를 DB 상태로 기록한 뒤에야 다른 사용자에게 다운로드 정보를 노출해야 한다. 만료된 presigned URL과 이미 완료된 object를 정리하는 작업도 별도 운영 경로다.
브라우저 재시도에서는 같은 key를 덮어쓸지 새 upload id를 발급할지 선택해야 한다. 전자는 중복 object를 줄이지만 부분 업로드가 최신 파일을 가릴 수 있고, 후자는 정리는 쉬워도 orphan object가 늘어난다. 구현을 다시 한다면 upload id, object key, manifest 상태를 분리해 관찰한다.
한 문장 정리
PonsWarp Cloud Drop은 서버가 파일 바이트를 중계하지 않고 presigned PUT/Multipart URL을 발급해 브라우저가 R2로 직접 업로드하게 한다.
관련 개념: [개념 21 — 시그널링] · [구현 13 — 서버가 하는 일과 안 하는 일]
presigned URL의 권한 경계
presigned URL은 파일을 공개하는 URL이 아니라 특정 object key와 HTTP 동작에 한정된 임시 권한이다. URL이 노출되어도 TTL이 지나면 사용할 수 없고, 다른 object를 임의로 지정할 수 없어야 한다.
Multipart 완료는 검증 단계다
각 파트 PUT의 ETag를 단순히 모아 보내는 것이 아니라, 서버가 생성한 파트 수·파일 크기와 비교해야 한다. 파트가 빠졌거나 순서가 다르면 complete를 거부하고 abort로 정리한다.
Direct와 Cloud Drop
Direct P2P는 두 사용자가 동시에 온라인이어야 하지만 서버 저장 비용이 없다. Cloud Drop은 R2를 사용해 비동기 pickup을 제공하지만 retention·용량·다운로드 제한이 생긴다. presigned URL은 이 둘 사이에서 “서버가 바이트를 보지 않으면서 저장을 허용하는” 경계다.