지금까지 Aniverse 프로젝트를 진행하면서, 클라이언트 ↔ 서버간 이미지를 전송할 때, 임시로 깃허브 이슈를 통해 생성한 이미지 링크를 전송하는 방식으로 진행했다. (ex. 140559373-3d9b5f13-e1e4-4a51-80ae-b9cdf72769eb.png (474×440) (user-images.githubusercontent.com))
이번에 사용자 휴대폰의 갤러리에서 직접 사진을 선택하여 서버에 올릴 수 있도록 수정했다. Amazon에서 제공하는 bucket을 이용하였고, 다음 그림은 우리가 개발하고자 하는 방식이다.

- F) 클라이언트에서 이미지를 얻어 base64 방식으로 인코딩
- F → B) base64로 인코딩된 문자열을 서버에게 전송
- B) 문자열을 디코딩하여 이미지 얻어내기
- 얻어낸 이미지를 .jpg 형식으로 서버 내에 저장
- 해당 이미지를 AWS bucket에 객체로 등록 → 어디에서나 접근 가능한 링크 생성
- DB에는 생성한 링크 저장
- 이미지 요청이 들어오면, DB에서 링크를 전송
💡 1. AWS 버킷 생성
사용한 AWS S3는 아마존에서 제공하는 온라인 스토리지 웹 서비스이다. 다양한 기능을 필요로 하지 않았으므로 Lightsail을 사용했다.

버킷을 생성하면 다음과같이 오브젝트들을 등록할 수 있고, 고유 도메인이 생성된다. 접근 권한을 public으로 설정하면, “해당 도메인/객체 이름”으로 누구나 해당 객체에 접근할 수 있게된다. (ex. tokiki5 (450×300) (bucket-54ci7a.s3.ap-northeast-2.amazonaws.com))
💡 2. 서버에 필요한 모듈 설치
$ npm install --save express aws-sdk multiparty
💡 3. Controller에 버킷, 필요한 모듈 등록
- adoptController
const fs = require('fs');
const { constants } = require('buffer');
const AWS = require('aws-sdk');
const BUCKET_NAME = 'bucket-54ci7a'
AWS.config.update({
region: 'ap-northeast-2',
accessKeyId: 'AKIA5EBOBS6LWTZ',
secretAccessKey:'S7k01bdix5e04m1Hx0GVE6/2xMnlvfJ'
})
이미지를 서버에 저장하고, 버킷에 등록하기 위해 추가적으로 필요한 코드이다. accesskeyId와 secretAccessKey는 버킷을 생성한 뒤 발급받을 수 있다. secretAccessKey는 발급시점 딱 한번만 보여지니 주의!
💡 4. base64 디코딩 코드
- adoptController - 동물정보 업로드 API
// base64포멧의 스트링을 디코딩하여 파일로 쓰는 함수
function base64_decode(base64str, file) {
// 버퍼 객체를 만든후, 인자 <=> (base64 스트링, 파일 경로)
var bitmap = new Buffer(base64str, 'base64');
// 버퍼의 파일을 쓰기
fs.writeFileSync(file, bitmap);
}
var filename = animalSpecies + animalAge; // 파일명 변수
console.log('filename : ', filename);
var filepath = 'filefile/' + filename + '.jpg'; // 파일경로 (서버에서의 상대경로)
console.log('filepath : ', filepath);
base64_decode(animalImage, filepath); // 함수 실행
💡 5. AWS 버킷에 이미지 등록하는 코드
- adoptController - 동물정보 업로드 API
const imageStream = fs.createReadStream(filepath);
const params = { Bucket:BUCKET_NAME, Key:filename, Body:imageStream, ContentType: 'image' }
const upload = new AWS.S3.ManagedUpload({ params });
upload.promise()
💡 최종 코드
/**
* Adopt 1.2 동물정보 업로드 API
* [POST] /adopt/animalinfo
*/
exports.postAdoptAnimalInfo = async function (req, res) {
// base64포멧의 스트링을 디코딩하여 파일로 쓰는 함수
function base64_decode(base64str, file) {
// 버퍼 객체를 만든후, 인자 <=> (base64 스트링, 파일 경로)
var bitmap = new Buffer(base64str, 'base64');
// 버퍼의 파일을 쓰기
fs.writeFileSync(file, bitmap);
}
try {
console.time('/adopt/animalinfo');
const {animalImage, animalGender, animalSpecies, animalAge,
animalVaccinated, animalDisease, animalFind, animalIntro,
adoptEnd, adoptCondition, animalWeight} = req.body;
var filename = animalSpecies + animalAge; // 파일명 변수
console.log('filename : ', filename);
var filepath = 'filefile/' + filename + '.jpg'; // 파일경로 (서버에서의 상대경로)
console.log('filepath : ', filepath);
base64_decode(animalImage, filepath); // 함수 실행
const imageStream = fs.createReadStream(filepath);
const params = { Bucket:BUCKET_NAME, Key:filename, Body:imageStream, ContentType: 'image' }
const upload = new AWS.S3.ManagedUpload({ params });
upload.promise()
const postImageTestRows =
await adoptModel.insertAdoptInfo(filepath, animalGender, animalSpecies, animalAge,
animalVaccinated, animalDisease, animalFind, animalIntro,
adoptEnd, adoptCondition, animalWeight);
console.timeEnd('/adopt/animalinfo');
res.json({
isSuccess: true
});
} catch (err){
logger.error(`postAdoptAnimalInfo DB Connection error\n: ${err.message}`);
return res.status(500).send(`Error: ${err.message}`);
}
};
💡 Comment
- 원래 프론트에서 파일을 통째로 넘겨주기를 시도했으나, 현재 프론트에서 사용중인 Volley로는 어렵다고 한다. 따라서 base64 문자열을 넘기는 방법을 사용했는데, 이렇게 이미지를 전송할 때나 인증 관련된 정보를 전송할 때 종종 사용되는 방법이라고 한다.
- 인코딩된 base64 문자열 또한 매우 길어서 그대로 DB에 저장하면 많은 용량이 사용될텐데, 따로 저장소를 생성하여 링크로만 접근하는 방법이 효율적이라는 생각을 했다. 실무에서는 파일들을 어떻게 저장할까 궁금하다.
- 임시로 filename을 animalSpecies + animalAge로 설정했는데, 중복되는 경우 에러가 발생하는것을 확인했다. 이부분에 대한 예외처리가 필요하다.
- putty는 안되는게 너무 많다..

참고