ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Presigned URL을 이용하여 이미지 업로드할 때 용량 제한하기
    AWS/S3 2022. 4. 20. 17:41

    백엔드(NestJS)

    import AWS from 'aws-sdk';
    import { BadRequestException, Inject, Injectable } from '@nestjs/common';
    import { ConfigType } from '@nestjs/config';
    
    import { GetPreSignedUrlQueryDTO, GetPreSignedUrlResDTO } from '@dtos';
    import AwsConfig from '@config/variables/aws.config';
    
    @Injectable()
    export class UploadService {
      constructor(
        @Inject(AwsConfig.KEY)
        private readonly awsConfig: ConfigType<typeof AwsConfig>
      ) {}
    
      private readonly s3 = new AWS.S3({
        accessKeyId: this.awsConfig.accessKey,
        secretAccessKey: this.awsConfig.secretAccessKey,
        region: this.awsConfig.region,
        signatureVersion: 'v4'
      });
    
      private readonly bucket = this.awsConfig.s3Bucket;
    
      async getPreSignedUrl(
        input: GetPreSignedUrlQueryDTO
      ): Promise<GetPreSignedUrlResDTO> {
        try {
          const { fileName } = input;
    
          const params = {
            Bucket: this.bucket,
            Conditions: [
              { bucket: this.bucket },
              ['content-length-range', 1048576, 10485760]
            ],
            Expires: 60 * 30,
            Fields: {
              key: fileName
            }
          };
    
          const { fields, url } = await this.s3.createPresignedPost(params);
    
          return {
            url,
            fields
          };
        } catch (err) {
          throw new BadRequestException(err);
        }
      }
    }

    여기서 이용할 method는 new AWS.S3().createPresignedPost() 다. AWS 설정 값을 주고 Conditions['content-length-range', 1048576, 10485760] 를 옵션을 준다. 두 번째 인자에 파일의 Minimum 크기, 세 번째 인자에 Maximum 크기를 준다.

    await this.s3.createePresignedPost(params)를 요청하면 fields와 url 값을 받는데, 이 값을 프론트에게 reponse로 전달한다.

    프론트엔드(React)

    export const Form = () => {
      const [selectedFile, setSelectedFile] = useState<File>(null);
    
      const uploadService = new UploadService();
    
      const onSubmit = async (e) => {
        const fileNameSplit = selectedFile.name.split('.');
        const fileExt = fileNameSplit.pop();
    
        const processedFileName = getS3KeyPath({
          intialFolder: 'initial',
          id: 33,
          fileExt
        });
    
        const { url, fields } = await uploadService.getPreSignedUrl({
          fileName: processedFileName
        });
    
        try {
          await uploadService.uploadToS3({ url, fields }, { file: selectedFile });
        } catch (error) {
          console.error(error);
          alert('이미지 크기는 최대 10mb 이하입니다.');
          throw new Error('이미지 크기는 최대 10mb 이하입니다.');
        }
      };
      
      const handleFileInput: React.ChangeEventHandler<HTMLInputElement> = (e) => {
        const files = e.target.files;
        setSelectedFile(files[0]);
      }
    
      return <input type="file" className="input" onChange={handleFileInput} />
    }

    여기서는 다 무시하고 봐야할 것은 uploadService 부분의 getPreSignedUrl 메소드와 uploadToS3 메소드다. getPreSignedUrl()로 서버로부터 url과 fiels 값을 받는다. 해당 값과 uploadToS3()를 이용해서 s3에 파일을 업로드한다.

    uploadToS3 코드

     async uploadToS3(
        { url, fields }: UploadToS3UrlDTO,
        { file }: UploadToS3BodyDTO
      ): Promise<void> {
        const form = new FormData();
        Object.entries(fields).forEach(([field, value]) => {
          form.append(field, value);
        });
        form.append('file', file);
    
        await axios.post<any, AxiosResponse<any>, FormData>(url, form);
      }

    fiels는 배열 값인데, 값을 줄 때 POST method를 이용하고 form data를 이용해야 한다. 특히 주의할 점은 반드시 key는 file로 파일을 줘야 하고 fields에 있는 값들을 다 form-data에 추가한 후 맨 마지막에 file을 추가해야 한다.
        그렇지 않으면 AWS S3에서 에러를 일으킨다.

    댓글

Designed by Tistory.