영원히 흘러가는 강

S3 presigned url을 통한 사진 업로드 react+drf 2 본문

카테고리 없음

S3 presigned url을 통한 사진 업로드 react+drf 2

double_R_one_G 2024. 6. 27. 15:25
728x90
내가 하고싶은것!

- react-quill을 이용하여 이미지 등록시에 presigned url을 이용하여 이미지를 미리 등록한다.
- react-quill value에는 위에 등록한 주소를 넣어 하나의 필드로 저장한다.

 

 

presigned url을 이용하는 방법에는 

1. 프론트엔드에서만 이미지 업로드
2. 프론트엔드 ,백엔드 통신 이후 프론트에서 이미지 업로드


첫번째 방법으로는 백엔드를 거치지 않고 프론트엔드에서만 활용하기에 장점만큼 단점이 크다.

장점으로는 당연히 편리하다. (서버를 고려하지 않아도 되므로)

단점으로는 보안에 매우매우 취약하다 (aws 과금 당해서 돈내기 싫다...)


두번째 방법으로는 백엔드에 파일을 보내서 presigned url을 받아오고 프론트에서 그 주소로 파일을 올리는 방법이다.

장점으로는 보안이 좋다 (맘먹으면 뭐든 다 해킹 가능하지만 Access key,secret key 정도는 서버가 알면 그나마..?)

단점으로는 이미지 파일 하나가 욜량이 크면 그만큼 서버가 부담이 된다 => 느리다


하지만 나는 과금당하기 싫기에... ㅎ

두번째 방법을 선택했다

 

순서대로 해야할일을 나열해본다면

1. 프론트에서 백엔드로 파일을 보내기
2. 백엔드에서 파일을 s3에 presigned url post 요청후 받아온 url을 프론트에 전달
3. 프론트는 url을 받아 s3에 해당 url에 파일을 put으로 저장하기


1번 부터 진행해보자

 

우선 나는 react-quill을 이용하고 있고,

imageHandler가 실행이 된다면 이 이미지를 백엔드로 보낼꺼다.

 

이전에 작성했던 글에서 마지막부분에 작성했던 사항과 동일

 

1. 프론트에서 백엔드로 파일을 보내기

 //1번에서 본 코드와 동일

const imageHandler = async () => {
      const input = document.createElement("input");
      input.setAttribute("type", "file");
      input.setAttribute("accept", "image/*");
      input.click();

      input.onchange = async () => {
        const files = input.files;
        if (files && files.length > 0) {
          const file = files[0];
          if (file) {
            try {
              const uploadeUrl = await handlePresignedUrl(file);  // 해당 부분
              if (!uploadeUrl) {
                throw new Error("이미지 업로드 URL을 가져올 수 없습니다.");
              }
              const uploadedUrl = await handleImageUpload(uploadeUrl, file); //3번코드
              const quill = quillRef?.current?.getEditor();
              if (quill) {
                const range = quill.getSelection(true);
                quill.insertEmbed(range.index, "image", uploadedUrl);
                quill.setSelection(range.index + 1);
              }
            } catch (error) {
              if (error instanceof Error) {
                alert(error?.message);
              }
            }
          }
        }
      };
    };

 

 

서버로의 파일을 formData로 전송한다. 

이렇게되면 위의 1번 프론트에서 백엔드로 파일을 보내기 완료!

 const handlePresignedUrl = async (file: File) => {

      const formData = new FormData();
      formData.append("file", file);

      const url = `${process.env.NEXT_PUBLIC_API_URL}/presigned-url/`;
      try {
        const res = await fetch(url, {
          method: "post",
          body: formData,
        });
        const response = await res.json();
        return response?.presigned_url; // 파일 위치를 담은 url을 받아온다
      } catch (error) {
        if (error instanceof Error) {
          alert(error?.message);
        }
      }
    };

 

 

2. 백엔드에서 파일을 s3에 presigned url post 요청 후 받아온 url을 프론트에 전달


서버는 받아온 파일을 s3를 이용하여 presigned_post를 진행한다.!

유의할점은 file_name이 유니크 하지 않아서 오류가 발생하였었다

그리하여 uuid를 활용하여 추가 진행

class PresignedURLView(APIView):
    def post(self,request):
        file = request.FILES.get('file')
        if file is None:
            return Response({'message':'NO file uploaded'},status=status.HTTP_400_BAD_REQUEST)
        s3_client = boto3.client('s3',aws_access_key_id=settings.AWS_ACCESS_KEY_ID,aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,region_name=settings.AWS_S3_REGION_NAME)
        file_uuid = uuid.uuid4()
        file_name = f"{file_uuid}_{file.name}"
        try:
            presigned_post = s3_client.generate_presigned_post(
                Bucket=settings.AWS_STORAGE_BUCKET_NAME,
                Key=file_name,
                Fields={"acl": "public-read"},
                Conditions=[
                    {"acl": "public-read"},["starts-with", "$Content-Type", "image/"],
                ],
                ExpiresIn=3600 
            )
            full_url = f"{presigned_post['url']}{presigned_post['fields']['key']}"
            return Response({
                'presigned_url':full_url ,
                'fields': presigned_post['fields']
            },status=status.HTTP_200_OK)

        except Exception as e:
            return Response({'message':str(e)},status=status.HTTP_500_INTERNAL_SERVER_ERROR)

 

 

 

3. 프론트는 url을 받아 s3에 해당 url에 파일을 put으로 저장하기

 

imageHandler에 작성한 부분에서 살펴보자


함수는 받아온 url에 파일을 put 메소드로 전달한다!

 

이때에 file을 그냥 body에 담아서 보내도된다.

formData로 보내겠다고 했다가 계속해서 오류를 맛본...ㅎ

 

const uploadedUrl = await handleImageUpload(uploadeUrl, file);

 

  const handleImageUpload = async (uploadeUrl: string, file: File) => {
      const res = await fetch(uploadeUrl, {
        method: "put",
        body: file,
        headers: {
          "Content-Type": file.type,
        },
      });
      return res?.url;
    };

 

 

이미지가 정상적으로 quill에 출력되면 완료!!!

중간중간 오류도 많았다....

S3 access denied 오류로 인해 

 


1. 버킷정책 확인

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Principal": "*",
			"Action": [
				"s3:GetObject",
				"s3:PutObject"
			],
			"Resource": "arn:aws:s3:::<버킷이름>/*"
		}
	]
}

 

 

2  CORS 편집

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "http://localhost:3000"
        ],
        "ExposeHeaders": []
    }
]

 

 

3  iam 권한 확인

프로젝트성이기에 사용자에게  AmazonS3FullAccess 권한도 부여하였다...

근데 보통 코드문제였었다..ㅎㅎㅎ

 

 

 

이상 react-quill로 이미지 s3에 presigned url로 저장후 저장 위치를 가져오는 코드와 내 삽질의 기록이다...

개발자는 자존감을 먹고 자란다지만 내 자존감 지켜줘...

728x90
Comments