Today I learned
이번 작업은 내가 원하는 위치에서 AWS S3에 file을 업로드 하는 것이 목표이다
작업을 진행하기 전에 사용할 패키지를 다운받자
npm install aws-sdk
지금 테스트는 aws-sdk만 이용해서 작업을 진행해보자
그리고 image.service.ts 파일로 가보자
// import 생략
import { S3 } from 'aws-sdk';
@Injectable()
export class ImageService {
constructor(private configService: ConfigService) {}
bucketName = this.configService.get('AWS_BUCKET_NAME');
s3 = new S3({
accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'),
});
async uploadImage(userId: string, file: Express.Multer.File) {
if (!file) {
throw new BadRequestException('이미지가 존재하지 않습니다.');
}
await this.s3
.upload({
Bucket: this.bucketName,
Body: readFileSync(file.path),
Key: file.filename,
ACL: 'public-read',
ContentDisposition: 'inline',
})
.promise();
}
}
테스트를 진행하기 위해서 image.service.ts에서 진행해보기로 하고 정상 작동한다면 provider를 이용해서 의존성을 주입시켜 사용해 볼 생각이다
프로그램을 시작하니까 문제가 발생했다
// image.service.ts 파일에서 문제발생
// 문제의 메시지
(node:11820) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at <https://a.co/7PzMCcy>
(Use `node --trace-warnings ...` to show where the warning was created)
이 문제는 AWS에서 v3가 출시해서 v3로 마이그레이션 해야한다는 경고 메시지다
v3를 이용해야하는 이유는 번들 크기를 최대 75%까지 줄여서 성능을 개선하는데 도움이 되고 타입스크립트로 되어있어서 오류를 포착하기 좋다고 한다
위에서 사용하는 패키지는 v2버전의 패키지다
AWS에서 제공하는 예제를 참고해서 작업을 진행하면 다른 패키지를 사용하는 부분을 알 수 있다
npm install @aws-sdk/client-s3 --save
해당 패키지를 사용하면 경고 메시지는 깔끔하게 해결이 되었다
이제 내가 원하는 위치에서 S3에 이미지를 업로드 하는 작업을 진행해보자
aws-s3.service.ts 파일을 생성하고 파일로 가보자
import { ConfigService } from '@nestjs/config';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { Injectable } from '@nestjs/common';
import { createReadStream } from 'fs';
@Injectable()
export class S3Service {
private readonly client: S3Client;
private readonly region: string =
this.configService.get<string>('AWS_BUCKET_REGION');
private readonly bucket: string =
this.configService.get<string>('AWS_BUCKET_NAME');
constructor(private readonly configService: ConfigService) {
this.client = new S3Client({
region: this.region,
credentials: {
accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY_ID'),
secretAccessKey: this.configService.get<string>(
'AWS_SECRET_ACCESS_KEY',
),
},
});
}
async putObject(file) {
const { filename } = file;
await this.client.send(
new PutObjectCommand({
Bucket: this.bucket,
Key: filename,
Body: file,
}),
);
return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${filename}`;
}
}
이 코드로 진행했을 때 문제가 발생했다
// error message
TypeError: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object
at new NodeError (node:internal/errors:393:5)
at Function.from (node:buffer:328:9)
at writeBody (C:\\Users\\pla26\\workspace\\sparta\\node\\nest\\nest-board\\node_modules\\@aws-sdk\\node-http-handler\\dist-cjs\\write-request-body.js:22:32)
at ClientRequest.<anonymous> (C:\\Users\\pla26\\workspace\\sparta\\node\\nest\\nest-board\\node_modules\\@aws-sdk\\node-http-handler\\dist-cjs\\write-request-body.js:9:13)
at ClientRequest.emit (node:events:513:28)
at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:635:11)
at HTTPParser.parserOnHeadersComplete (node:_http_common:117:17)
at TLSSocket.socketOnData (node:_http_client:534:22)
at TLSSocket.emit (node:events:513:28)
at addChunk (node:internal/streams/readable:324:12
Body부분에 buffer, ArrayBuffer 등 정해진 값만 들어갈 수 있다는 에러인거 같다
AWS의 공식문서에서 참고해서 createReadStream을 이용해서 파일을 읽어 버퍼를 사용하는 방식으로 진행했다
async putObject(file) {
const { filename } = file;
await this.client.send(
new PutObjectCommand({
Bucket: this.bucket,
Key: filename,
Body: createReadStream(path),
}),
);
return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${filename}`;
}
이 방식으로 진행하면 정상적으로 작동을 한다
버퍼를 보내야해서 readFileSync를 이용해서도 진행해 보았는데 이 부분도 정상적으로 작동을 했다
readFile과 createReadStream의 차이가 무엇일까? 궁금해졌다
readFile과 createReadStream
파일을 읽거나 쓸때 버퍼, 스트림 두 가지 방식을 사용한다
node는 파일을 읽을 때 버퍼를 사용한다 메모리에 파일 크기만큼 공간을 마련해두고 테이터를 다룰 수 있다
버퍼의 단점은 미리 공간을 잡아야 한다 만약 서버에 많이 이용자가 발생한다면 메모리 문제가 발생한다
또 다음 동작으로 넘어가기 위해 버퍼를 다 쓰고 넘어갈 수 있고 읽기 압축 쓰기를 연달아 하면 매번 전체 용량을 버퍼로 처리한다
이런 단점을 해결하기 위해서 버퍼 크기를 작게 만들어서 여러번 나누어 보내는 스트림 방식이 생겼다 버퍼를 1MB에 걸쳐 나누어 보내는 100MB를 만든다
파일이 작다면 readFile을 이용해서 버퍼를 사용해도 괜찮다
하지만 이미지처럼 파일크기를 알 수 없다면 스트림을 이용해서 처리속도도 빠르고 메모리에 저장되는 데이터를 최소화 시켜서 메모리를 안정적으로 사용하자
S3에 이미지 파일을 업로드하는 부분까지는 문제가 없지만 다른 문제가 발생했다
이미지의 URL을 클릭하면 이미지를 볼 수 없고 이미지 파일을 다운받아지기만 하는 문제가 발생했다
문제를 해결해보자
async putObject(file) {
const { path, filename, mimetype } = file;
await this.client.send(
new PutObjectCommand({
Bucket: this.bucket,
Key: filename,
Body: createReadStream(path),
ContentType: mimetype,
}),
);
return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${filename}`;
}
ContentType을 추가하고 내가 지금 업로드하고 있는 파일의 type을 정해주면 해당 이미지는 image/png로 되어있다
추가만 해주면 이미지가 다운받아 지는게 아니라 이미지를 볼 수 있어진다!
이번에 배운점은 항상 로컬에 저장하던 File을 S3 클라우드 저장소에 저장할 수 있게 되었고
원하는 위치에서 저장할 수 있게 해서 예외가 발생했을 때 이미지만 저장되던 문제를 해결 할 수 있어 졌다 그리고 버퍼와 스트림에 대해서 알게되었다.
참고 사이트 / 블로그
'과거공부모음' 카테고리의 다른 글
20230228 TIL - nestjs 유닛테스트 (0) | 2023.03.01 |
---|---|
20230227 TIL - 최종프로젝트 (찰칵), nestjs-form-data (0) | 2023.02.27 |
20230221 TIL - nestjs 이미지 업로드 (0) | 2023.02.21 |
나의 개발일지 TIL(Today I learned) - 트랜잭션, 동시성 제어 (0) | 2023.02.17 |
나의 개발일지 20220213 TIL(Today I learned) - NestJS, Token 응답 (0) | 2023.02.13 |