Skip to content

Latest commit

 

History

History
441 lines (328 loc) · 19.3 KB

File metadata and controls

441 lines (328 loc) · 19.3 KB

WSS S3 Service

1. 개요

AWS SDK for Java V2를 기반으로, AWS S3(Simple Storage Service)와의 상호작용을 간소화하고 파일 및 이미지 업로드/조회/삭제 기능을 안정적이고 편리하게 사용할 수 있도록 설계된 Java 라이브러리입니다.

본 라이브러리는 S3의 핵심 기능들을 추상화하여 제공하며, 특히 이미지 파일의 MIME 타입 감지, 콘텐츠 크기 검증 등과 같은 부가적인 기능들을 포함하여 개발자가 비즈니스 로직에 더 집중할 수 있도록 돕습니다.

2. 주요 기능

  • 간편한 파일 및 이미지 관리: S3FileServiceS3ImageService를 통해 인터페이스S3DefaultService로 파일을 업로드, 조회, 삭제할 수 있습니다.
  • 유연한 업로드 방식: File 객체와 InputStream을 모두 지원하여 다양한 환경에 적용할 수 있습니다.
  • 안전한 이미지 업로드 (MIME 타입 검증): S3ImageService를 통해 이미지 업로드 시, 파일의 유효성을 검증하기 위해 MIME 타입을 감지합니다.
    • 고속(FAST) 전략 : 파일 헤더 일부를 분석하여 검사합니다. 빠르지만 의도적인 조작을 탐지하기는 어렵습니다. (tika-core 기반의 감지 방식)
    • 정밀(PRECISE) 전략 : 파일 내부 시그니처를 분석하여 검사합니다. 정확도는 높지만, 환경에 따라 메모리 및 디스크 I/O 오버헤드가 발생할 수 있습니다. (tika-parsers 기반의 분석 방식)
  • 강타입(Strongly-typed) 모델: ObjectKey, Bucket, AccessKey 등 S3의 주요 개념들을 클래스로 래핑하여 컴파일 타임에 실수를 방지하고 코드의 안정성을 높입니다.
  • 체계적인 예외 처리 계층: AWS SDK의 복잡한 예외를 추상화하여, 목적에 따라 세 가지 최상위 예외로 명확하게 구분했습니다. 하위에는 세분화된 커스텀 예외를 제공합니다. 이를 통해 라이브러리 사용자는 오류를 일관되고 손쉽게 처리할 수 있습니다.
    • ValidationException: 사용자 입력값(파일 형식 등)이 규칙에 맞지 않을 때 발생합니다.
    • AwsGlobalException: AWS 전역 설정(자격 증명, 리전 설정 등)이 잘못되었을 때 발생합니다.
    • S3OperationException: S3 서비스 사용 중 발생하는 모든 오류(버킷 없음, 접근 거부 등)를 처리합니다.
  • 유연한 설정: S3AccessConfig를 통해 AWS 자격 증명(Credentials) 및 리전(Region)을 쉽게 설정할 수 있습니다. 환경 변수, 시스템 프로퍼티를 통한 자동 설정도 지원합니다.
  • 팩토리 및 캐싱: S3ClientFactoryMimeTypeDetectionStrategyFactory를 통해 S3 클라이언트와 MIME 타입 감지 전략을 유연하게 생성하고, 생성된 S3 클라이언트를 캐싱하여 성능을 최적화합니다.

3. 설치 방법

Gradle (JitPack)

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.Team-WSS:WSS-Server-S3:{latest-version}'
}

Maven (JitPack)

<repositories>
  <repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>com.github.Team-WSS</groupId>
    <artifactId>WSS-Server-S3</artifactId>
    <version>{latest-version}</version>
  </dependency>
</dependencies>

{latest-version} 부분에는 Releases 페이지에서 최신 버전을 확인하여 입력해주세요.

4. 사용 방법

4.1. 기본 사용법

4.1.1. S3 클라이언트 설정

S3AccessConfig 빌더를 사용하여 AWS 자격 증명과 리전을 설정합니다.

  • 자격 증명을 명시하지 않으면, 라이브러리는 환경 변수(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), 시스템 프로퍼티 등에서 자동으로 값을 탐색합니다.
  • 리전을 명시하지 않으면 기본값으로 ap-northeast-2 (서울)가 사용됩니다.
