Replies: 6 comments
Presigned Url 사용장점
단점
업로드 됐는지 확인하기키가 존재하는지 확인해보면 됨 public boolean isFileUploaded(String bucketName, String key) {
try {
HeadObjectRequest headRequest = HeadObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();
s3Client.headObject(headRequest); // 존재하면 정상 응답, 없으면 예외
return true;
} catch (NoSuchKeyException e) {
return false; // 아직 업로드 안 됨
}
}서버에서 S3에 업로드 된 파일이 악성 파일인지 확인하기
// EventBridge 룰이 트리거하는 Lambda 예시
public void handleScanResult(GuardDutyEvent event) {
String scanStatus = event.getMalwareScanStatus();
String key = event.getObjectKey();
if ("THREATS_FOUND".equals(scanStatus)) {
s3Client.deleteObject(bucket, key); // 즉시 삭제
fileMetaService.markAsRejected(key);
notificationService.alertAdmin("악성 파일 탐지: " + key);
} else {
fileMetaService.markAsSafe(key);
}
}
public boolean isActuallyImage(byte[] fileBytes) {
// PNG 시그니처: 89 50 4E 47
// JPEG 시그니처: FF D8 FF
if (fileBytes.length < 4) return false;
byte[] header = Arrays.copyOfRange(fileBytes, 0, 4);
return Arrays.equals(header, new byte[]{(byte)0x89, 0x50, 0x4E, 0x47}) // PNG
|| (header[0] == (byte)0xFF && header[1] == (byte)0xD8); // JPEG
}서버 경유해서 업로드장점
단점
|
성능/서버 자원 관점 정리 (presigned vs 서버 경유)위에서 장단점·검증은 잘 정리돼서, 저는 "실제로 톰캣 자원을 얼마나 먹는지" 수치 관점으로 보탤게요. (Spring Boot + 내장 Tomcat 기준, 값은 기본 설정 + 모델 계산이라 회선 속도에 따라 달라짐) Spring 기본값부터 (큰 파일 받으려면 먼저 손봐야 함)
서버 경유가 먹는 자원
스레드 점유 시간 계산 (100MB, 클라 10MB/s 가정)→ 스레드 200개 기준 동시성 천장 ≈ 200 ÷ 11 ≈ 18 uploads/s, 그리고 이 사이 일반 API 요청까지 스레드가 굶음 Presigned는 발급이 CPU 네트워크(비용/한계 직결)
요약 (100MB, 동시 100건)
결론 제안 (하이브리드)
|
스프링 구현 관점 보충 (빈 등록 · 코드 스켈레톤 · presigned 보안)성능 얘기에 이어, 실제 Spring Boot에서 어떻게 짜는지와 놓치기 쉬운 포인트를 정리합니다. (AWS Java SDK v2 기준) 빈 등록 — 두 방식은 서로 다른 클라이언트가 필요@Bean
S3Client s3Client() { return S3Client.builder().region(REGION).build(); } // 서버 경유(PutObject)용
@Bean
S3Presigner s3Presigner() { return S3Presigner.builder().region(REGION).build(); } // presigned 발급용 (별도 빈!)
1) 서버 경유 — 구현 + multipart 튜닝@PostMapping(value="/upload", consumes=MULTIPART_FORM_DATA_VALUE)
public UploadResponse upload(@RequestParam("file") MultipartFile file) throws IOException {
String key = "uploads/" + UUID.randomUUID() + ext(file);
s3Client.putObject(
PutObjectRequest.builder().bucket(bucket).key(key)
.contentType(file.getContentType()).build(),
RequestBody.fromInputStream(file.getInputStream(), file.getSize())); // getBytes() 금지 → OOM
return new UploadResponse(key, publicUrl(key));
}# 큰 파일 받으려면 기본값(1MB/10MB) 반드시 상향
spring.servlet.multipart.max-file-size: 50MB
spring.servlet.multipart.max-request-size: 50MB
2) Presigned — 2단계 + 완료 콜백// (1) 발급
@PostMapping("/upload/presign")
public PresignResponse presign(@RequestBody PresignRequest req) {
String key = "uploads/" + UUID.randomUUID() + "-" + req.filename(); // key는 서버가 생성
PutObjectRequest obj = PutObjectRequest.builder()
.bucket(bucket).key(key).contentType(req.contentType()).build(); // contentType 서명에 포함
PresignedPutObjectRequest p = s3Presigner.presignPutObject(
PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(5)) // 짧은 만료
.putObjectRequest(obj).build());
return new PresignResponse(key, p.url().toString());
}
presigned 보안 체크리스트
한 줄 결론빈 2개 등록해두고 작은 파일은 (1) 서버 경유 / 큰 파일은 (2) presigned 로 라우팅하는 하이브리드가 구현 난이도 대비 가장 무난합니다. |
서버 경유 심화: MVC(블로킹) vs WebFlux(비동기) 다이렉트 업로드같은 "서버 경유"라도 MVC 블로킹이냐 WebFlux 비동기냐에 따라 스레드 거동이 완전히 다릅니다. 차이는 딱 하나 — 업로드가 진행되는 동안 스레드가 묶여 있느냐, 풀려 있느냐. 방식 A — MVC(서블릿) 블로킹방식 B — WebFlux(Netty) 비동기동시 업로드 3개일 때 비교정리표
오해 방지 2가지
결론: WebFlux는 스레드 고갈을 기술로 푸는 것, presigned는 파일 바이트를 서버에서 치워서 문제 자체를 없애는 것. 큰 파일·고동시성이면 presigned 우선, 그게 불가능할 때 WebFlux를 고려. |
|
결론upload 로직 자체는 presignedURL로 구현 단 클라이언트에서 호출할떄 upload , 실제 api 를 두개로 나눠서 보내도록 구현 |
Uh oh!
There was an error while loading. Please reload this page.
.
All reactions