본문 바로가기
Library&Framework/Spring Boot

[문제해결]이미지를 등록하고나서 , 이미지가 바로 나오지 않는 이유

by 우지uz 2024. 6. 4.

프로필 이미지를 등록하는 시나리오 및 과정

 

현재 진행중인 프로젝트에서 , 프로필 페이지에서

회원 정보 수정 페이지로 들어갔을 때 ,
프로필 이미지를 POST(업데이트) 하는 

 

연필모양의 버튼을 누르게 되면,
프로필 이미지를 등록하는 모달창이 나오게 됩니다.

이 모달창에서는, 
1. 이미지 파일을 선택하고
2. 이미지 파일 미리보기에서 이미지를 크롭하고
3. 프로필 이미지를 등록하는 순서로 프로세스가 진행됩니다.

Crop Image 를 누르면, Apply Profile Image 버튼이 활성화된다.

Crop Image 를 누르면, 무조건 Apply Profile Image 버튼이 활성화 되는 것은 아닙니다.

const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB

// 프로필 이미지 크롭 헨들러
const handleCropImage = async () => {
setCanvasPreview(
  imgRef.current,
  previewCanvasRef.current,
  convertToPixelCrop(crop, imgRef.current.width, imgRef.current.height)
);

let quality = 0.8; // 초기 품질 설정
let blob;

do {
  // Blob으로 이미지 품질 조정
  const currentQuality = quality; // 루프 변수 캡처
  blob = await new Promise((resolve) => {
    previewCanvasRef.current.toBlob(
      (blob) => resolve(blob),
      "image/jpeg",
      currentQuality
    );
  });

  console.log(
    "Current quality:",
    quality,
    "Size:",
    (blob.size / (1024 * 1024)).toFixed(2),
    "MB"
  ); // MB 단위로 출력

  if (blob.size > MAX_FILE_SIZE) {
    quality -= 0.1; // 파일 크기가 너무 크면 품질 낮춤
  }
} while (blob.size > MAX_FILE_SIZE && quality > 0);

console.log(
  "Final quality:",
  quality,
  "Size:",
  (blob.size / (1024 * 1024)).toFixed(2),
  "MB"
);

const url = URL.createObjectURL(blob);
setCroppedImageUrl(url); // 크롭된 이미지 URL 설정
console.log(url);

if (blob.size > MAX_FILE_SIZE) {
  setIsFileTooLarge(true); // 파일 크기가 2MB를 초과할 경우 상태 설정
} else {
  setIsFileTooLarge(false); // 파일 크기가 2MB 이하일 경우 상태 설정
}
};

handleCropImage 버튼을 누르고 난 후에, 프로필 이미지 Apply 버튼을 누르게 되기 때문에
크롭 이후의 이미지 품질이나 해상도에 대해서 조절해주어야 합니다.

실제로 Cropped Image 품질을 조정 해주지 않으면
원본 이미지의 크기가 500KB 였던 것이, 크롭한 후

Cropped image size: 7208916 Bytes = 7041.7 KB = 6.87 MB 
까지 올라간 것을 확인할 수 있었습니다.

이대로 서버에 프로필 이미지를 등록하면,

서버에서 설정해둔 파일의 최대 사이즈를 넘기게되어,
영문도 모른체 500 server error 를 반환해주었습니다.

위 코드에서 핵심 코드는

let quality = 0.8; // 초기 품질 설정
let blob;

do {
  // Blob으로 이미지 품질 조정
  const currentQuality = quality; // 루프 변수 캡처
  blob = await new Promise((resolve) => {
    previewCanvasRef.current.toBlob(
      (blob) => resolve(blob),
      "image/jpeg",
      currentQuality
    );
  });

  console.log(
    "Current quality:",
    quality,
    "Size:",
    (blob.size / (1024 * 1024)).toFixed(2),
    "MB"
  ); // MB 단위로 출력

  if (blob.size > MAX_FILE_SIZE) {
    quality -= 0.1; // 파일 크기가 너무 크면 품질 낮춤
  }
} while (blob.size > MAX_FILE_SIZE && quality > 0);

초기 품질을 80% 로 설정하고, 2MB 를 넘어갈 시, do - while 문을 통해서

크롭 이미지 품질을 10% 씩 반복해서 저하시키도록 해준 부분입니다.
(혹시나 참고하실 분들을 위해.. )

 

그렇게 프로필 이미지를 등록하고, 프로필 이미지를 확인한 결과

콘솔에서는 정상적으로 프로필 이미지 url Path 를 반환하고 있음이 확인 되었고
/uploads/profileimg/3a8aca43b2d249848e98caf07f737964_croppedImage.jpeg

프로필 이미지가 랜더링되어서 나오지 않고, 

404 Not Found 에러와 함께, 찾을 수 없는 페이지가 나오는 것을 확인할 수 있었습니다.

 

여기서 상황에 대한 분석을 해보면, 

