-
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에서 에러를 일으킨다.