리액트에서 서버로 이미지를 업로드 하려면 보통 FormData를 사용한다.
우리도 FormData로 이미지 업로드 기능을 문제없이 구현해서 사용하고 있었다. 그런데 웹에서 테스트할 때는 문제가 없었지만, 안드로이드 웹앱으로 배포한 환경에서는 안되는 것이었다 !
결국에 해결했지만 해결하는 과정에서 문제를 제대로 인식하기 헷갈리게끔 하는 상황들이 계속 있었고, 원인을 찾기 힘들었다.
사실 정말 별거 아니고 당연한건데 우리가 경험이 부족해서 찾는데 오래걸린 거다. 해결하고 너무 뿌듯했지만 한편으로는 더 열심히 개발하고 공부해야겠다고 생각했다.
원인을 찾는데 여러 접근을 하였고 어떤 순서로 접근했는지 정리하려고 한다.
처음에는 당연히 배포된 URL로 웹에서는 테스트시 정상작동 하였기 때문에 안드로이드 문제라고 생각했다. 사진을 고르고 업로드 하는 것 자체가 native 기능을 react 에서 브릿지 함수로 추가하는 과정이 있는줄 알았고 거기서 문제가 오는 것이라고 생각했다. 그래서 안드로이드를 담당하던 팀원에게 물어봤고, 팀원분이 거기에는 브릿지 함수가 없다고 하셨다.
✨ 브릿지 함수 때문에는 아니라는걸 알았지만 이후에도 계속 안드로이드 환경에서 문제가 발생하는 것으로 추측했다.
이미지 업로드가 안되는 문제를 찾았던 부분은 회원가입 기능이었다. 그런데 다른 팀원분이 리뷰 작성에서는 핸드폰으로 해도 이미지 업로드가 된다고 하셨다. 그자리에 가서 확인했는데 진짜 된다. 그래서 내가 코드를 잘못 짰다고 생각하고 리뷰작성 페이지의 이미지 업로드 부분이랑 완전히 똑같이 코드를 수정했다. 웹에서는 잘 작동했었지만 axios headers의 Content-Type
도 multipart/form-data
로 바꿔줬다. 다시 내 핸드폰으로 회원가입을 테스트 해봤다.
여전히 이미지 업로드가 안된다
그래서 내 핸드폰으로 리뷰작성 페이지에서 이미지 업로드가 되나 확인해보니 안된다. 아까 리뷰작성 페이지가 된다고 했던 팀원에게 회원가입 이미지 업로드가 되는지 테스트 해달라고 했다.
❗놀랍게도 된다고 하셨다
✨ 안드로이드 핸드폰 마다 버전이 달라 되는 핸드폰이 있고 안되는 핸드폰이 있다고 추측했다.
웹에서는 문제 없이 동작하고, 핸드폰에서는 conosle창을 볼수 없어서 문제를 찾기가 어려웠다. 백엔드 개발자 분에게 FormData 업로드 하는 api를 호출했을 때, 서버에 찍히는 로그를 봐달라고 부탁드렸다.
❗안드로이드 환경에서는 아에 해당 api 호출 자체를 안하고 있다고 하셨다.
✨ 웹에서는 호출을 잘하던 것이 안드로이드에서는 호출을 안하는건, api 호출하기 직전 이미지를 FormData에 넣는 과정이 안드로이드일 환경일 때 문제가 생겼고, 특정 안드로이드 폰에서만 발생한다고 생각했다
핸드폰으로는 로그를 확인할 수 없다는 생각에 갖혀있다가, 분명 핸드폰으로도 로그를 확인하는 라이브러리가 없을리 없다고 생각했다. 찾아보니 당연히 있었고 vconsole
이라는 라이브러리를 설치해서 핸드폰으로 로그를 확인해봤다. FormData는 console.log()
에 찍히지 않기 때문에 확인할 수 없지만 api가 왜 호출안되는지 알 수 있었다.
✨ 이미지를 FormData에 넣는 코드는 잘 작동했지만 업로드하는 api는 status가 0이었다.
찾아보니 HTTP status 0은 요청이 중단되었거나 요청중 연결이 끊어진 것이었다.
나는 안드로이드 버전마다 사진파일을 저장하는 형식이 다르고 특정 형식은 FormData에 넣어서 보내면 api 요청하는 과정에서 문제가 생길 수 있다고 추측했다. 그래서 아까 된다고 했던 팀원분에게 사진을 찍어서 카카오톡으로 보내달라고 했다.
❗ 받은 사진으로 이미지 업로드를 성공했다.
❗ 그런데 사진을 보낸 본인이 그 사진으로 업로드를 실패했다고 했다.
혹시 아까 이미지 업로드 할 때 다운받은 사진으로 했는지 여쭤봤는데 그렇다고 하셨다. 확인해보니 이전에 이미지 업로드에 성공했다고 하셨던 분들 모두 직접 찍은 사진이 아니라 다운받은 사진으로 진행했었다.
✨ 사진을 보내신 본인이 보낸 파일을 다운받아 이미지 업로드를 하니까 작동하였고, 찍은 사진과 카카오톡으로 보낸 같은 사진의 용량이 다른걸 확인하고 그제서야 사진 파일의 크기, 압축 때문에 문제가 생긴다는걸 파악했다.
요즘 핸드폰 카메라로 찍은 사진은 용량이 매우 크다. 3mb 정도인데 pc에 그정도 크기의 사진을 구하려면 4K 배경화면을 다운받아야 한다. 그래서 pc 웹으로 테스트 했을 때는 문제가 없던 것이다. 핸드폰으로 찍은 사진을 보내도 보내는 과정에서 전부 압축되어 1mb 이하로 떨어지기 때문이다.
✨ 대략 2mb 넘는 4k 배경화면을 다운받아서 테스트해보니까 웹에서도 안되었고 사진의 크기 때문에 문제가 발생했던 것이라고 확신했다.
이미지를 업로드 하기전에 클라이언트 단에서 압축을 하고 보내기로 했다.
이미지 압축을 위해서 browser-image-compression
이라는 라이브러리를 사용했다.
npm install browser-image-compression
const reader = new FileReader();
const options = {
maxSizeMB: 0.2,
maxWidthOrHeight: 1920,
useWebWorker: true,
};
const compressedFile = await imageCompression(event.target.files![0], options);
reader.readAsDataURL(compressedFile);
reader.onloadend = () => {
const base64data = reader.result;
console.log(compressedFile.type);
setSendingImage(base64data);
}
FileReader
객체를 가져와서 위에 만든 압축된 Blob
객체 compressedFile
을 읽는다.readDataUrl
을 이용해 base64로 변환한다.setSendingImage()
에 바로 event.target.files![0]
을 넣어줬었다. const handlingDataForm = async (dataURI:any) => {
const byteString = atob(dataURI.split(",")[1]);
// Blob 구성 준비
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i+=1) {
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ia], {
type: "image/jpeg"
});
const file = new File([blob], "image.jpg");
const formData = new FormData();
formData.append("photo", file);
return formData;
};
const formData = await handlingDataForm(sendingImage);
handlingDataForm
에서 반환된 formData를 그대로 api 호출할 때 사용, 핸드폰으로 찍은 사진도 문제없이 업로드 됐다 !