동영상 업로드 기능이 있는 프로젝트를 진행하면서, 어떻게 하면 서버에 부담이 덜 가면서 파일 업로드를 할 수 있을까 고민했다.🤔
아래 사진은 원래 사용하려고 했던 multer를 이용한 파일 업로드이다.
파일이 브라우저 -> 서버 -> S3순으로 전달되며, 서버는 중간 다리 역할만 하고 있다.
상당히 비효율적이다🤣
그래서 이것 저것 찾다가 발견한 PRESIGNED URL😆을 사용해보기로 했다.
S3 버켓에 이미지를 업로드 하기 위해서는 해당 S3에 대한 접근 권한을 인증해야 한다. 접근 권한에 대한 인증을 마치면 S3에 업로드 할 수 있는 URL을 발급해 주는데, 이 URL을 preSigned(pre-signed) URL라고 한다.
- 이미지 업로드 요청 시 서버 api 호출
- S3 접근 권한을 가진 서버가 AWS S3에 preSignedURL 요청
- AWS에서 preSignedURL을 return
- 서버는 브라우저로 preSignedURL을 전달
- 브라우저에서 AWS preSignedURL로 이미지 upload
- 서버에게 해당 요청이 종료 되었음을 알림
aws-sdk
를 사용한다.$ npm install aws-sdk
import { Body, Controller, HttpException, Post } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ApiTags } from '@nestjs/swagger';
import AWS from 'aws-sdk';
import { v4 as uuid } from 'uuid';
import { IFileSignedUrl } from './fileSignedUrl.interface';
const AWS_S3_BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME;
const s3 = new AWS.S3({ useAccelerateEndpoint: true });
AWS.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
});
@ApiTags('Product')
@Controller('products')
export class ProductController {
private imageS3Bucket: string;
constructor() {}
@Post('/signedUrl')
async getSignedUrlForProductImage(
@Body() { contentType, filePath }: IFileSignedUrl
): Promise<{
fileName: string;
s3Url: string;
}> {
if (!contentType) {
throw new HttpException('Missing contentType', 400);
}
if (!filePath) {
throw new HttpException('Missing filePath', 400);
}
const filetype: string = contentType.split('/')[1];
// Rename file, I just want to show there is a way to rename file before you it into S3
// Renaming file might be necessary for SEO
const fileName: string = `${uuid()}.${filetype}`;
const params = {
Bucket: AWS_S3_BUCKET_NAME,
Key: fileName,
Expires: 3600,
ContentType: contentType,
ACL: 'public-read',
};
const s3Url = await s3.getSignedUrlPromise('putObject', params);
return {
fileName,
s3Url,
};
}
}