똑같은 삽질은 2번 하지 말자

AWS S3 Presigned URL 본문

카테고리 없음

AWS S3 Presigned URL

곽빵 2022. 2. 28. 21:39

개요

presigned URL 이란?

presigned URL 은 왜 필요할까?

presigned URL 이용해 presigned POST를 해보자

 

Presigned URL 이란

Presigned URL 이란 AWS 자원에 대한 접근 권한을 제공하기 위해서 사용되는 이름 그대로 사전에 적절한 권한을 가진 자격증명에 의하여 Signed 된 URL 을 말합니다.

 

Presigned URL 왜 필요할까?

 

앱에서 이미지가 저장되는 과정을 살펴보자

1. Client에서 이미지를 업로드한다는 request를 날린다.

2. Server에서는 이미지 업로드를 하기위해 DB와 S3에 각각 이미지에 대한 정보를 업로드한다.

3. DB, S3에 저장이 에러없이 끝나면 프론트에 response 200을 날린다.

4. 프론트에서는 서버에서 200 응답을 받으면 방금 업로드한 이미지를 표시한다.

 

위의 과정에서 문제점이 있다.

  • 2번째 과정이 행해지는 동안 프론트에서는 작업이 어느정도 진행됬는지 알 수가 없다.
  • 너무 느리다.

이걸 해결하기 위해 Pre-siged URL이 나왔다.

Pre-signed URL을 서버에서 받고난뒤 일정시간 동안은 프론트에서 바로 S3에 업로드(post)를 할 수 있다.

 

Presigned URL 이용해 Presigned POST를 해보자

server(nodejs)

const getSignedUrl = (key) => {
  return new Promise((resovle, reject) => {
    s3.createPresignedPost(
      {
        Bucket: "bucket이름",
        Fields: {
          key, // presignedUrl안에 Fields라는 객체 속에 이미지명으로 사용할 key를 넣기위해
        },
        Expires: 300,
        Conditions: [
          ["content-length-range", 0, 50 * 1000 * 1000], // 0 ~ 50MB
          ["starts-with", "$Content-Type", "image/"], // only image
        ],
      },
      (err, data) => { // 두번째 인자로 콜백함수 성공,실패제어
        if (err) reject(err);
        else resovle(data);
        return;
      }
    );
  });
};
imageRouter.post("/presigned", async (req, res) => {
  try {
    const { contentTypes } = req.body;
    if (!Array.isArray(contentTypes)) throw new Error("invalid contentTypes");
    const presignedData = await Promise.all(
      contentTypes.map(async (contentType) => {
        const imageKey = `${uuid()}.${mime.extension(contentType)}`;
        const key = `raw/${imageKey}`; // 아까 Fields에 넣을 key생성
        const presigned = await getSignedUrl(key);
        return { imageKey, presigned };
      })
    );
    return res.json(presignedData);
  } catch (err) {
    console.log(err);
  }
});

return 한 presigned는 대충 이렇게 생겼다.

front(reactjs)

const onSubmitV2 = async (e) => {
    e.preventDefault();
    try {
      const presignedData = await axios.post(
        "http://서버루트주소/images/presigned",
        { // contentTypes로 업로드할 모든 파일의 contentType을 보내 이미지인지 체크
          contentTypes: [...files].map((file) => file.type),
        }
      );

      const result = await Promise.all(
        [...files].map((file, index) => {
          const { presigned } = presignedData.data[index];
          const formData = new FormData();
          for (const key in presigned.fields) { // presigned의 fields를 모두 formData 추가
            formData.append(key, presigned.fields[key]);
          }
          formData.append("Content-Type", file.type);
          formData.append("file", file);
          return axios.post(presigned.url, formData);
        })
      );
    } catch (err) {
      console.error(err);
    }
  };

 

도중에 CORS에러가 날 때 체크해야할 항목

1. 해당 버킷의 허용 origins확인

 

2. 서버에서 s3 aws-sdk로 S3 인스턴스 생성할 때 region 설정은 제대로 했는지

const s3 = new aws.S3({
  secretAccessKey: AWS_SECRET_KEY,
  accessKeyId: AWS_ACCESS_KEY,
  region: "ap-northeast-1",
});
Comments