AWS/S3
Presigned URL을 이용하여 이미지 업로드할 때 용량 제한하기
Cog Factory
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에서 에러를 일으킨다.