본문으로 건너뛰기
runlot

서명 URL

로그인하지 않은 사용자도 제한된 시간 동안 사용할 수 있는 URL을 생성합니다. 업로드와 다운로드 모두 지원합니다.

const url = await env.storage.presign("covers/hello.png");

기본 요청 메서드는 GET이고 유효 기간은 300초입니다.

await env.storage.presign(key, { method: "GET", ttl: 60 });    // 1분 동안 다운로드 가능
await env.storage.presign(key, { method: "PUT", ttl: 3600 });  // 1시간 동안 업로드 가능

ttl은 초 단위이며 최대 **3,600초(1시간)**입니다. 이 값을 초과하면 요청을 거절합니다.

브라우저가 직접 업로드하도록 구성하기

워커는 URL만 발급하고 파일 데이터는 워커를 거치지 않습니다.

// 워커
app.post("/upload-url", async (c) => {
  const { name, type } = await c.req.json();
  const key = `uploads/${crypto.randomUUID()}/${name}`;
  const url = await c.env.storage.presign(key, { method: "PUT", ttl: 600 });
  return Response.json({ key, url });
});
// 브라우저
const { key, url } = await fetch("/upload-url", {
  method: "POST",
  body: JSON.stringify({ name: file.name, type: file.type }),
}).then((r) => r.json());

await fetch(url, { method: "PUT", body: file });

키는 워커에서 생성하는 것이 중요합니다. 클라이언트가 전달한 파일 이름을 키로 그대로 사용하면 다른 사용자의 파일을 덮어쓸 수 있습니다.

업로드 크기를 묶습니다

size를 전달하면 그 바이트 수가 서명에 들어갑니다. 다른 길이로 올리는 요청은 스토리지 백엔드가 403으로 거부하므로, 바이트가 워커를 지나지 않아도 상한이 지켜집니다.

const url = await env.storage.presign(key, { method: "PUT", size: file.size, ttl: 600 });

size가 없으면 URL 하나가 올릴 수 있는 양에 상한이 없습니다. 전달하면 URL을 발급할 때 이미 쓴 용량만이 아니라 그 크기까지 함께 할당량과 대조합니다.

브라우저는 정확히 그 바이트 수를 보내야 합니다. 같은 URL로 다른 파일을 보내면 실패하므로, 파일이 바뀔 때마다 URL을 새로 요청하세요.

GET과 PUT만 지원합니다

DELETE 요청에는 서명 URL을 만들 수 없습니다. 삭제 작업은 항상 워커에서 권한을 확인한 뒤 수행해야 합니다. 유출된 URL 하나가 파일 삭제 권한이 되는 것을 막기 위해서입니다.

스토리지 백엔드에 따른 제한

서명 URL은 오브젝트 스토리지 백엔드에서만 사용할 수 있습니다. 로컬 개발 환경처럼 디스크 백엔드를 사용하는 경우 501 오류(unsupported)를 반환합니다.

로컬에서 서명 URL을 사용하는 흐름을 테스트해야 한다면, 워커가 파일을 직접 반환하는 대체 경로를 둘 수 있습니다.

let url: string;
try {
  url = await env.storage.presign(key, { ttl: 600 });
} catch {
  url = `/files/${encodeURIComponent(key)}`; // 워커가 get으로 파일을 반환하는 경로
}

쿼터 초과 시 동작

쓰기용 서명 URL은 저장 용량이 가득 차면 발급되지 않습니다(507). 읽기용 URL은 계속 발급할 수 있습니다. 파일을 삭제해 용량을 확보할 수 있어야 하기 때문입니다. "가득 찼다"의 뜻은 요금제마다 다릅니다. 쿼터를 참고하세요.

공개 접두사

서명 URL은 한 사람이 잠시 읽어야 하는 파일에 맞는 도구입니다. 사이트 이미지, 업로드된 아바타, 내려받기 파일처럼 누구나 언제까지나 읽어야 하는 파일에 주소마다 서명을 붙이는 것은 맞지 않는 도구입니다. 대신 접두사를 공개로 표시하면, 그 아래의 모든 키를 서명도 만료도 없이 평범한 HTTPS GET으로 읽을 수 있습니다.

