AWS S3를 이용해 파일업로드 기능을 구현해보자 ❕
지난 글에서 S3 버킷을 생성하고 권한을 설정했다. 이번에는 파일 업로드하는 기능을 코드로 구현해보려고 한다.
AWS S3 - 개념 및 버킷 설정
AWS에서 제공하는 서비스인 S3를 이용해보자❕ 1. AWS S3(Simple Storage Service) S3는 AWS에서 제공하는 클라우드 기반의 객체 스토리지 서비스이다. 이미지, 동영상 및 여러 형태의 파일을 저장하고 관리
chchaego.tistory.com
1. 구현
구현 환경
- Spring
- Maven
1-1. 의존성 추가 및 application.properties 설정
pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-aws -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
application.properties
# == AWS ACCESS SETTING ==
cloud.aws.credentials.access-key={ACCESS_KEY}
cloud.aws.credentials.secret-key={SECRET_KEY}
#== AWS S3 Bucket Setting ==
cloud.aws.s3.bucket={BUCKET_NAME}
cloud.aws.region.static={REGION}
cloud.aws.stack.auto=false
- IAM을 생성해 발급받은 Access Key와 Secret Key를 채워 넣으면 된다.
spring-cloud-starter-aws
는 기본적으로 서비스가 *CloudFormation 스택 내에서 실행된다고 가정하기 때문에, 설정한 CloudFormation이 없으면 해당 내용을 사용하지 않도록 `cloud.aws.stack.auto=false`로 설정해줘야 한다.
* CloudFormation?
AWS에서 제공하는 대표적인 IaC 기반의 서비스. CloudFormation을 사용하면 AWS의 EC2, Lambda 등과 같은 리소스를 수동으로 생성할 필요 없이, 템플릿(코드)으로 구성하고 스택을 생성하여 인프라를 구성할 수 있다.
1-2. FileUploaderApi 컨트롤러 생성
@RestController
@RequestMapping("/api/v1/upload")
public class FileUploaderApi {
@Autowired
private FileUploaderService fileUploaderService;
@PostMapping("/cloud")
public ResponseEntity<?> uploadFilesToCloud(@RequestParam("file") MultipartFile file) {
Message message = new Message();
try{
message.setData(fileUploaderService.uploadFileToCloud(file, "uploads"));
message.setStatus(HttpStatus.OK);
message.setMessage("success");
} catch(Exception e) {
message.setStatus(HttpStatus.BAD_REQUEST);
message.setMessage("error");
}
return new ResponseEntity<>(message, message.getStatus());
}
}
/api/v1/upload/cloud
로 요청이 들어오면 파라미터 file로 넘어오는 MultipartFile를 받아 서비스단에 넘긴다.uploadFileToCloud()
의 두번째 인자 “uploads”는 S3에서 저장되는 디렉토리를 명시한 것이다.
1-3. FileUploaderService 서비스 생성
@Service
@RequiredArgsConstructor
public class FileUploaderService {
private final AmazonS3Client s3Client;
@Value("${cloud.aws.s3.bucket}")
public String bucket;
// fileName(Key)은 중복되지 않도록 랜덤한 값을 추가
public String uploadFileToCloud(MultipartFile file, String dir) throws IOException {
String fileName = dir + "/"
+ UUID.randomUUID().toString().replaceAll("-", "")
+ file.getOriginalFilename();
return putS3(file, fileName);
}
// s3에 파일 업로드
private String putS3(MultipartFile file, String fileName) {
ObjectMetadata objMeta = new ObjectMetadata();
objMeta.setContentType(file.getContentType());
objMeta.setContentLength(file.getSize());
try {
s3Client.putObject(new PutObjectRequest(bucket, fileName, file.getInputStream(), objMeta)
.withCannedAcl(CannedAccessControlList.PublicRead)); // 오브젝트 권한 설정
} catch (IOException e) {
throw new FileUploadException("S3 파일 업로드 실패");
}
// 업로드된 파일의 S3 URL 주소를 반환
return s3Client.getUrl(bucket, fileName).toString();
}
}
spring-cloud-starter-aws
가 S3 관련 Bean을 자동 생성해주기 때문에, 설정 코드 없이도 AmazonS3Client를 DI 받은 후 관련된 기능들을 사용할 수 있다. (application.properties에 미리 값을 설정해줘야 한다)- 버킷 내에서 동일한 키 값이 업로드되면 덮어 씌워버리기 때문에 fileName에 랜덤한 값을 추가해줬다. (버전 관리를 설정하면 동일한 키 값 사용 가능)
- S3에 파일을 업로드하는 객체 `PutObjectRequest`를 만들어 AmazonS3Client의 `putObject()`를 통해 저장한다.
PutObjectRequest
는 다음을 참고해서 생성할 수 있다. 나는 세번째 생성자를 통해 `PutObjectRequest`를 만들었다. (AbstractPutObjectRequest는 AmazonWebServiceRequest의 자식 클래스)
- `CannedAccessControlList.PublicRead` : 외부에 공개할 이미지 이므로, 해당 파일에 public read 권한을 설정했다.
1-4. index.html 생성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spring - AWS S3</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<!-- 업로드 form -->
<form id="img-uploader-form" enctype="multipart/form-data" method="post">
<input id="img-uploader" type="file" name="uploadfile" accept="image/*" onchange="checkFileType(this);" />
<input type="button" value="확인" onclick="uploadFile();" />
</form>
<!-- 결과 이미지 -->
<div>
<img src="" id="uploaded-img" style="width: 150px; height: 150px">
</div>
<script>
// 업로드 파일 확장자 체크
function checkFileType(obj) {
var fileKind = obj.value.lastIndexOf('.');
var type = obj.value.substring(fileKind+1, obj.length);
var fileType = type.toLowerCase();
var allowType = ['jpg', 'gif', ,'png','jpeg','bmp','tif'];
if(allowType.indexOf(fileType) == -1){
alert('Only image file can be uploaded.');
obj.value = '';
}
}
function uploadFile() {
var formData = new FormData();
var file = $("#img-uploader")[0].files[0];
var urlPath = "/api/v1/upload/cloud";
formData.append("file", file);
$.ajax({
type: "POST",
url: urlPath,
data: formData,
enctype: 'multipart/form-data',
processData: false,
contentType: false,
cache: false,
success: function(res) {
$('#uploaded-img').attr("src", res.data)
},
error: function(e) {
console.log("File Upload Error : ", e);
}
});
}
</script>
</body>
</html>
- 업로드가 성공된다면 응답값으로 조회된 이미지 경로를 설정한다.
$('#uploaded-img').attr("src", res.data)
1-5. 결과 확인
/api/v1/upload/cloud
에 업로드 요청을 하면- 정상적으로 S3 버킷 ‘chaego-bucket’의
/uploads
에 저장되고, 이미지 결과가 표시된다.
2. 마무리
구현 과정은 매우 간단하다. 실무에서는 파일 사이즈 제한, 다중 파일 업로드 처리 및 S3를 안전하게 사용하기 위해 설정해야 할 기능이 많을 것이다. 찾아보니, S3에 Cloudfront를 연결해 S3를 퍼블릭으로 공개하지 않고 Cloudfront를 통해서 S3에 접근할 수 있도록 하는 방법이 있다고 한다. 이런 기능들을 잘 활용하는 것이 보다 더 안전하게 서비스를 운영할 수 있을 것이다.
참고자료 😃
https://jojoldu.tistory.com/300
https://techblog.woowahan.com/11392/
'AWS' 카테고리의 다른 글
[CI/CD] Github + Jenkins + AWS EC2를 활용한 CI/CD 구축 (2) | 2024.11.03 |
---|---|
[AWS] AWS S3 - 개념 및 버킷 설정 (0) | 2024.04.09 |