Spring Boot
Presigned Url을 통한 파일 업로드 최적화
최-코드
2024. 11. 8. 16:06
필요성
- 기존의 방식으로 서버에서 s3로 업로드하는 방식을 사용하면 서버에서 이미지 파일을 받고 이를 s3에 업로드했어야 했다. 이 과정에서 서버 자원이 많이 소모되기에 presigned url을 통해 서버에서 직접 파일을 처리하지 않게 되어 서버에 부담이 많이 줄어든다.
- 또한 aws에서 presigned url을 통해 파일을 업로드할 시에 처리 속도가 더 빠르다는 장점이 있다.
기본 로직
- 클라이언트에서 이미지 이름, 타입(옵션)을 보낸다.
- 서버는 이를 토대로 s3에게 presigned url을 요청한다.
- s3는 서버에 presigned url을 응답한다.
- 서버는 이를 클라이언트에 응답한다.
- 클라이언트는 presigned url에 직접 파일을 업로드하여 bucket에 업로드한다.
의존성
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.767'
implementation 'software.amazon.awssdk:s3:2.27.3'
implementation 'software.amazon.awssdk:s3control:2.27.3'
implementation 'software.amazon.awssdk:s3outposts:2.27.3'
설정파일
cloud:
aws:
s3:
bucket: chois3
stack:
auto: false
region:
static: ap-northeast-2
credentials:
access-key:
secret-key:
ConfigClass
@Configuration
public class S3Config {
@Value("${cloud.aws.key.access-key}")
private String accessKey;
@Value("${cloud.aws.key.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
.build();
}
}
Controller
@RestController
@RequiredArgsConstructor
@RequestMapping("/images")
public class S3ImageController {
private final S3ImageService s3ImageService;
@GetMapping
public ResponseDto getPreSignedUrl(@RequestParam("imageName") String imageName) {
PreSignedUrlResponseDto responseDto = s3ImageService.generatePreSignedUrl(imageName);
return BodyResponseDto.onSuccess("PreSignedUrl 생성 및 조회 성공", responseDto);
}
}
Service
@Service
@RequiredArgsConstructor
public class ImageUploadService {
@Value("${cloud.aws.s3.bucket}")
private String bucket;
private final AmazonS3Client amazonS3Client;
public PreSignedUrlResponseDto generatePreSignedUrl(String imageName) {
GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(imageName);
String preSignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest).toString();
String imageSavedUrl = amazonS3.getUrl(bucket, generatePresignedUrlRequest.getKey()).toString();
return PreSignedUrlResponseDto.of(preSignedUrl, imageSavedUrl);
}
private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String fileName) {
String extension = extractExtension(fileName);
System.out.println(extension);
String savedFileName = UUID.randomUUID() + "." + extension;
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, savedFileName)
.withMethod(HttpMethod.PUT) // put으로 요청 보내야 함
.withExpiration(new Date(System.currentTimeMillis() + 180000)) // presigned url 만료 설정
.withContentType(MimeTypeUtil.getMimeType(extension)); // content-type 설정 -> 다른 형식의 파일 방지
// 익명 사용자도 업로드된 파일을 읽을 수 있도록 상태 설정
generatePresignedUrlRequest.addRequestParameter(
Headers.S3_CANNED_ACL,
CannedAccessControlList.PublicRead.toString()
);
return generatePresignedUrlRequest;
}
public void deleteFile(String fileUrl) {
String key = fileUrl.substring(fileUrl.indexOf("image/"));
amazonS3Client.deleteObject(bucket, key);
}
public void deleteFiles(List<String> fileUrls) {
if(fileUrls !=null && !fileUrls.isEmpty()) {
List<KeyVersion> keyVersions = fileUrls.stream()
.map(url -> url.substring(url.lastIndexOf("image/")))
.map(KeyVersion::new).toList();
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucket)
.withKeys(keyVersions);
amazonS3Client.deleteObjects(deleteObjectsRequest);
}
}
}
- 정적 url(s3에 저장된 업로드된 파일을 볼 수 있는 url)을 같이 보내줌으로써 프론트에서 presigned url을 가공하는 과정 없앤다.
- 이를 통해 가공 과정에서의 오류나 문제를 걱정할 필요 없이 정확한 경로를 받을 수 있도록 할 수 있다.