runlot storage public add assets
runlot storage public list
runlot storage public rm assets

같은 세 동작이 대시보드의 파일 저장소 탭 "공개 접두사" 카드에 있습니다. 공개와 해제는 admin 역할이 필요하고, 목록 조회는 viewer면 됩니다.

접두사는 정규화된 형태로 저장됩니다. assets는 assets/가 되고, 매칭은 키에 대한 단순한 문자열 접두사 비교입니다. 끝의 슬래시가 assets/가 assets-private/까지 여는 일을 막습니다. / 하나만 적으면 프로젝트의 모든 키가 공개됩니다. 빈 접두사는 거절되므로 빈 인자가 "전부"를 뜻하는 일은 없습니다. 프로젝트마다 접두사를 20개까지 공개할 수 있습니다.

주소

https://objects.runlot.io/<org>/<project>/<key>

조직 acme의 프로젝트 shop에 있는 키 assets/logo.png는 https://objects.runlot.io/acme/shop/assets/logo.png에 있습니다. 이 경로로는 GET과 HEAD만 처리합니다. 서명 없이는 아무것도 쓸 수 없습니다.

워커 코드에서

env.storage.publicUrl(key)가 그 주소를 돌려주고, 키가 공개 접두사 아래에 없으면 null을 돌려줍니다.

const url = env.storage.publicUrl("assets/logo.png"); // "https://objects.runlot.io/acme/shop/assets/logo.png"
const none = env.storage.publicUrl("private/report.pdf"); // null

동기 함수이고 요청을 보내지 않습니다. 워커가 시작될 때 받은 접두사 목록으로 문자열을 이어 붙일 뿐입니다. 아무도 공개하지 않은 키에 null이 나오는 것은 오류가 아니라 보통의 답입니다. 없는 키에 get이 null을 돌려주는 것과 같은 모양입니다. put이 거절하는 키(.., 빈 구간)는 여기서도 예외를 던지므로, 잘못된 키가 <img src>에 들어갈 그럴듯한 URL이 되는 일은 없습니다.

공개 접두사를 바꾸면 워커가 새 목록을 보도록 다시 시작됩니다. 바꾸기 전에 시작된 워커는 실제로 공개된 키에 null을 답할 수는 있어도, 공개되지 않은 키에 주소를 답하는 일은 없습니다. 실제로 서빙할지는 이 값이 아니라 오브젝트 프런트가 결정합니다.

공개 파일과 함께 보내는 헤더

헤더값
Cache-Controlpublic, max-age=3600
ETag, Content-Type, Content-Length객체의 값
Last-Modified알 수 있을 때 객체의 값

캐시 수명은 일률적으로 1시간이고 immutable은 절대 붙이지 않습니다. 덮어쓴 키는 1시간 안에 새 내용을 보여줍니다. 더 길게 캐시하고 싶다면 키에 내용 해시를 넣고, 내용이 바뀔 때 새 키로 업로드하세요.

서명 없이는 절대 서빙하지 않는 것

어느 공개 접두사에도 속하지 않는 키는 서명 없이 절대 서빙하지 않고, 응답은 403이 아니라 404입니다. 비공개 키와 존재하지 않는 키가 바깥에서는 똑같이 보이므로, 비공개 파일의 주소는 아무것도 드러내지 않습니다. 버킷 자체는 내내 비공개입니다. 공개 읽기는 저희 프런트가 목록을 확인한 뒤에 저희 자격 증명으로 수행하므로, 스토리지 백엔드 자체의 정책이 바뀌어도 공개 범위가 넓어지지 않습니다.

공개 해제는 60초 안에 적용됩니다. 프런트가 목록을 그만큼 캐시하므로, 이미 주소를 가진 쪽은 runlot storage public rm이 끝난 뒤에도 최대 1분까지 계속 읽을 수 있습니다.

서명 URL은 이 모든 것의 영향을 받지 않습니다. 서명된 요청은 공개 목록을 보기 전에 처리되므로, 접두사를 공개해도 서명 URL의 동작은 바뀌지 않습니다.

이 페이지의 목차