aws4fetch로 R2 사용하기
aws4fetch 패키지로 R2를 사용하는 예시예요. fetch와 SubtleCrypto API를 사용해 S3 호환 API를 호출하고 버킷·객체를 나열하며 사전 서명 URL을 생성해요. 브라우저와 Workers에서 자연스럽게 동작해요.
출처: 문서
본문
JavaScript나 TypeScript 사용자는 평소처럼 aws4fetch ↗︎ npm 패키지를 계속 사용할 수 있어요. 이 패키지는 브라우저에서나 Cloudflare Workers에서 작업할 때 익숙한 fetch와 SubtleCrypto API를 사용해요.
S3 서비스 클라이언트를 만들 때 R2 구성 자격 증명을 반드시 전달해야 해요:
import { AwsClient } from "aws4fetch";
// Provide your Cloudflare account ID
const R2_URL = `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`;
const client = new AwsClient({
// Retrieve your S3 API credentials for your R2 bucket via API tokens (see: https://developers.cloudflare.com/r2/api/tokens)
accessKeyId: ACCESS_KEY_ID,
secretAccessKey: SECRET_ACCESS_KEY,
});
const ListBucketsResult = await client.fetch(R2_URL);
console.log(await ListBucketsResult.text());
// <ListAllMyBucketsResult>
// <Buckets>
// <Bucket>
// <CreationDate>2022-04-13T21:23:47.102Z</CreationDate>
// <Name>user-uploads</Name>
// </Bucket>
// <Bucket>
// <CreationDate>2022-05-07T02:46:49.218Z</CreationDate>
// <Name>my-bucket</Name>
// </Bucket>
// </Buckets>
// <Owner>
// <DisplayName>...</DisplayName>
// <ID>...</ID>
// </Owner>
// </ListAllMyBucketsResult>
const ListObjectsV2Result = await client.fetch(
`${R2_URL}/my-bucket?list-type=2`,
);
console.log(await ListObjectsV2Result.text());
// <ListBucketResult>
// <Name>my-bucket</Name>
// <Contents>
// <Key>cat.png</Key>
// <Size>751832</Size>
// <LastModified>2022-05-07T02:50:45.616Z</LastModified>
// <ETag>"c4da329b38467509049e615c11b0c48a"</ETag>
// <StorageClass>STANDARD</StorageClass>
// </Contents>
// <Contents>
// <Key>todos.txt</Key>
// <Size>278</Size>
// <LastModified> 2022-05-07T21:37:17.150Z</LastModified>
// <ETag>"29d911f495d1ba7cb3a4d7d15e63236a"</ETag>
// <StorageClass>STANDARD</StorageClass>
// </Contents>
// <IsTruncated>false</IsTruncated>
// <MaxKeys>1000</MaxKeys>
// <KeyCount>2</KeyCount>
// </ListBucketResult>
사전 서명 URL 생성 (Generate presigned URLs)
버킷에 대한 공개 읽기 또는 쓰기 접근 권한을 일시적으로 공유하는 데 사용할 수 있는 사전 서명 링크도 생성할 수 있어요.
import { AwsClient } from "aws4fetch";
const client = new AwsClient({
service: "s3", // Required by SDK but not used by R2
region: "auto", // Required by SDK but not used by R2
// Retrieve your S3 API credentials for your R2 bucket via API tokens (see: https://developers.cloudflare.com/r2/api/tokens)
accessKeyId: ACCESS_KEY_ID,
secretAccessKey: SECRET_ACCESS_KEY,
});
// Provide your Cloudflare account ID
const R2_URL = `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`;
// Use the `X-Amz-Expires` query param to determine how long the presigned link is valid.
console.log(
(
await client.sign(
new Request(`${R2_URL}/my-bucket/dog.png?X-Amz-Expires=${3600}`),
{
aws: { signQuery: true },
},
)
).url.toString(),
);
// You can also create links for operations such as PutObject to allow temporary write access to a specific key.
// Specify Content-Type header to restrict uploads to a specific file type.
console.log(
(
await client.sign(
new Request(`${R2_URL}/my-bucket/dog.png?X-Amz-Expires=${3600}`, {
method: "PUT",
headers: {
"Content-Type": "image/png",
},
}),
{
aws: { signQuery: true },
},
)
).url.toString(),
);
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Expires=3600&X-Amz-Date=<timestamp>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<credential>&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature>
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Expires=3600&X-Amz-Date=<timestamp>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<credential>&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=<signature>
PutObject 예시로 생성한 링크를 사용해서 사전 서명 링크가 만료되기 전까지 지정된 버킷과 키에 업로드할 수 있어요. Content-Type이 포함된 사전 서명 URL을 사용할 때는 클라이언트가 요청에 일치하는 Content-Type 헤더를 꼭 포함해야 해요.
curl -X PUT "https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Expires=3600&X-Amz-Date=<timestamp>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<credential>&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=<signature>" \
-H "Content-Type: image/png" \
--data-binary @dog.png
CORS와 Content-Type으로 업로드 제한하기 (Restrict uploads with CORS and Content-Type)
업로드용 사전 서명 URL을 생성할 때 다음 방법으로 남용과 오용을 제한할 수 있어요:
-
Content-Type 제한: 서명할 때 요청에
Content-Type헤더를 지정해요. 클라이언트가 다른Content-Type헤더를 보내면 업로드는 실패해요. -
CORS 구성: 버킷에 CORS 규칙을 설정해 어떤 오리진이 파일을 업로드할 수 있는지 제어해요. Cloudflare 대시보드 ↗︎에서 버킷 설정에 JSON 정책을 추가해 CORS를 구성할 수 있어요:
[
{
"AllowedOrigins": ["https://example.com"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
그다음 Content-Type 제한이 적용된 사전 서명 URL을 생성해요:
const signedRequest = await client.sign(
new Request(`${R2_URL}/my-bucket/user-upload.png?X-Amz-Expires=${3600}`, {
method: "PUT",
headers: {
"Content-Type": "image/png",
},
}),
{
aws: { signQuery: true },
},
);
const putUrl = signedRequest.url.toString();
클라이언트가 이 사전 서명 URL을 사용할 때는 반드시 다음을 지켜야 해요:
-
허용된 오리진에서 요청할 것 (CORS로 강제됨)
-
Content-Type: image/png헤더를 포함할 것 (서명으로 강제됨)