Spring Boot, React, AWS S3, AWS CloudFront를 활용한 파일 업로드/다운로드 기능 구현하기 1️⃣ - AWS S3 연결하기

조경민·2024년 4월 3일
0
post-thumbnail

지난 포스트에서 Spring Boot와 React, AWS S3를 활용한 파일 업/다운로드 기능을 구현해보았습니다. 이번 시간에는 백엔드와 프론트엔드는 동일한 구성으로 유지하고 AWS의 S3, CloudFront CDN과 연결해보는 실습을 진행하겠습니다. 본 포스트는 다음과 같이 2부로 나뉘어 게시됩니다.

1. Spring Boot, React, AWS S3, AWS CloudFront 활용한 파일 업로드/다운로드 기능 구현하기 1️⃣ - AWS S3 연결하기

2. Spring Boot, React, AWS S3, AWS CloudFront 활용한 파일 업로드/다운로드 기능 구현하기 2️⃣ - AWS S3에 CloudFront 적용하기

AWS

S3 - Simple Storage Service

S3는 AWS에서 제공하고 있는 오브젝트 스토리지입니다. 2021년 3월 발표된 AWS 블로그 포스트에 따르면 당시 기준 S3가 총 100조 개의 오브젝트를 저장하고 있으며, 초당 수천만 개의 요청을 처리하고 있다는 엄청난 수치를 기록하였다고 하는데요, 현재는 이보다도 훨씬 많은 오브젝트가 저장되어있을 것이라고 추정됩니다.
14년 전 AWS에서 S3를 설계할 당시의 디자인 원칙을 살펴보면 개발자가 오브젝트 스토리지를 간편하게 활용할 수 있도록 여러 고민을 했다는 것을 할 수 있습니다.

탈중앙화: 병목 현상 및 단일 장애 포인트 제거
비동기성: 상황과 무관한 시스템 발전
자율성: 개별 구성 요소가 로컬 정보 기반으로 의사 결정 가능
로컬 책임: 개별 구성요소의 일관성 유지 책임
동시성 제어: 작업 시, 동시성 제어 불필요 및 제한
내결함성: 무중단 혹은 최소 중단으로 작동 지속
제어된 병렬 처리: 복구 성능, 안정성, 신규 노드 도입 등에 병렬 처리 사용
이해가 쉬운 작은 블록단위로 분해: 타 서비스의 블록으로 사용되는 작은 구성요소 구축
대칭성: 기능적으로 동일한 시스템 노드 구성. 작동 시 별도 구성 불필요 혹은 최소한 의 구성
단순성: 가능한 단순한 시스템.

어플리케이션에서 S3를 활용하는 데에 필요한 SDK는 Java 뿐만 아니라, C++, Go, JavaScript, Python, Kotlin, Rust, .NET 등 다양한 언어와 프레임워크를 지원하고 있어 사용성 측면에서도 매우 우수합니다. 컴퓨터의 폴더와 비슷한 역할을 하는 버킷(bucket) 으로 파일을 관리하며 외부에서 파일에 접근할 수 있는 엔드포인트 및 권한도 상황에 맞게 설정할 수 있습니다.

CloudFront

CloudFront는 AWS의 CDN 서비스입니다. 전 세계에 구축된 AWS의 네트워크 에지를 통해 동적 컨텐츠 전송 속도를 향상 시킬 뿐만 아니라 DDos 등의 공격을 방어하는 기술이 적용되어 널리 사용되고 있습니다. 고해상도의 영상을 실시간으로 스트리밍하거나 동영상 강의를 제공하는 서비스, 다양한 미디어 에셋을 활용해야 하는 게임 서비스를 구축 혹은 운영한다면 CDN을 적용하는 것은 선택이 아닌 필수로 여겨지고 있죠. AWS를 메인 인프라로 사용하는 서비스의 경우, AWS 오리진에서 데이터를 송신할 때 무료 정책이 적용되기 때문에 비용 측면에서도 이점을 가집니다.

