주제: Presigned URL을 통한 파일 업로드
1. Presigned URL방식이란
- Presigned URL은 서버에서 인증된 임시 업로드 링크를 생성해 클라이언트에 전달하고, 클라이언트가 직접 외부 스토리지(예: AWS S3)에 파일을 업로드할 수 있도록 해주는 방식입니다.
2. Vivim프로젝트에서 썼던 기존의 파일 업로드 방식
@PostMapping(path = "/posts/{postId}/file/stream", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "게시글에 파일 생성 및 S3 업로드")
public ResponseEntity<ApiResponse> createPostFile(@PathVariable Long postId,
@RequestPart("file") MultipartFile file) throws IOException {
metadata = new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
bucketName = "vivim-s3";
today = LocalDate.now().toString();
uuid = UUID.randomUUID().toString();
extension = getExtensionFromContentType(file.getContentType());
objectKey = "uploads/" + today + "/" + uuid + extension;
fileName = file.getOriginalFilename();
amazonS3Client.putObject(bucketName, objectKey, file.getInputStream(), metadata);
String fileUrl = amazonS3Client.getUrl(bucketName, objectKey).toString();
fileService.createPostFile(fileName, fileUrl, file.getSize(), postId);
}
요약하면
-
클라이언트가 multipart/form-data형태의 파일을 WAS가 전달받는다.
-
WAS가 S3에 파일을 InputStream 형태로 직접 업로드
-
DB에는 파일의 MetaData를 저장
이런 방식으로 구현이 됐었다.
처음엔 100KB이하의 파일들로만 테스트했기 때문에 문제가 발견되지 않았었다.
추후 대용량 파일 업로드, 다운로드를 하며 문제점을 발했다.
- 문제점
-
1번 문제: 100GB의 대용량 파일을 업로드, 다운로드 할 때 15초 정도 시간이 걸리는데 그 시간동안 서버가 멈추는 문제가 발생했다.(서버에 부하가 많이 감)
-
2번문제: 다운로드의 경우 클라이언트가 다운로드를 누르면 다운로드 진행상황이 보이지 않고 파일 다운이 완료됐을 때만 알림이 떴었다.
- 해결방법
-
기존의 방식은 서버에 부하를 많이 주기 때문에 Presigned URL방식으로 바꿨다.
3.Presigned URL을 적용한 방식
@PostMapping(path = "/posts/{postId}/file/presigned", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "게시물에 파일 업로드용 PreSigned URL 생성")
public ResponseEntity<PreSignedUrlResponse> createPostFilePresignedUrl(
@PathVariable Long postId,
@RequestBody FileMetadataRequest fileMetadata) {
// S3 경로 설정
String bucketName = "vivim-s3";
String today = LocalDate.now().toString();
String uuid = UUID.randomUUID().toString();
String extension = getExtensionFromContentType(fileMetadata.getContentType());
String objectKey = "uploads/" + today + "/" + uuid + extension;
// PreSigned URL 생성을 위한 메타데이터 설정
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(
bucketName, objectKey)
.withMethod(HttpMethod.PUT)
.withExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 15)); // 15분 유효
// Content-Type 설정
generatePresignedUrlRequest.addRequestParameter(
Headers.CONTENT_TYPE, fileMetadata.getContentType());
// PreSigned URL 생성
URL preSignedUrl = amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest);
String fileUrl = amazonS3Client.getUrl(bucketName, objectKey).toString();
// DB에 파일 정보 저장 (실제 업로드는 클라이언트가 URL을 통해 직접 수행)
fileService.createPostFile(fileMetadata.getFileName(), fileUrl, fileMetadata.getFileSize(),
postId);
// 클라이언트에게 PreSigned URL 반환
PreSignedUrlResponse response = new PreSignedUrlResponse(
preSignedUrl.toString(),
fileUrl,
objectKey
);
return ResponseEntity.ok(response);
}
Presigned URL방식을 요약하면
-
서버는 파일 업로드용 S3 URL만 생성 & DB에 파일의 MetaData저장
-
클라이언트가 해당 URL로 직접 S3에 PUT 요청하여 파일 업로드
물론 Presigned URL 방식도 단점이 있다.
-
업로드 실패를 서버가 감지를 못하고 클라이언트가 전송을 해줘야한다.
-
Presigned URL을 가진 사용자는 누구든지 업로드, 다운로드가 가능하다.
-> 때문에 Presigned URL의 유효시간을 15분 등 짧게 두어야 한다.