프로젝트에서 S3에서 받아온 이미지 URL을 파일로 변환해서 서버로 보내야 하는 경우가 있었다. 파일 변환을 위해 S3 URL에 GET 요청을 해서 blob 타입으로 응답을 받아와야 했는데 여기서 CORS 오류가 발생했다.
요청 코드는 다음과 같다.
import axios from 'axios';
export const convertURLtoFile = async (url: string) => {
try {
const response = await axios.get(url, {
responseType: 'blob',
withCredentials: true,
});
const blob = response.data;
return new File([blob], 'profile-image', { type: blob.type });
} catch (error) {
console.log('파일 변환 실패 : ', error);
throw error;
}
};
img 태그 등에서 사용하기 위해 브라우저가 S3에 요청하는 것이 아닌 직접 get 요청을 하는 경우에는 S3에서 CORS 설정을 해줘야 한다. AllowOrigin에 도메인을 추가해 주면 된다.
이걸로 해결이 되는 듯 했는데... 서비스 배포 후에 다시 문제가 발생했다.
프로필을 수정할 때 이미지가 없으면 new Blob()을, 사용자가 PC에서 새로 업로드 한 파일이 있는 경우 해당 File을, 기존 이미지가 존재하며 이미지를 제외한 다른 정보 수정이 있을 경우엔 해당 이미지 url(s3 url)을 File로 변환해서 file 필드에 보내고 있었다. 그런데 이미지를 처음 세팅하고 금방 다시 이미지를 제외한 수정을 하여 url을 File로 변환할 때는 정상적으로 잘 되는데 시간이 좀 지난 후에 url -> File 변환 시에 다시 CORS 오류가 뜨는 것이다.
이 문제가 참 어려웠던 점이, 정확히 어떤 경우에 CORS 오류가 뜨는지를 파악하는 게 어려웠다. 시간이 얼마나 지나야 하는지, 어떤 플로우로 요청을 해야 하는지 등 확실한 부분이 없었다. 나의 경우 이미지를 세팅한 후 다른 페이지로 이동하고 1분 정도 지난 후 다시 프로필 페이지로 돌아와 이미지를 제외한 요청(url -> File 변환 요청)을 할 때 CORS가 발생했다. 또한 나는 로컬, develop 배포 환경, production 배포 환경에서 모두 같은 오류를 겪었는데 다른 팀원들은 같은 플로우로 테스트해도 오류가 나는 환경이 다르거나 아예 오류가 나지 않는다는 팀원도 있었다.
클라이언트 문제인지 서버 문제인지 조차 파악하기가 어렵던 와중, 브라우저 캐시 때문일지도 모르겠다는 생각이 들었다. S3와 캐시를 조합해서 찾아보니 S3에서 제공하는 서명된 URL을 사용할 때 응답이 캐시될 수 있다고 한다. 이는 CloudFront와 같은 CDN을 사용하거나 브라우저 자체의 캐싱 정책에 의해 발생할 수 있다고. 브라우저 캐시 테스트를 위해 Cache-Control 헤더를 설정해 보기로 했다.
기존 convertURLtoFile의 axios 요청에 아래 헤더를 추가했다.
const response = await axios.get(url, {
responseType: 'blob',
withCredentials: true,
headers: {
'Cache-Control': 'no-cache',
},
});
이렇게 하니까 더이상 오류가 발생하지 않았다!!! no-cache 헤더를 추가하면 브라우저는 캐시된 데이터가 있더라도 항상 서버에 확인 요청을 보내 데이터가 변경되었는지 확인하게 된다.
캐시가 된 것이 왜 문제가 되었을까? 생각해 보았다. S3 이미지로 요청이 이루어지는 모든 경우 요청, 응답 헤더를 비교해 봤다.
img 태그로 인한 S3 URL 요청, 응답
응답 헤더에 CORS 관련 내용이 없고, 요청 헤더에 Origin이 없다. 이는 브라우저가 img 태그와 같은 미디어 태그를 통해 리소스를 요청할 때는 CORS를 제한하지 않기 때문이다.
파일 변환을 위해 직접 S3 URL에 GET 요청한 경우 요청, 응답
응답 헤더에 CORS 관련 설정이 있으며, 요청 헤더에도 Origin이 포함되어 있다.
CORS가 뜬 요청, 응답 헤더
응답 헤더도 없고, 요청 헤더도 임시 헤더가 표시된다고 뜬다. 자세히 알아보기를 눌러서 자세한 내용을 확인해봤다.
요청이 서버로 전송되지 않았으며, 요청 헤더는 로컬 캐시에서 제공된 것이다.
프로필 수정을 위해 들어간 프로필 페이지에서 이미 현재 사용자 정보를 조회할 때 img url이 img 태그에 포함되어 있었다. 하지만 아까 말했듯이 브라우저는 img 태그를 통한 리소스를 요청에는 CORS를 제한하지 않는다. 이때 브라우저의 로컬 캐시가 활성화 되어있다면, 이 img 태그에서 사용한 CORS 헤더가 없는 요청, 응답을 브라우저가 로컬 캐시로 재사용하여 문제가 될 수 있는 것이다.
설정해 두었던 cache-control 설정을 잠시 제거하니 다시 CORS 오류가 떴는데 네트워크 탭에서 캐시 사용 중지를 설정하니 바로 성공했다...ㅠㅠ 캐시는 불필요한 트래픽을 막고 빠른 데이터 제공을 가능하게 하지만 때로는 이렇게 예기치 않은 오류를 발생시킬 수 있기 때문에 어떤 경우에 캐시를 사용하고 사용하지 않을지 전략을 잘 세워야 하는 것 같다.
참고
'Study > TIL · 오류해결' 카테고리의 다른 글
[TIL] 프론트엔드, 백엔드 도메인이 다를 때 쿠키 설정 (0) | 2024.09.12 |
---|---|
vite.config.js에서 환경 변수 사용하기 (0) | 2024.09.05 |
[Next.js] 서버 컴포넌트 try-catch에서 redirect가 안 된다 (0) | 2024.08.16 |
[TypeScript/Jest] jest.setup.js를 설정했는데도 jest-dom 타입 오류가 뜨는 문제 (0) | 2024.08.14 |
[Firebase/오류해결] Firebase Hosting with Github Actions (env 오류) (1) | 2024.05.23 |