클라이언트 측에서는,

1. 정상적으로 이미지 파일을 Blob 화 시켜서, 
2. 서버의 프로필 이미지 POST api 에 요청을 보내고,
3. 응답을 받아왔으며
4. 그 응답으로, 프로필 이미지 url 을 클라이언트측에 전달 했음을 알 수 있는데

서버 측에서는, 

1. 정상적으로 프로필 엔티티의 ImageName, ImagePath 가 등록되었고
2. ImagePath 를 클라이언트 측에 전달 했음을 알 수 있었지만
3. 클라이언트에서 http://localhost:8080/uploads/profileimg/3a8aca43b2d249848e98caf07f737964_croppedImage.jpeg
를 요청 했을 때, 서버에 등록되어 있는 이미지 url 리소스를 가져오지 못하고
404 Not Found 에러를 반환해주는 것을 확인할 수 있었습니다.

의심 가는 상황(문제에 대한 원인)

 

1. 클라이언트 측에서 이미지 형식 및 이미지 파일의 크기
(이미지를 Base64 데이터 URL 대신 Blob URL로 처리 - 크기 및 네트워크 속도 향상)
이외에는 클라이언트에서 문제될 것이 없다고 판단.

2. 브라우저 & 서버 Cache
브라우저 캐시는 원인이 아니었으며, 서버에서 이미지 캐싱을 Redis cache 로 적용)

3. 서버 응답 시간 체크

2024-06-04 --- Initial file size: 121455 bytes
2024-06-04 --- Final file size: 14783 bytes
2024-06-04 --- Final file name: 3a8aca43b2d249848e98caf07f737964_croppedImage.jpeg
2024-06-04 --- Total time taken: 463 ms

400~500ms 정도로 준수하게 나오도록 , 이미지 resize 함수를 리펙토링. 

4. 이미지 포함, 리소스 파일에 대한 Configuration 등록

스프링 부트가 정적 리소스를 제공하는 과정

Spring 5 - How to provide static resources

스프링 부트에서는 이미지 url 에 대해서 먼저 
1. 서블릿 컨테이너를 뒤져본다. 컨트롤러에 등록되지 않으면
2. ResourceHttpRequestHandler클래스의
handleRequest 메소드에서 같은 클래스의
getResource 메소드를 호출한 뒤 응답하는 방식이다.

 

1번. 2번. 3번의 경우에는 응답 속도가 빨라졌지만
서버에서 이미지 리소스 url 에 대한 404 Not Found Error 를 해결해주진 못했지만

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * Adds a resource handler to the provided registry.
     * This method maps the "/uploads/**" path pattern to the "file:src/main/resources/static/uploads/" location,
     * allowing these resources to be served by the web server.
     *
     * @param registry the registry to which the resource handler is added
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //  "/uploads/**" 경로로 시작하는 요청이 "src/main/resources/static/uploads/" 디렉토리에서 정적 파일을 제공할 수 있습니다.
        registry.addResourceHandler("/uploads/**")
                .addResourceLocations("file:src/main/resources/static/uploads/");
    }
}

WebMvcConfigurer를 구현한 구현체에서 addResourceHandlers 함수를 Override 해주는데, 위 설정은

      "/uploads/**" 경로로 시작하는 요청이
"src/main/resources/static/uploads/" 디렉토리에서 정적 파일을 제공할 수 있다는 의미이다.

이후 프로필 이미지를 등록하고, 이미지 url 에 대한 요청을 
서버에서 곧바로 정상적으로 반환해주었다.

프로필 이미지를 등록하고 난 후, 정상적으로 
원하는 모든 곳에서 프로필 이미지에 대한 State 가 적용되고
서버 리소스 url 을 정상적으로 응답해주는 것을 확인할 수 있었습니다. 

 

시도한 것

1. 클라이언트(프론트) 측에서 문제 없으나, 이미지 크기 리사이즈 최적화 완료
2. 백엔드(서버) 측에서, 이미지 크기 리사이즈 최적화 및 캐싱 완료
(배포하게 되면, s3 에 이미지 파일 관리 예정)
3. Controller -> Servlet Container -> Resource Handler 순으로 
서버에 대한 리소스 파일 응답이 이루어 지므로, 곧바로 찾을 수 있도록
Resource Handler 를 등록하자. 

 

느낀 점

프로필 및 회원 관련해서 프론트 및 백 둘다 공부하다보니

처음에는 어디서 문제의 원인이 있는 건지 , 유추하기가 쉽지 않았습니다.

 

클라이언트 측의 request ? 서버측의 api response?
전달하는 과정에서의 문제일까? 혹시 트렌젝션 처리가 잘못 되었나?
이미지 url 설정이 잘못되었나?

전부다 아니었습니다. 그렇지만, 아니라는 것을 깨달았기 때문에
Resource Configuration 설정 문제라는 것을 확인할 수 있었던 것 같습니다.