S3AccessConfig s3Config = S3AccessConfig.builder()
    .withCredentials("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
    .withRegion("ap-northeast-2")
    .build();

S3Client s3Client = S3ClientFactory.getS3Client(s3Config);

4.1.2. 서비스 인스턴스 생성

  • 일반 파일용: S3FileService
  • 이미지 파일용: S3ImageService (MIME 타입 감지 전략 지정 가능 / 미지정시 기본값은 FAST 전략)
    • 고속(FAST) 전략 : 파일 헤더 일부를 분석하여 검사합니다. 빠르지만 의도적인 조작을 탐지하기는 어렵습니다. (tika-core 기반의 감지 방식)
    • 정밀(PRECISE) 전략 : 파일 내부 시그니처를 분석하여 검사합니다. 정확도는 높지만, 환경에 따라 메모리 및 디스크 I/O 오버헤드가 발생할 수 있습니다. (tika-parsers 기반의 분석 방식)
// 일반 파일 서비스
S3FileService fileService = new S3FileService(s3Client, "your-bucket-name");

// 이미지 서비스 (기본: FAST 전략)
S3ImageService defaultImageService = new S3ImageService(s3Client, "your-bucket-name");

// 이미지 서비스 (지정: FAST 전략)
S3DetectionProperties fastProps = new S3DetectionProperties();
fastProps.setMimeDetection(S3DetectionProperties.MimeDetection.FAST);
MimeTypeDetectionStrategy fastDetector = MimeTypeDetectionStrategyFactory.from(fastProps);
S3ImageService fastImageService = new S3ImageService(s3Client, "your-bucket-name", fastDetector);

// 이미지 서비스 (지정: PRECISE 전략)
S3DetectionProperties preciseProps = new S3DetectionProperties();
preciseProps.setMimeDetection(S3DetectionProperties.MimeDetection.PRECISE);
MimeTypeDetectionStrategy preciseDetector = MimeTypeDetectionStrategyFactory.from(preciseProps);
S3ImageService preciseImageService = new S3ImageService(s3Client, "your-bucket-name", preciseDetector);

4.1.3. 파일 업로드

S3FileServiceupload 메서드를 사용하여 파일을 업로드합니다. 업로드 결과로 S3UploadResult 객체가 반환되며, 성공 시 S3 URL을 포함합니다.

File file = new File("path/to/your/file.txt");
String objectKey = "my-folder/my-file.txt";

try {
  S3UploadResult result = fileService.upload(objectKey, file);
  System.out.println("업로드 성공 URL: " + result.getUrl());
} catch (ValidationException e) {
  log.error("입력값이 유효하지 않습니다: {}", e.getMessage());
} catch (AwsGlobalException e) {
  log.error("AWS 설정 오류가 발생했습니다: {}", e.getMessage());
} catch (S3OperationException e) {
  log.error("S3 작업 중 오류 발생 (HTTP: {}, Code: {}): {}", e.getHttpStatusCode(), e.getAwsErrorCode(), e.getMessage());
} catch (Exception e) {
log.error("알 수 없는 오류가 발생했습니다.", e);
}

구체적인 예외 처리 가이드는 '5. 예외 처리 가이드' 부분을 참고하세요.

4.1.4. 이미지 업로드

S3ImageService를 사용하여 이미지를 업로드합니다. S3ImageService 인스턴스 생성 시 지정한 감지 전략에 따라 유효한 이미지인지 검사하여 안정성을 높입니다.

File imageFile = new File("path/to/your/image.png");
String objectKey = "my-images/my-avatar.png";

try {
  S3UploadResult result = imageService.upload(objectKey, imageFile);
  System.out.println("이미지 업로드 성공 URL: " + result.getUrl());
} catch (ValidationException e) {
  log.error("입력값이 유효하지 않습니다: {}", e.getMessage());
} catch (AwsGlobalException e) {
  log.error("AWS 설정 오류가 발생했습니다: {}", e.getMessage());
} catch (S3OperationException e) {
  log.error("S3 작업 중 오류 발생 (HTTP: {}, Code: {}): {}", e.getHttpStatusCode(), e.getAwsErrorCode(), e.getMessage());
} catch (Exception e) {
log.error("알 수 없는 오류가 발생했습니다.", e);
}

구체적인 예외 처리 가이드는 '5. 예외 처리 가이드' 부분을 참고하세요.

S3ImageService 인스턴스 생성 가이드는 '4.1.2. 서비스 인스턴스 생성' 부분을 참고하세요.

4.2. Spring Framework 통합 예시

@Configuration 클래스를 사용하여 S3 관련 빈(Bean)을 등록하고, @Service에서 편리하게 주입받아 사용할 수 있습니다.

4.2.1. S3 관련 Bean 설정 (S3Config.java)

@Configuration
public class S3Config {

    @Value("${aws.s3.access-key}")
    private String accessKey;

    @Value("${aws.s3.secret-key}")
    private String secretKey;

    @Value("${aws.s3.region}")
    private String region;

    @Value("${aws.s3.bucket}")
    private String bucket;

    // S3AccessConfig Bean 설정
    @Bean
    public S3AccessConfig s3AccessConfig() {
        return S3AccessConfig.builder()
                .withCredentials(accessKey, secretKey)
                .withRegion(region)
                .build();
    }

    // S3Client Bean 설정
    @Bean
    public S3Client s3Client(S3AccessConfig s3AccessConfig) {
        return S3ClientFactory.getS3Client(s3AccessConfig);
    }
    
    // S3FileService Bean 설정
    @Bean
    public S3FileService s3FileService(S3Client s3Client) {
        return new S3FileService(s3Client, bucket);
    }

    // S3ImageService Bean 설정 (아래 두 가지 방법 중 하나를 선택, 둘 다 사용시 @Primary 명시)
    // 방법 1: 정확성이 중요한 경우 (PRECISE 전략)
    @Bean
    @Primary // 여러 S3ImageService Bean 중 우선권을 가짐
    public S3ImageService s3ImageServicePrecise(S3Client s3Client) {
        S3DetectionProperties preciseProps = new S3DetectionProperties();
        preciseProps.setMimeDetection(S3DetectionProperties.MimeDetection.PRECISE);
        MimeTypeDetectionStrategy preciseDetector = MimeTypeDetectionStrategyFactory.from(preciseProps);
        return new S3ImageService(s3Client, bucket, preciseDetector);
    }
    // 방법 2: 속도가 중요한 경우 (FAST 전략 - 기본값)
    @Bean
    public S3ImageService s3ImageServiceFast(S3Client s3Client) {
        return new S3ImageService(s3Client, bucket);
    }
    
}

4.2.1. 서비스 레이어에서 사용 예시 (MyFileService.java)

@Service
@RequiredArgsConstructor
public class MyFileService {
    
    private final S3FileService s3FileService;
    
    // 여러 개의 S3ImageService Bean을 등록한 경우, @Primary로 지정된 s3ImageServicePrecise Bean이 기본으로 주입됩니다.
    // 만약 다른 전략을 사용하고 싶다면 @Qualifier("s3ImageServiceFast")와 같이 사용하세요.
    private final S3ImageService s3ImageService;

    public String uploadProfileImage(MultipartFile imageFile) throws RuntimeException {
        String key = "profiles/" + UUID.randomUUID() + ".jpg";
        S3UploadResult result = s3ImageService.upload(
            key,
            imageFile.getInputStream(),
            imageFile.getContentType(),
            imageFile.getSize()
        );
        
        if (result.isSuccess()) {
            return result.getUrl();
        }
        throw new RuntimeException("이미지 업로드 실패");
    }
}

5. 예외 처리 가이드

WSS S3 Service는 안정적인 S3 상호작용을 위해 엄격한 입력값 검증 및 체계적인 예외 계층을 제공합니다. 모든 예외는 RuntimeException을 상속하며, 세 가지 최상위 예외 클래스로 구분됩니다. 이를 통해 사용자는 발생 가능한 오류를 일관성 있게 분류하고 상황에 맞게 처리할 수 있습니다.

  • ValidationException : 입력값 검증 예외
  • AwsGlobalException : AWS 전역 설정 예외
  • S3OperationException : S3 작업 예외

5.1. 예외 계층 구조

RuntimeException
│   
├── ValidationException (입력값 검증 최상위 예외 객체)
│   └── InvalidAccessKeyException (AccessKey 입력값 예외 객체)
│   └── InvalidSecretKeyException (SecretKey 입력값 예외 객체)
│   └── InvalidBucketNameException (Bucket 입력값 예외 객체)
│   └── InvalidObjectKeyException (ObjectKey 입력값 예외 객체)
│   └── InvalidContentTypeException (ContentType 입력값 예외 객체)
│   └── InvalidContentLengthException (ContentLength 입력값 예외 객체)
│   └── InvalidFileException (File 입력값 예외 객체)
│   └── InvalidImageException (Image 입력값 예외 객체)
│   
├── AwsGlobalException (AWS 전역 설정 최상위 예외 객체)
│   └── AwsCredentialsNotFoundException (AWS 자격 증명 예외 객체)
│   └── AwsRegionNotFoundException (AWS 리전 예외 객체)
│   
└── S3OperationException (S3 작업 최상위 예외 객체)
    └── S3BucketNotFoundException (S3 버킷 예외 객체)
    └── S3ObjectKeyNotFoundException (S3 객체 키 예외 객체)
    └── S3AccessDeniedException (S3 권한 예외 객체)
    └── S3EntityTooLargeException (S3 업로드 파일 예외 객체)

5.1. 예외 종류

5.1.1. 입력값 검증 예외 (ValidationException)

라이브러리 사용자의 입력값이 사전에 정의된 규칙을 위반했을 때 발생합니다. S3에 요청을 보내기 전에 클라이언트 측에서 방지할 수 있는 오류입니다.

  • InvalidAccessKeyException (AccessKey)

    • null 또는 비어 있을 수 없습니다.
    • 길이는 16자 이상 128자 이하여야 합니다.
    • 대문자 알파벳과 숫자로만 구성되어야 합니다.
    • AKIA 또는 ASIA로 시작해야 합니다.
  • InvalidSecretKeyException (SecretKey)

    • null 또는 비어 있을 수 없습니다.
  • InvalidBucketNameException (Bucket)

    • null 또는 비어 있을 수 없습니다.
    • 길이는 3자 이상 63자 이하여야 합니다.
    • 소문자, 숫자, 하이픈(-), 점(.)만 포함할 수 있습니다.
    • 문자 또는 숫자로 시작하고 끝나야 합니다.
    • 연속된 점(..)을 포함할 수 없습니다.
    • IP 주소 형식일 수 없습니다.
    • 금지된 접두사(xn--, sthree- 등)로 시작할 수 없습니다.
    • 금지된 접미사(-s3alias, --ol-s3 등)로 끝날 수 없습니다.
  • InvalidObjectKeyException (ObjectKey)

    • null 또는 비어 있을 수 없습니다.
    • 길이는 1024자를 초과할 수 없습니다.
    • 금지된 문자(\:*?"<>|)를 포함할 수 없습니다.
    • 금지된 경로 시퀀스(//, ./, /.)를 포함할 수 없습니다.
    • / 또는 \로 시작할 수 없습니다.
    • . 또는 ..일 수 없습니다.
  • InvalidContentTypeException (ContentType)

    • null 또는 비어 있을 수 없습니다.
    • 유효한 MIME 타입 형식(type/subtype)이어야 합니다.
    • S3ImageService 사용 시, 허용된 이미지 타입(image/jpeg, image/png 등)이어야 합니다.
  • InvalidContentLengthException (ContentLength)

    • 0보다 커야 합니다.
  • InvalidFileException (File)

    • File 객체가 null이거나, 존재하지 않거나, 디렉토리일 경우 발생합니다.
  • InvalidImageException (Image)

    • S3ImageService 사용 시, 파일이 허용된 이미지 형식이 아닐 때 발생합니다.

5.1.2. AWS 전역 설정 예외 (AwsGlobalException)

S3 클라이언트를 설정하거나 AWS 자격 증명을 확인하는 단계에서 발생하는 오류입니다.

  • AwsCredentialsNotFoundException

    • AWS 자격 증명을 찾을 수 없을 때 발생합니다.
  • AwsRegionNotFoundException

    • 유효하지 않은 AWS 리전을 설정했을 때 발생합니다.

5.1.3. S3 작업 예외 (S3OperationException)

S3 서비스와 통신하는 과정에서 발생하는 모든 클라이언트 측, 서버 측 오류를 포함합니다.

  • S3BucketNotFoundException

    • 작업을 요청한 버킷을 찾을 수 없을 때 발생합니다.
  • S3ObjectKeyNotFoundException

    • 객체 키를 찾을 수 없을 때 발생합니다.
  • S3AccessDeniedException

    • S3 서비스 요청에 대한 권한이 없을 때 발생합니다.
  • S3EntityTooLargeException

    • 업로드하려는 파일이 너무 클 때 발생합니다.

S3OperationException은 자체적으로 awsErrorCode, awsRequestId, awsExtendedRequestId, httpStatusCode 와 같은 메타데이터를 제공하며, 사용자는 getter 메서드를 통해 해당 정보에 접근할 수 있습니다. 단, 오류 상황에 따라 S3OperationException이 포함하는 메타데이터의 제공 범위에는 차이가 있을 수 있습니다. 상세 내용은 다음의 표와 같습니다.

오류 유형 awsErrorCode awsRequestId awsExtendedRequestId httpStatusCode
AWS S3 서비스 오류 실제 값(String) 실제 값(String) 실제 값(String) 실제 값(int)
그 외 AWS 서비스 오류 null 실제 값(String) 실제 값(String) 실제 값(int)
AWS 클라이언트 오류 null null null -1

5.2. 예외 처리 예시

try {
    S3UploadResult result = fileService.upload("test/my-file.txt", file);
    // ... 성공 로직
    
} catch (InvalidImageException e) {
   // 유효성 검증 실패: 이미지 형식 실패를 별도로 처리
   log.error("업로드할 수 없는 이미지 파일입니다: {}", e.getMessage());
   
} catch (ValidationException e) {
   // 유효성 검증 실패: 그 외 유효성 검증 오류(객체 키 규칙 위반 등)를 포괄적으로 처리
   log.error("입력값이 유효하지 않습니다: {}", e.getMessage());
   
} catch (AwsRegionNotFoundException e) {
   // AWS 전역 설정 실패: 리전 설정 오류를 별도로 처리
   log.error("리전이 유효하지 않습니다: {}", e.getMessage());
   
} catch (AwsGlobalException e) {
   // AWS 전역 설정 실패: 그 외 AWS 전역 설정 오류(자격 증명 등)를 포괄적으로 처리
   log.error("AWS 설정 오류가 발생했습니다: {}", e.getMessage());
   
} catch (S3BucketNotFoundException e) {
   // S3 작업 실패: 특정 케이스(버킷 없음)를 별도로 처리
   log.error("S3 버킷 '{}'를 찾을 수 없습니다. (Request ID: {})", e.getBucketName(), e.getAwsRequestId());
   
} catch (S3OperationException e) {
   // S3 작업 실패: 그 외 S3 관련 오류(권한, 네트워크 등)를 포괄적으로 처리
   log.error("S3 작업 중 오류 발생 (HTTP: {}, Code: {}): {}", e.getHttpStatusCode(), e.getAwsErrorCode(), e.getMessage());
   
} catch (Exception e) {
    log.error("알 수 없는 오류가 발생했습니다.", e);
}

6. 프로젝트 구조

src/main/java/org/websoso/s3/
├── config/         # S3 접속 정보 및 MIME 타입 감지 전략 설정
├── core/           # S3 핵심 서비스 (업로드, 조회, 삭제) 및 전략 인터페이스
│   └── strategy/   # MIME 타입 감지 전략 구현체
├── exception/      # 커스텀 예외 클래스 (aws/s3, aws/global, validation)
├── factory/        # S3 클라이언트 및 전략 객체 생성 팩토리
└── modle/          # S3 관련 데이터 모델 (AccessKey, SecretKey, Bucket, 응답 객체 등)

7. 빌드 및 테스트

빌드

프로젝트 루트 디렉토리에서 다음 명령어를 실행하여 프로젝트를 빌드할 수 있습니다.

./gradlew build

테스트

다음 명령어를 통해 유닛 테스트를 실행할 수 있습니다.

./gradlew test