실습

버전 정보

  • Backend
    • JDK v17
    • Spring Boot 3.2.4
  • Frontend
    • Node.js v18
    • React v17.0.2

준비 사항

  • JDK 17 혹은 그 이상의 버전이 설치된 디바이스
  • Node.js 18 버전이 설치된 디바이스
  • GitHub 계정
  • 클라우드타입 계정
  • AWS 계정 및 아래 정책이 적용된 사용자(루트 사용자 사용 X)
    • AmazonS3FullAccess
    • CloudFrontFullAccess

GitHub 저장소

실습은 아래 저장소의 Spring Boot, React 어플리케이션을 통해 진행됩니다. 저장소를 clone 하거나 fork 해주세요.

따라하기

AWS S3 버킷 생성

  1. AWS S3 페이지에 접속하여 우측의 Create Bucket 버튼을 누릅니다.

  2. General configuration - 버킷의 리전과 이름을 입력합니다.

  3. Object Ownership - ACLs Enabled, Object Writer를 선택합니다.

  4. Block Public Access settings for this bucket - 이미지와 같이 체크박스에 체크한 후 하단의 Create Bucket 버튼을 누릅니다.

  5. 정상적으로 버킷이 생성되면 이미지와 같이 생성된 버킷을 확인할 수 있습니다.

