본문 바로가기
Today I Learned 2024. 7. 23.

(24.07.23)[15주차] MinIO 활용 정리 - 코드 분석

S3대신 개발/테스트 환경으로 MinIO를 사용하기로 결정하여 학습하고 검색한 대로 패턴을 보면서

비지니스 로직을 Service 단에 구성을 했다.

하지만, JSON에서 한 필드의 값으로 받아온 String 타입의 텍스트를 txt 파일로 바꾼뒤에, MinIO 서버의 버킷에 저장하고 다운로드 하는 코드의 구조를 한번 더 점검하면서,

 

추후 필요할 경우에 능동적으로 사용할 수 있도록 분석한 부분을 정리


사전 세팅

  • code 는 RequestDTO의 코드 리뷰의 코드 부분을 Spring 으로 String 타입으로 가져온 텍스트
  • codReviewId는 code 부분을 제이한 코드 리뷰의 제목, 내용을 담은 객체로 이미 DB에 save에 되에서 PK인 해당 객체의 ID
  • minioClient 는 이미 주입받은 형태

업로드

String filename = "codereviews-code/code-" + codeReviewId + ".txt";

    if (code != null && !code.isEmpty()) {
      try {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(code.getBytes(
            StandardCharsets.UTF_8));

        minioClient.putObject(
            PutObjectArgs.builder()
                .bucket("project-dev-bucket")
                .object(filename)
                .stream(inputStream, inputStream.available(), -1)
                .contentType("text/plain")
                .build()
        );
      } catch (Exception e) {
        throw new CustomException(ErrorCode.FILE_UPLOAD_FAILED);
      }
    }
ByteArrayInputStream inputStream = new ByteArrayInputStream(code.getBytes(
            StandardCharsets.UTF_8));
  • code 는 문자열 String이기때문에 인코딩 타입을 지정해서 텍스트 파일로 만들 수 있도록 인코딩을 진행
    • 위해서는 UTF_8 타입으로 정의하여 다양한 문자 기호를 지원하고 저장이 가능
  • getBytes() String 메서드로 String을 바이트 배열=일반 문자배열 로 변환하는 메서드
    • ()안에 StandardCharsets 문자 인코딩을 지정해서 바꿀 수 있음
    • “UTF_8” 처럼 문자열 통째로 리터럴로 사용할 수 있지만, 명확하게 지정해서 수정이나, 코드의 흐름을 빠르게 파악하기 위해서 정확하게 지정하는게 보편적인 사용방법
  • ByteArrayInputStream 바이트 배령의 스트림화를 시켜서 메모리에 있는 바이트 배열을 직접 읽고 쓸수 잇도록 바꾸는 것
    • InputStream 으로 바꿔서 데이터를 읽을 수 있도록 하는 것, 여기에서는 code 의 바이트=문자배열 데이터를 읽기 위해 세팅을 하는 것
minioClient.putObject(
            PutObjectArgs.builder()
                .bucket("project-dev-bucket")
                .object(filename)
                .stream(inputStream, inputStream.available(), -1)
                .contentType("text/plain")
                .build()
                );
  • minioClient를 통해 minio 버킷으로 업로드 하는 단계
  • putObject() 메서드는 ()안의 파일을 업로드시킬 수 있는 기능을 수행하는 메서드
  • PutObjectArgs.builder() MinIO의 파일을 업로드할 때, 여러 정보들을 인수들로 설정하는데 사용할 수 있는 빌더 클래스의 일종
    • 따라서 builder()로 생성해줘야함
    • bucket 저장할 MinIO버킷 이름을 지정
    • object 업로드할 파일 객체의 이름, S3와 똑같이 경로까지 포함된 동적인 이름을 가지고 있음
      • 위에서는 code-객체ID.txt 파일이 버킷안에 codereviews-code 경로 안에 만들어지게 됨
  • stream(inputStream, inputStream.available(), -1)
    • inputStream.available() 스트림에서 읽을 수 있는 바이트 수를 환산 = 업로드 데이터 길이
    • -1 업로드할 데이터의 크기를 지정하는 부분인데, -1로 지정을 하면 전체를 업로드 한다는 의미
    → 스트림 형식으로 문자열데이터 = 바이트 단위로 읽혀서 업로드를 하는 것
  • contentType 파일 콘텐츠 타입으로 지정
    • 위에서는 txt파일로 저장하는 것
    • MIME 타입으로 자유롭게 바꿀 수 있음
    • Java
      • 파일 확장자: .java
      • MIME 타입: text/x-java-source
    • Python
      • 파일 확장자: .py
      • MIME 타입: text/x-python
    • JavaScript
      • 파일 확장자: .js
      • MIME 타입: application/javascript
    • C++
      • 파일 확장자: .cpp (또는 .h for headers)
      • MIME 타입:
        • text/x-c++src (for source files)
        • text/x-c++hdr (for header files)
  • try-catch 문을 통한 예외처리
    • 업로드는 네트워크와 I/O 에대해 오류를 처리하는 부분으로 Java Spring단에서가 아닌 네트워크 MinIO 서버, 파일 읽기쓰기 문제 등 다양하게 발생할 수 있기 때문에, 예외를 발생할경우 사용자에게 빠르게 알려서 이를 처리할 수 있도록 세팅

