<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cloudflare R2 on 얼쑤 · Team Edo</title><link>https://team-edo.github.io/tags/cloudflare-r2/</link><description>Recent content in Cloudflare R2 on 얼쑤 · Team Edo</description><generator>Hugo</generator><language>ko-kr</language><lastBuildDate>Tue, 03 Nov 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://team-edo.github.io/tags/cloudflare-r2/index.xml" rel="self" type="application/rss+xml"/><item><title>206 응답 뒤에 숨은 CORS 오류</title><link>https://team-edo.github.io/posts/2026/11/cors-after-audio-redirect/</link><pubDate>Tue, 03 Nov 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/11/cors-after-audio-redirect/</guid><description>&lt;p>얼쑤의 모범 연주 음원 API를 Swagger에서 확인하다가 &lt;code>Failed to fetch&lt;/code>를 만났습니다. 같은 음원의 처음 2바이트를 &lt;code>curl&lt;/code>로 요청하니 저장소는 HTTP 206을 반환했습니다. 저장소가 바이트를 보내고도 브라우저에서 읽을 수 없는 이유를 찾으려면, 처음 요청한 API와 음원을 실제로 돌려주는 서버를 나눠 봐야 했습니다.&lt;/p>
&lt;h2 id="302-뒤의-응답을-확인했다">302 이후의 응답 추적&lt;/h2>
&lt;p>Swagger 페이지와 음원 API는 같은 출처에 있었습니다. API는 곡과 음원의 게시 상태를 확인한 뒤 HTTP 302의 &lt;code>Location&lt;/code> 헤더에 R2 서명 URL을 담았습니다. 브라우저가 리다이렉트를 따라가면 음원 본문은 다른 출처인 R2에서 내려옵니다. 그 응답에는 Swagger 페이지의 출처를 허용하는 &lt;code>Access-Control-Allow-Origin&lt;/code>이 없었습니다. 브라우저는 저장소 응답을 받았지만 페이지의 자바스크립트에 넘기지 않았습니다. &lt;a href="https://fetch.spec.whatwg.org/#cors-check">Fetch 표준의 CORS 검사&lt;/a>&lt;/p></description></item><item><title>업로드 재시도에서 기존 파일 지키기</title><link>https://team-edo.github.io/posts/2026/11/retry-safe-object-upload-conditional-put/</link><pubDate>Tue, 03 Nov 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/11/retry-safe-object-upload-conditional-put/</guid><description>&lt;p>팀에서 음원 파일이나 폴더를 R2에 올리는 공용 명령을 만들었습니다. 폴더를 올리다 중간에 연결이 끊기면 같은 명령을 다시 실행하고 싶습니다. 그런데 같은 키에 일반 PUT을 보내면, 이미 올라간 파일이 지금 로컬에 있는 내용으로 바뀔 수 있습니다. 기본 실행은 빈 키만 채우고, 기존 파일을 바꿀 때는 교체 옵션을 명시하도록 했습니다.&lt;/p>
&lt;h2 id="쓰기-요청에-빈-키라는-조건을-넣었다">비어 있는 키에만 쓰기&lt;/h2>
&lt;p>먼저 HEAD로 키가 비어 있는지 확인하고 뒤이어 PUT을 보내면, 두 요청 사이에 다른 작업자가 같은 키에 파일을 올릴 수 있습니다. 저는 기본 PUT에 &lt;code>If-None-Match: *&lt;/code>를 넣었습니다. R2는 이 조건을 객체 쓰기와 함께 판단합니다. 키가 이미 있으면 &lt;code>412 PreconditionFailed&lt;/code>가 돌아오고, 도구는 그 파일을 건너뛰었다고 표시한 뒤 다음 파일로 넘어갑니다. &lt;code>--overwrite&lt;/code>를 준 경우에는 이 조건을 붙이지 않습니다. &lt;a href="https://developers.cloudflare.com/r2/api/s3/extensions/#conditional-operations-in-putobject">Cloudflare R2의 조건부 PUT 문서&lt;/a>&lt;/p></description></item><item><title>녹음 저장소를 바꿔도 지킨 15분 읽기 권한</title><link>https://team-edo.github.io/posts/2026/10/recording-storage-signed-read/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/recording-storage-signed-read/</guid><description>&lt;p>얼쑤에서 연습 녹음을 받으면 백엔드는 원본을 객체 저장소에 보관하고, 분석 서버는 필요한 파일을 가져와 처리합니다. 처음에는 서울 리전과 신규 계정 크레딧을 고려해 Google Cloud Storage(GCS)를 쓰는 설계를 잡았습니다. 다음 날에는 크레딧이 끝난 뒤의 비용과 다운로드 조건, 저장소를 바꿀 때의 구현 부담을 다시 검토해 S3 호환 API를 쓰는 Cloudflare R2로 계획을 바꿨습니다.&lt;/p>
&lt;h2 id="분석-서버에는-15분-동안-한-파일을-읽을-권한만-준다">한 파일에 15분 읽기 권한&lt;/h2>
&lt;p>분석 서버가 해야 할 일은 전달받은 녹음 한 건을 읽는 일이었습니다. 그래서 백엔드가 특정 객체에 대해 &lt;code>GET&lt;/code> 요청만 &lt;strong>15분(900초)&lt;/strong> 동안 허용하는 URL을 서명하고, 분석 서버는 그 주소로 원본을 내려받도록 했습니다. 저장소의 자격 증명은 서명하는 백엔드에만 둡니다. 유효한 URL을 가진 사람도 파일을 읽을 수 있으므로 주소 자체를 노출하지 않는 것도 이 설계의 일부입니다. &lt;a href="https://docs.cloud.google.com/storage/docs/access-control/signed-urls">Google Cloud Storage 서명 URL&lt;/a> · &lt;a href="https://developers.cloudflare.com/r2/api/s3/presigned-urls/">Cloudflare R2 서명 URL&lt;/a>&lt;/p></description></item></channel></rss>