Backend(Spring Boot) 배포

  1. 클라우드타입에 로그인 후 우측 네비바의 ➕ 버튼을 눌러 새 프로젝트 창을 띄우고 프로젝트 이름과 표시 이름을 입력한 뒤 생성하기 버튼을 누릅니다.

  2. build.gradle 파일은 다음과 같습니다. AWS S3 클라이언트를 생성하기 위해 com.amazonaws:aws-java-sdk-s3:1.12.691 가 사용되었습니다.

    plugins {
        id 'java'
        id 'org.springframework.boot' version '3.2.4'
        id 'io.spring.dependency-management' version '1.1.4'
    }
    
    group = 'io.cloudtype'
    version = '0.0.1-SNAPSHOT'
    
    java {
        sourceCompatibility = '17'
    }
    
    repositories {
        mavenCentral()
    }
    
    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'com.amazonaws:aws-java-sdk-s3:1.12.691'
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    
    tasks.named('test') {
        useJUnitPlatform()
    }
  3. S3 클라이언트 관련 설정을 담당하는 S3Config.java 파일의 내용은 다음과 같습니다.

    package io.cloudtype.springfileupload.config;
    
    import com.amazonaws.auth.AWSStaticCredentialsProvider;
    import com.amazonaws.auth.BasicAWSCredentials;
    
    import com.amazonaws.services.s3.AmazonS3;
    import com.amazonaws.services.s3.AmazonS3ClientBuilder;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class S3Config {
    @Value("${aws.s3.region}")
    private String s3Region;

    @Value("${aws.accessKey}")
    private String awsAccessKey;

    @Value("${aws.secretKey}")
    private String awsSecretKey;

    @Bean
    public AmazonS3 s3Client() {

        BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);

        return AmazonS3ClientBuilder
                .standard()
                .withRegion(s3Region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                .build();
    }
}
```
  1. FileUploadController.java 에는 S3 통신하여 파일을 업/다운로드 할 수 있는 라우팅 규칙을 구성하였습니다.

    package io.cloudtype.springfileupload.controller;
    
    import com.amazonaws.services.s3.model.AmazonS3Exception;
    import io.cloudtype.springfileupload.service.FileUploadService;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.core.io.InputStreamResource;
    import org.springframework.core.io.Resource;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.IOException;
    
    @RestController
    @Log4j2
    @RequestMapping("/api")
    public class FileUploadController {
    
        private final FileUploadService fileUploadService;
    
        public FileUploadController(FileUploadService fileUploadService) {
            this.fileUploadService = fileUploadService;
        }
    
        @GetMapping
        public String status() {
            return "OK";
        }
    
        @PostMapping(path = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
        public String uploadFile(@RequestParam("file")MultipartFile file) throws IOException {
            fileUploadService.uploadFile(file.getOriginalFilename(), file);
            return "파일이 스토리지에 업로드 되었습니다.";
        }
    
        @GetMapping("/download/{fileName}")
        public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) throws AmazonS3Exception{
                return ResponseEntity.ok()
                        .contentType(MediaType.APPLICATION_OCTET_STREAM)
                        .body(new InputStreamResource(fileUploadService.getFile(fileName).getObjectContent()));
        }
    
    }
  2. 프로젝트 설정에 진입하여 변수-시크릿 항목에 다음과 같이 AWS 액세스 키 정보를 입력 후 저장합니다. 이 때, AWS의 루트 사용자 인증 정보를 사용하지 않도록 주의합니다.

    • aws-access-key: AWS 사용자 액세스키 ID
    • aws-secret-key: AWS 사용자 시크릿키
  3. 클라우드타입의 프로젝트 페이지에서 ➕ 버튼을 누르고 Spring Boot를 선택한 후, 미리 fork 해놓은 s3-fileupload 를 선택합니다. 기타 설정은 아래를 참고하여 입력한 후 배포하기 버튼을 클릭합니다.

    • 서브 디렉토리: backend
    • 서비스 이름: s3-fileupload-backend
    • 버전: v17
    • 환경변수(Environment Variables)
      • AWS_ACCESS_KEY: 열쇠 아이콘 => aws-access-key
      • AWS_SECRET_KEY: 열쇠 아이콘 => aws-secret-key
      • AWS_S3_BUCKET: 생성한 S3 버킷이름
      • AWS_S3_REGION: ap-northeast-2
  4. 배포가 완료되면 Spring Boot 어플리케이션 페이지의 연결 탭에서 https:// 로 시작되는 URL을 확인합니다. 추후 프론트엔드에서 API를 호출하는 주소로 사용됩니다.

Frontend(React) 배포

  1. ➕ 버튼을 누르고 React 템플릿을 선택한 후, 미리 fork 해놓은 s3-fileupload 를 선택합니다. 기타 설정은 아래를 참고하여 입력한 후 배포하기 버튼을 클릭합니다.

    • 서브 디렉토리: frontend
    • 서비스 이름: s3-fileupload-frontend
    • Node.js 버전: v18
    • 환경변수(Environment Variables)
      • REACT_APP_FILE_UPLOAD_ENDPOINT: [Spring Boot URL]/api/upload
      • REACT_APP_FILE_DOWNLOAD_ENDPOINT: [Spring Boot URL]/api/download
  2. 배포가 완료되면 접속하기 버튼을 눌러 정상적으로 페이지가 로드되는지 확인합니다.

업로드 테스트

  1. 프론트엔드 페이지에 접속한 후 파일을 드래그 앤 드롭하여 업로드 영역으로 옮깁니다.

  2. 업로드 대상 파일이 하단에 표시되며, 업로드를 하거나 파일 선택 취소 작업을 할 수 있습니다. 업로드 버튼을 누릅니다.

  3. 정상적으로 파일이 업로드 되면 아래와 같은 알림창이 표시됩니다.

  4. AWS S3 대시보드 페이지에 접속하여 업로드 한 파일을 확인합니다.

다운로드 테스트

  1. 위에서 업로드 했던 파일을 다운로드 할 수 있는 버튼이 하단에 생성되었습니다. 다운로드 버튼을 누릅니다.

  2. 다운로드가 정상적으로 수행되면 브라우저의 다운로드 창에서 해당 파일을 확인할 수 있습니다.


여기까지 AWS S3에 파일을 업로드 하는 어플리케이션을 구성해보았습니다. 다음 포스트에서는 Cloudfront CDN을 연결하여 S3의 리소스를 AWS의 네트워크 에지에 캐싱하는 실습을 이어서 다뤄보겠습니다.

Reference

profile
Live And Let Live!

0개의 댓글