다운로드

 String code;

    try (InputStream stream = minioClient.getObject(
        GetObjectArgs.builder()
            .bucket("project-dev-bucket")
            .object(codeReview.getCode())
            .build())) {
      ByteArrayOutputStream result = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int length;
      while ((length = stream.read(buffer)) != -1) {
        result.write(buffer, 0, length);
      }
      code = result.toString(StandardCharsets.UTF_8.name());
    } catch (Exception e) {
      throw new CustomException(ErrorCode.FILE_DOWNLOAD_FAILED);
    }
  • ResponseDTO에 담아서 보낼 code에 다운로드 받아온 txt 파일을 String화 시켜서 JSON타입으로 응답하려는 코드
  • minioClient.getObject() putObject와 반대로 ()안의 내용을 바탕으로 MinIO 버킷에서 해당 파일을 가져올 수 있음
    • putObject와 똑같고, InputStream 으로 일단 가져옴
    • ByteArrayInputStream 은 메모리에서 시작된 데이터를 읽기 스트림으로 가져오는 것을 목적으로 다운로드시에는 맞지 않음
  • try-with-resources 문의 사용
    • {} 부분에는 AutoCloseable 구현 객체만 들어올 수 있는데 InputStream은 AutoCloseable 구현이 된 객체이기때문에 I/O작업에서 Resource를 close()하는데 많이 사용하는 것
    • 다운로드 중에 계속 close() 없이 계속 열려있으면 리소스 누수가 발생하면서 계속 해서 메모리에 쌓이게 될 수 있기 때문에try-with-resources 문을 사용할 수 밖에 없음
  • ByteArrayOutputStream 데이터를 메모리에 저장하면서 내부에서 처리할 수 있도록 하게끔 하기 위해 생성
  • byte[] buffer = new byte[1024]; 1024 바이트 크기의 어떤 바이트 배열을 생성을해서 스트림에서 읽어오는 데이터들 저장하는데에 사용
    • 즉 1KB를 가지고 오기 위한 배열이 되는 것
int length;
      while ((length = stream.read(buffer)) != -1) {
        result.write(buffer, 0, length);
      }
  • 1024바이트 =1KB를 읽어온 데이터를 계속 result(ByteArrayOutputStream)에 기록을 하게 되는데, stream.read(buffer)를 통해 stream=MinIO에서 buffer크기 만큼 가져온 데이터를(0부터 읽은 크기=length)만큼 result에 넣기 시작함
  • 다 읽었으면 당연히 stream 자체가 다 읽혔기 때문에 length는 자동적으로 다 읽었다는 의미로 -1 이 반환이 되기 때문에 결국 모두 다 읽어올 때까지 result에 쓴다고 보면 됨
  • result.toString(StandardCharsets.UTF_8.name());
    • 다시 String으로 바꾸는 데 앞서서 UTF_8형식으로 인코딩을 했기 때문에 똑같은 UTF_8로 디코딩
  • toString은 디코딩 타입을 무조건 문자열로 받아야하기 때문에 “UTF_8” 로 써야하지만 업로드때와 마찬가지로 명확하게 지정하기 위해서 name() 을 사용

 


업로드 다운로드가 정상적으로 이뤄져있고 WebUI를 통해서도 정상적올 업로드가 된 것을 확인할 수 있었다.

좀더 코드를 분석하면서 어떻게 작동이 되고 있는지를 확인하여 다양하게 추후에 확장할 수 있도록