MOMO의 이미지 서버 구축기

https://github.com/2022-momo/momoimage

 

GitHub - 2022-momo/momoimage: 모모팀 이미지 서버

모모팀 이미지 서버. Contribute to 2022-momo/momoimage development by creating an account on GitHub.

github.com

 

어떤 일이 있었길래?

모두모여라 프로젝트 진행 중, 기존에는 모임 카테고리별 고정 이미지를 제공하였습니다. 그렇다 보니 모임 관련해서 모임명이 다르더라도 모든 썸네일이 같아 사용자로 하여금 직관적이지 않을 것 같다는 느낌을 받았습니다.

또한 무엇보다 모임을 대표할만한 썸네일이 아니라는 점이 발목을 잡았습니다. 이에 썸네일 이미지를 따로 유저가 저장하게 하는 방식을 도입하자는 팀 회의 끝에 기능을 도입하게 되었습니다.

 

업로드는 어떻게 해야 할까?

조사를 하다 보니 두가지 방법이 있었습니다. 하나는 S3 버킷을 이용하여 파일을 관리하는 방법이었고, 다른 하나는 직접 스프링을 띄워서 파일을 업로드시키는 방법이었습니다.

S3 버킷을 사용하려다가 우테코 보안 정책에 의해 가로막히는 부분이 많았습니다. 따라서 어쩔수 없이 직접 스프링을 띄워서 파일을 업로드시키자는 결론이 나왔습니다. 따로 서버를 둔 이유는 다음과 같습니다.

 

  1. 이미지 서버에 직접 접근해서 업로드하는것 보다는 기존 서버를 거쳐서 회원 유무를 체크하고, 이미지 서버로 이미지를 전송하는 방식이 더 좋아 보입니다.
  2. EC2의 용량이 적다 보니, 기존 서버에서 이미지 관리까지 담당한다면 이미지가 많이 쌓이게 되면 서버가 터지지 않을까 고민이 많았습니다.

 

따라서, (1)의 방법을 사용하고, MultipartFile을 스프링에서 파라메터로 받을 수 있다고 하여 MultipartFile을 사용하였습니다. 전체적인 구조는 다음과 같습니다.

 

구현하기 위해 참고한 Multipart 관련 자료는 다음과 같습니다.

https://www.baeldung.com/sprint-boot-multipart-requests

 

Multipart Request Handling in Spring | Baeldung

Learn how to send multipart HTTP requests using Spring Boot.

www.baeldung.com

 

업로드 된 이미지를 사용자에게 어떻게 전달해야 할까?

업로드가 다 해결되고 나니, 이미지를 제공할 때 어떻게 해야 될지 생각을 하다 두가지 방법을 생각해 보았습니다.

  1. 클라이언트가 백엔드 서버로 요청을 하면, 백엔드 서버가 이미지 서버로부터 이미지 파일을 가져오도록 구현합니다.
  2. 클라이언트가 직접 이미지 서버로 접근하여 이미지를 가져오도록 구현합니다.

(1)의 방법은, 백엔드 서버가 이미지 서빙을 하기엔 부하가 너무 클것이라고 생각이 되기도 하고, 굳이 이미지를 서버 하나를 거쳐서 전달해야 하나 하는 생각이 있었습니다.

따라서, 이미지 전달은 스프링을 거쳐서 하는것이 아닌 Nginx를 사용하여 정적 파일을 제공하자는 결론이 나왔습니다. 따라서, 이미지 서버에 Nginx를 따로 하나 더 두어 최종적으로 Nginx와 스프링을 동시에 사용하였습니다.

번외로, 고민한 사항

이미지 서버로 파일이 들어오는데, 이를 이미지인지 어떻게 체크할 수 있을까요? 다음과 같은 요소들을 체크하였습니다.

 

첫번째는 ContentType 체크입니다.

  • 들어온 파일에 대해서 ContentType를 체크합니다.(image/jpeg인지, image/png인지)
private static final List<String> IMAGE_CONTENT_TYPES =
            List.of(IMAGE_JPEG_VALUE, IMAGE_PNG_VALUE);

public void validateContentType(MultipartFile file) {
    String contentType = file.getContentType();

    if (contentType == null || isContentTypeNotImage(contentType)) {
        throw new ImageException(String.format("올바른 컨텐츠 타입이 아닙니다. [%s]", contentType));
    }
}

private boolean isContentTypeNotImage(String contentType) {
    return IMAGE_CONTENT_TYPES.stream()
            .noneMatch(contentType::equals);
}

 

두번째는 정말 이미지 파일이 맞는가에 대한 검증입니다.

  • Content-Type만 지정하기에는 데이터가 바뀔 수 있다는 점에서 추가 검증을 진행합니다.
  • ImageIO.read 메서드는 읽어들인 데이터가 이미지가 아닌 경우 null을 반환합니다.
public void validateFileIsImage(MultipartFile multipartFile) {
    try {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(multipartFile.getBytes());
        BufferedImage read = ImageIO.read(byteArrayInputStream);
        if (read == null) {
            throw new ImageException("올바른 이미지 파일이 아닙니다!!");
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

 

세번째는 MultiPartFile의 용량 제한입니다.

  • 기본적으로 스프링에서는 1MB로 용량을 제한하고 있는데, 이를 늘려야 하는지, 줄여야 하는지 생각해 본 결과, 1MB정도면 적당하다고 판단되어 조절하진 않았습니다.

네번째는 중복 파일 명 체크입니다.

  • 이미지 저장 시에, 중복된 파일명이 나타날 수도 있을거라 생각해 파일명은 임의로 UUID 메서드를 사용하여 파일명을 구성하도록 구현하였습니다.
String extension = extractExtension(multipartFile.getOriginalFilename());
String changedFileName = UUID.randomUUID().toString() + "." + extension;

 

  1.  

+ Recent posts