Project

[중간이들] AWS S3 presigned-url content type 불일치 오류 해결

꼽파 2024. 10. 4. 10:57

현재 프로젝트에는 S3 버킷에 파일을 업로드하는 기능을 구현하려고 한다.
회원가입 시 인증서류 이미지 업로드, 글쓰기 에디터 이미지 업로드 등 다양한 곳에서 사용된다.

 

프론트엔드 측에서는 서버에서 presigned url을 받아온 다음 그 URL을 엔드포인트로 하여 formdata의 정보들을 request body로 담아서 보내야 한다.

 

로컬에서 이 부분을 만들어서 직접 구현하고 있는데, 다음과 같은 에러가 났다.

 

 

서버에서 presigned url을 받아오는 부분은 문제가 없으나, S3 버킷에 직접 업로드할 때는 에러가 발생하였다.

 

AWS S3에서 던져준 에러는 아래와 같다.

"Policy Condition failed: ["starts-with", "$Content-Type", "image/png"]"

이 에러는 해당 조건을 위반했다는 의미이다.

Content-Type이 image/png로 시작하지 않는다는 것이었다.

 

서버에서는 제약사항 중 Content-Type이 해당 파일의 타입으로 시작해야한다는 사항을 추가하였다.

 

이 부분을 주석처리하여 'starts-with' 제약사항을 해제해보았다.

이렇게 하면 프론트엔드에서 업로드가 잘 되었다.

 

하지만 생성된 URL을 클릭하면 브라우저에 렌더링되는게 아니라 클릭하자마자 해당 파일이 다운로드가 되었다.

뭔가 이상함을 감지하였다.

 

기본적으로 formData를 생성하고, 그 안에 file을 추가하면 Content-Type이 자동으로 생성되는게 아닌가 싶었다.

하지만 지금 상황에서 Content-Type이 일치하지 않는다는 에러가 발생한 것을 보면 아니었다.

 

짚이는 원인은 다음과 같다.

1) Content-Type이 'image/png'와 같은 형식으로 예상한 형태로 들어가지 있지 않음.

2) Content-Type이 formData 안에 없음.

 

1)은 프론트엔드에서 생성된 파일의 type을 직접 console로 출력해보면 알 수 있다.

2)도 마찬가지로 S3 버킷에 요청을 보내기 직전 생성한 formData에 어떤 것들이 있는지 console로 출력하면 확인할 수 있다.


1) 업로드한 이미지 파일의 타입 확인하기 

file을 확인해보면 'type'안에 "image/png"라고 잘 적혀있는 것을 볼 수 있다. 이것이 원인이 아니었다.


2) FormData에 Content-Type이 있는지 확인

일단 console.log로 출력해보았는데, 이렇게 하면 빈 객체가 나온다.

당황스러워서 찾아보니 formData는 iterable 객체라서 for...of로 출력해야 확인할 수 있다고 하였다.

 

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects

 

Using FormData Objects - Web APIs | MDN

The FormData object lets you compile a set of key/value pairs to send using the Fetch or XMLHttpRequest API. It is primarily intended for use in sending form data, but can be used independently from forms in order to transmit keyed data. The transmitted da

developer.mozilla.org

 

https://se9round.dev/post/FormData-%EA%B0%92-console.log%EB%A1%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0

 

이렇게 for...of 반복문으로 key, value를 구분하여 출력을 해주었다.

for (let [key, value] of formData.entries()) {
  console.log(key, value);
}

 

그랬더니 formData에 있는 key와 value들이 하나씩 출력되기 시작하였다.

 

확인해본 결과 'Content-Type'이라는 key가 별도로 없었다. 그래서 이를 인식하지 못하여 오류가 났던 것이다.

append 메소드로 직접 명시적으로 추가해주었다.

formData.append('Content-Type', file.type)

 

추가해주고 formData를 출력해보면 이렇게 Content-Type이 잘 들어가 있다.

 

한 가지 의문인 점은 이 파일 타입을 formData에 넣어줄 때, formData를 생성한 직후 가장 먼저 넣어주어야 오류가 나지 않았다는 것이다.

이 이유는 자세히 분석해보지 않았다.

다른 값들을 넣어주기 전에 가장 먼저 넣어야 잘 작동했고, 해당 file을 append하고 나서 넣어주면 Content-Type이 명시적으로 formData에 추가되지 않았다.

 

코드를 수정하였더니 파일 업로드가 예상한대로 잘 되었다.

https://github.com/devellybutton/S3_Bucket_Image_Uploader

 

GitHub - devellybutton/S3_Bucket_Image_Uploader: Simple image uploader built with Vanilla JavaScript to upload images directly t

Simple image uploader built with Vanilla JavaScript to upload images directly to an Amazon S3 bucket - devellybutton/S3_Bucket_Image_Uploader

github.com


formData를 POST Request로 보낼 때는 Content-Type을 명시적으로 쓰면 안 됨.

mdn 사이트의 스크롤을 쭉 내리다보니까 다음과 같은 내용이 나왔다.

브라우저에서 생성한 boundary expression이 포함되지 않기 때문에 수동으로 Content-Type을 설정하면 서버가 올바르게 파싱하지 못한다는 말이다.

// 틀린 예시
const uploadData = await fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'multipart/form-data', // 끝장남.
  },
  body: formData,
});

// 올바른 예시
const uploadData = await fetch(url, {
  method: 'POST',
  body: formData,
});

 

728x90