[S3] PRESIGNED URL

Hocaron·2021년 12월 15일
3

DevOps

목록 보기
2/4

구글링은 최고야

동영상 업로드 기능이 있는 프로젝트를 진행하면서, 어떻게 하면 서버에 부담이 덜 가면서 파일 업로드를 할 수 있을까 고민했다.🤔
아래 사진은 원래 사용하려고 했던 multer를 이용한 파일 업로드이다.
파일이 브라우저 -> 서버 -> S3순으로 전달되며, 서버는 중간 다리 역할만 하고 있다.
상당히 비효율적이다🤣

그래서 이것 저것 찾다가 발견한 PRESIGNED URL😆을 사용해보기로 했다.

PRESIGNED URL이란?

S3 버켓에 이미지를 업로드 하기 위해서는 해당 S3에 대한 접근 권한을 인증해야 한다. 접근 권한에 대한 인증을 마치면 S3에 업로드 할 수 있는 URL을 발급해 주는데, 이 URL을 preSigned(pre-signed) URL라고 한다.

어떻게 진행될까?

  1. 이미지 업로드 요청 시 서버 api 호출
  2. S3 접근 권한을 가진 서버가 AWS S3에 preSignedURL 요청
  3. AWS에서 preSignedURL을 return
  4. 서버는 브라우저로 preSignedURL을 전달
  5. 브라우저에서 AWS preSignedURL로 이미지 upload
  6. 서버에게 해당 요청이 종료 되었음을 알림

어떻게 구현할까?

  • AWS 자격증명에는 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,
    };
  }
}

References

profile
기록을 통한 성장을

0개의 댓글