프로필 이미지를 등록하는 시나리오 및 과정
현재 진행중인 프로젝트에서 , 프로필 페이지에서
회원 정보 수정 페이지로 들어갔을 때 ,
프로필 이미지를 POST(업데이트) 하는
연필모양의 버튼을 누르게 되면,
프로필 이미지를 등록하는 모달창이 나오게 됩니다.
이 모달창에서는,
1. 이미지 파일을 선택하고
2. 이미지 파일 미리보기에서 이미지를 크롭하고
3. 프로필 이미지를 등록하는 순서로 프로세스가 진행됩니다.
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 설정 문제라는 것을 확인할 수 있었던 것 같습니다.
'Library&Framework > Spring Boot' 카테고리의 다른 글
[Spring Boot]상품 더미데이터 만들기 - (2)유틸 클래스와 기능 설명[상품, 이미지, 카테고리, 색상, 인벤토리 등] (0) | 2024.07.29 |
---|---|
[Spring Boot]상품 더미데이터 만들기 - (1)기획과 설계 (ApplicationRunner) (0) | 2024.07.28 |