
이번엔 multer 로 로컬에 이미지를 저장하던 것에서 좀 더 나아가
AWS S3에 이미지를 올리고 이미지의 URL을 반환 받아 이미지로 나타내는 것 까지 해보겠습니다.
전체적인 흐름
우선 React(browser) 에서 axios를 통해 백엔드로 요청을 보낸다.
const [src, setSrc] = useState("");
const s3Test = (e) => {
const blob = e.target.files[0];
if (!!e.target.value) {
const imageData = new FormData();
const file = new File([blob], encodeURI(`${blob.name}`), {
type: blob.type,
});
imageData.append("image", file);
axios({
method: "POST",
url: `백엔드 API 주소`,
headers: {
"Content-Type": "multipart/form-data",
// "Content-Type": "application/x-www-form-urlencoded",
},
data: imageData,
withCredentials: true,
})
.then((data) => {
setSrc(data?.data?.fileURL);
})
.catch((error) => {});
}
위의 내용을 요약하면 백엔드로 요청을 보내고 fileURL 을 받아서 img태그의 src로 설정해둔 src상태를 변경한다는 말이올시다.
그리하여 백엔드로 요청을 보내게 되고
백엔드에서는 이 FormData 를 받아서 multer라는 녀석이 처리를 해주는데 AWS S3 에 올리려면
multer 도 설치하고 multer-s3 라는 패키지를 추가로 설치해야한다.
그리고 우선 AWS 에 연결 한 뒤, multer-s3를 통해 어떤 버킷에 저장할 것인지를 명시 해 주고 나면 끝.
여담으로 FormData 를 multer 없이 직접 받아보려했지만 Express 에서는 자체적으로 FormData를 받지 못한다고한다.
따라서 패키지를 썼다... (근데 multer 를 만든 사람들은 어떻게...?)
multer 로 원본파일 업로드 후, 리사이징 하여 다시 업로드 하려면 aws에 업로드 과정을 두번을 거쳐야하는데, FormData를 받아서 Buffer를 가지고 원본 Buffer와 리사이징 하고 난 Buffer를 한번에 올리면 통신하는 과정에 드는 리소스를 아낄 수 있지 않을까 라는 생각으로 접근해봤지만,, 한 세시간 뒤져도 단 한가지의 실마리조차 찾지 못했다..
과거 Express v4 이하 버전에서는 body-parser 패키지를 통해서 직접 FormData를 받을 수 있었지만 지금은 막혔다고 한다. (왜..?)
이 안건에 대해서는 추후에도 꾸준히 찾아봐서 무조건 해결하고만다..
const AWS = require("aws-sdk");
const dotenv = require("dotenv");
dotenv.config();
AWS.config.update({
credentials: {
accessKeyId: process.env.AWS_S3_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
},
region: process.env.AWS_S3_REGION,
});
AWS.config.getCredentials((err) => {
if (err) console.log(err);
console.log(`📚 Connect With AWS S3`);
});
module.exports = AWS;
const AWS = require("./aws");
const multer = require("multer");
const multerS3 = require("multer-s3");
require("dotenv").config();
const s3 = new AWS.S3();
const uploadToS3 = multer({
storage: multerS3({
s3: s3,
bucket: process.env.AWS_S3_BUCKET,
acl: "public-read",
key: function (req, file, cb) {
cb(
null,
`original/${new Date().valueOf()}.${file.originalname.split(".").pop()}`
);
},
}),
limits: {
fileSize: 1000 * 1000 * 10,
},
});
module.exports = uploadToS3;
아무튼 나는 router 파일 내에 aws객체를 생성하고, multer upload storage를 지정하니 더러워보여서 따로 파일을 분리하여 진행했다.
원본 파일은 original/ 에 저장하기 위해 key 값을 저런식으로 적었다. 파일의 원본명은 UTF8로 인코딩되어 파일내의 metadata 에 저장되어있다. 저장되는 이름은 겹칠 수가 있기에 시스템의 시간 값으로 계산 두들겨서 저장되도록 하였다.
아무튼 위에서 AWS 객체를 만들고 multer-s3를 통해서 저장될 위치, 파일명등을 정했다.
이제 남은 과정은 이미지를 리사이징하고 다시 s3에 올리는 것,
const uploadImageToS3 = async (req, res, next) => {
// S3 객체 생성
const s3 = new AWS.S3();
// s3 에서 원본 이미지를 받아온다.
const file = await s3
.getObject({
Bucket: process.env.AWS_S3_BUCKET,
Key: req.file.key,
})
.promise();
// 원본이미지의 Body값 (Buffer)를 sharp 라이브러리로 webp형식으로 계산하고 Buffer형태로 변수에 저장한다.
const buffer = await sharp(file.Body).webp({ lossless: true }).toBuffer();
// s3에 다시 업로드 한다.
await s3
.putObject({
Bucket: process.env.AWS_S3_BUCKET,
Key: `resized/resized-${
req.file.key.split("/").pop().split(".")[0]
}.webp`,
// 꼭 써줘야한다. 안그럼 access denied 당한다. 여기서 30분 날렸다.
ACL: "public-read",
Body: buffer,
})
.promise()
// 위의 과정을 마치면 브라우저로 응답한다.
.then(() => {
res.json({
message: "ok",
status: 200,
// 굉장히 더럽다. 그치만 aws s3에는 일련의 URI 규칙이 있는데 이것을 응용해서 위치를 나타낸다.
// 사실 getObject로 위치를 알아 낼 수 있지만 어찌되었든 네트워크 통신이 한번 더 일어나는것이기에
// 리소스 소모가 있을것으로 판단해서 하드코딩(?) 느낌으로 통신 횟수를 줄였다.
fileURL: `https://${process.env.AWS_S3_BUCKET}.s3.${
process.env.AWS_S3_REGION
}.amazonaws.com/resized/resized-${
req.file.key.split("/").pop().split(".")[0]
}.webp`,
});
});
};
설명하기엔 너무 더러울까봐 주석으로 정리했다.
내가 살면서 해본 코딩 경험중에 가장 더럽게 힘들었다고 생각된다.
AWS 를 다뤄본것도 처음이고 특히 S3는 누가 하는거 구경조차 해본적이 없다.
굉장히 어려웠다.
버킷을 만드는것 자체는 매우 쉬웠지만 그 버킷의 정책을 관리하는건 매우매우 힘들었다.
보안상의 이슈가 있을 수 있다고 해서 owner 계정에서 IAM 사용자를 하나 만들고 사용자에서 AccessKey를 발급받아 그 AccessKey를 사용하는데,
IAM 이나 IAM 그룹마다 접근권한같은걸 설정해야하는데 그 설정하는 권한 이름조차도 복잡했고
S3 버킷을 만드는건 쉽지만 그 버킷의 접근권한등을 설정하고 소유권등을 설정하는 과정이 매우 힘들었다.
그리고 aws-s3 라이브러리를 사용해 연결하는 과정에서도
amazon document를 읽으며 진행했는데, 뭔가 많이 생략된 내용을 알려주는데, 이는 27년간 부모님 등골 빨아먹는 못난 아들의 눈치로 해결했다.
AWS.config.update() 이 부분에서 update의 인자로 뭐가 들어갈 지 안적혀있었다..
있긴했는데 region만 적혀있었고,, credentials에 관한 부분은 생략되어있었다.
하지만 다른 방식 ( aws.json 파일을 작성해서 하는 방식 )에서 사용된 것을 보고 해결했다.
하지만, 난 덕분에 성장했다.
라며 시계를 봤는데 새벽 6시
아....
개발자...
될 수 있을까...?
아무튼 오늘은
git add .
git commit -m "[FEAT] AWS는 언제나 어려움"
git push origin aws-s3
$sudo shutdown -s now
'B4 Junior' 카테고리의 다른 글
DOCKER 기초 docker build , docker run , docker images , docker container (0) | 2023.02.28 |
---|---|
IPFS with PINATA (0) | 2023.01.31 |
Image compress, Format convert with Sharp Library (0) | 2023.01.03 |
🍞 Image upload ( Toast Editor ) (0) | 2023.01.02 |
벱-영오-프로젝트 투 회고 📝 (0) | 2022.08.31 |
백엔드는 못말려
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!