S3를 사용해 이미지 업로드 기능을 구현해보려고 한다.
S3 버킷을 채택한 이유!
1. 서비스의 가용성
- 이 거의 100%이다. (서비스를 제공할 수 있는 상태)
2. 저렴한 비용
- 사용한 만큼 비용을 낸다.
- 장기간 보관을 하지만 빈도수가 낮은 파일들 (ex: 법적 보관기간 5년 파일)은 타입 저장방식을 달리해 비용 절감이 가능
(파일 access 빈도수에 따라 보호수준을 차등할 수 있고 차별화 된 비용을 지불할 수 있다.)
- 확장성
- AWS가 망하지 않는 한 원하는 만큼 서비스를 사용할 수 있다...
- 고성능
- AWS Region을 선택해 가까운 Region에 데이터를 관리함으로써 네트워크 지연시간을 최소화 할 수 있다.
- 관련 자료의 양
- AWS cloud 서비스에 대한 자료의 양이 많기에 문제 해결이 보다 쉬울 수 있다.
서버리스 기반 S3 Presigned URL 적용하기
보통의 파일 업로드 방식은 권한 설정이 필요하기에 서버를 경유해 업로드하게 된다. 저용량의 파일이라면 크게 상관없겠지만,
영상파일같은 대용량의 파일이 업로드 될 경우에 서버를 통해 스토리지에 저장되기 때문에 이중작업이 발생하기에 비효율적일 수 있다.
서버 CPU의 사용량이 커져 서버의 성능저하를 유발할 수도 있다. 또 서버에서 multipart file을 받아 s3 버킷에 업로드하면
서버쪽에도 파일을 갖고 있어야 하므로 리소스 낭비가 발생할 수 있다.
그럼 서버를 거쳐서 업로드를 하는 이유는 무엇이냐..
보안 때문에 그렇다.
S3 버킷을 생성할 때 여러 규칙과 권한이 설정되어있는데, 그 해당 권한을 가진 사용자만 S3에 접근해야한다.
이를 서버가 수행해준다고 보면 될 것 같다.
위의 단점을 개선해서 서버의 리소스를 사용하지 않고 클라이언트가 S3에 바로 접근해 업로드 할 수 있고, 보안까지 보장되는
Presigned Url 방식을 사용하려고 한다.
사용했을 때와 사용하지 않았을 때의 장단점은 마지막에 정리하려고 한다..
Controller
@RestController
@RequestMapping("/images")
class ImageController(
private val awsS3Service: AwsS3Service
) {
@GetMapping
@Throws(IOException::class)
fun getFile(
@RequestParam fileName: String
): ResponseEntity<String> {
val url = awsS3Service.getPresignURl(fileName)
return ResponseEntity(url, HttpStatus.OK)
}
}
get 요청으로 Presigned Url을 받아오는 메서드
Service
@Service
class AwsS3Service(
private val s3Client: S3Client,
private val presigner: S3Presigner
){
@Value("\${cloud.aws.s3.bucket}")
lateinit var bucketName: String
fun getPresignURl(fileName: String?): String? {
if (fileName.isNullOrBlank()) {
return null
}
val getObjectRequest = GetObjectRequest.builder()
.bucket(bucketName)
.key(fileName)
.build()
val getObjectPresignRequest = GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(5)) // 5분간 허용
.getObjectRequest(getObjectRequest)
.build()
val presignedGetObjectRequest = presigner.presignGetObject(getObjectPresignRequest)
val url = presignedGetObjectRequest.url().toString()
return url
}
}
fileName 기반으로 S3 버킷에서 프리사인된 URL을 생성하는 코드
Front 이미지 업로드 부분 코드
const handleFileUpload = async (event) => {
const file = event.target.files[0];
try {
const presignedUrlResponse = await axios.get('/images', {
params: {
fileName: file.name,
},
});
const presignedUrl = presignedUrlResponse.data;
await uploadImage(presignedUrl, file);
console.log('이미지 업로드 완료');
} catch (error) {
console.error('이미지 업로드 실패:', error);
}
};
const uploadImage = async (presignedUrl, file) => {
try {
const response = await axios.put(presignedUrl, file, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
console.log('이미지 업로드 성공:', response);
} catch (error) {
console.error('이미지 업로드 실패:', error);
throw error;
}
};
이렇게 작성 후 테스트를 해본 결과..
GET 요청에 실패한다.
이유를 찾아본 결과, 스프링 시큐리티 config의 설정에서 "images/**"을 추가해줬다.
그 이후에 또 업로드를 시도해봤다.
업로드 된 파일을 저장하는 과정에서 문제가 생긴다.
PUT요청에 실패했다. s3 버킷에 파일이 저장이 안된다.
S3에 저장하는 과정에서 요청에 문제가 있는것으로 보이는데..
s3 버킷에 퍼블릭으로 설정, cors도 편집했는데 권한 문제는 아닌 것 같고..
Vue의 코드 문제이거나, 백의 코드문제이거나 둘 중 하나 인 것 같다.
자바스크립트 코드를 잘 모르니 일단 서버로 넘어가서 코드를 확인했다.
@Service
class AwsS3Service(
private val s3Client: S3Client,
private val presigner: S3Presigner,
@Value("\${cloud.aws.s3.bucket}")
private var bucketName: String
) {
fun putPreSignUrl(fileName: String?): String? {
if (fileName.isNullOrBlank()) {
return null
}
val putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileName)
.build()
val putObjectPresignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(5))
.putObjectRequest(putObjectRequest)
.build()
val presignedPutObjectRequest = presigner.presignPutObject(putObjectPresignRequest)
val url = presignedPutObjectRequest.url().toString()
return url
}
}
원인은 getObjectRequest 였다.
친절하게 에러코드에 PUT 오류가 있었는데, 정작 내 코드엔 PUT에 대한 코드가 없었다.
HTTP에 대한 무지함이 드러나는 부분이다...ㅠㅠ PUT Request로 수정을 해줬다.
다시 이미지 업로드를 시도해봤다.
드디어 성공했다. 이 업로드 하나때문에 거의 이틀 가까이 시간을 썼다.
getObject는 파일을 다운로드 할 때 필요한 메서드이고, 이미지 업로드랑은 아무 상관이 없는것이었다..
이미지 업로드는 정상적으로 작동이 되니, 이제 배포 서버에서 작동하게끔 하려면 권한설정을 새로 해야할 것 같다.
게시글 조회시 이미지를 불러오는 방법은 또 뭘까..
'내일배움캠프 프로젝트' 카테고리의 다른 글
BuySell - 주문 조회 (0) | 2024.04.03 |
---|---|
BuySell - S3 이미지업로드 (2) (1) | 2024.03.29 |
BuySell - 선착순 쿠폰 발급기능 (3) (0) | 2024.03.24 |
BuySell - 선착순 쿠폰발급 (2) (0) | 2024.03.20 |
BuySell - 선착순 쿠폰발급 (0) | 2024.03.20 |