열심히 프론트엔드 기술에 대해서 공부를 하다가 문득 이미지나 파일, 비디오 등과 같은 데이터들을 서버에 보낼때 어떤 유형으로 보내고 어떤 방법으로 보내고 받는지 궁금해지기 시작했습니다.
그래서 이러한 주제로 정리하면서 공부해 보았습니다.
웹 개발을 진행하다 보면 이진 데이터를 다루어야 할 때를 간혹 마주칠 수 있습니다.
브라우저에선 주로 파일 생성, 업로드, 다운로드 또는 이미지 처리와 관련이 깊죠
이진 데이터
컴퓨터는 우리가 사용하는 모든 데이터를 0과 1로 저장합니다.
이러한 데이터를 이진 데이터라고 하며 Binary 데이터 라고도 합니다.
우리가 평소에 프로그래밍 하면서 직접 이진 데이터를 다루는 일은 별로 없습니다.
우리가 프로그래밍 할 때에는 고급 언어를 사용하기 때문에 알아서 프로그램이 내부적으로 이진 테이터로 변환하고 읽고 처리하기 때문이죠.
하지만 파일이나 이미지, 비디오 같은 멀티미디어 같은 데이터를 다루어야 할 때는 이 멀티미디어 데이터를 정수, 문자 다루듯이 해야 합니다.
우리가 지금 보고 있는 브라우저는 0과 1로 이루어져 있는 것이며 폴더나 파일 역시 이진 데이터로 이루어져 있습니다.
그럼 컴퓨터 안에 저장된 데이터를 꺼내 쓰고 싶을때 어떻게 해야 할까요??
메모리에 저장된 0과 1로 이루어진 데이터를 변수에 적재해놓고, 필요하면 우리는 변수를 불러 덧셈, 뺄셈을 하는 것으로 컴퓨터 안의 이진 데이터(바이너리)를 다루고 있습니다.
숫자나 스트링이 아닌, 이미지나 비디오 같은 복잡한 멀티미디어 파일들은 어떻게 변수에 메모리에 저장할까??
변수에 이미지 url을 저장하는건 링크라는 징검다리를 저장하는거지 이미지 데이터 그 자체를 저장하는 것이 아닙니다.
이때 등장하는 것이 Base64 라는 개념입니다.
Base64는 0과 1로 이루어진 이진 데이터(바이너리)를 인코딩하여 텍스트 형식으로 변환하는 것을 말합니다.
아래 사진과 같이 이미지 경로가 아닌 base64 로 이루어진 긴 문자열 이미지 데이터를 본적이 있을 텐데 이것이 바로 base64 입니다.
0과 1로 이루어진 이미지 데이터 자체를 base64 텍스트 기반 포맷으로 변환해줌으로서 우리가 직접 이미지 데이터를 다룰수 있는 것입니다.
즉 쉽게 말하자면 어떤 경로를 이용해서 보여주는 것이 아니라 그냥 코드 자체에서
이미지를 보여주는 거라고 생각하시면 됩니다.
base64 를 사용하는 경우는 어떤 경우가 있을까요??
이렇게 base64로 변환하면 직접 바이너리를 다룬다는 특징도 있지만, 바이너리 데이터를 텍스트로 바꾸는 64진법 인코딩을 통해, 바이너리 데이터 대비 33%의 데이터의 양 증가하게 된다는 단점이 있습니다.
하지만 데이터의 길이가 증가함에도, Base64를 사용하는 가장 큰 이유는 앞서 소개하듯이 Binary 데이터를 텍스트 기반 규격으로 다룰 수 있기 때문입니다.
또한 순수 바이너리 형식으로 남아있는보다 전송/저장이 훨씬 쉽다는 점도 꼽을 수 있죠. (이진 데이터는 손상될 확률이 높다)
첫번째로 이미지를 base64로 바꾸어주는 사이트가 있습니다.
https://elmah.io/tools/base64-image-encoder/
두번째로 자바스크립트 코드를 이용해 변환하는 방법은 다음과 같습니다.
먼저 fetch api로 이미지를 읽어들이고, 이미지를 blob 형식으로 변환합니다.
그리고 FileReader 객체를 통해 blob 데이터를 base64 형식으로 읽어들어 변환시킵니다.
Blob
컴퓨터에서 큰 덩어리의 정보를 담고 있는 것으로 생각할 수 있습니다.
이 덩어리 안에는 텍스트, 이미지, 오디오, 비디오 등 다양한 종류의 정보가 들어갈 수 있습니다. 그러니까, 우리가 인터넷에서 사진을 다운로드할 때 그 사진은 하나의 'Blob'로 저장되고, 이후에 우리가 컴퓨터에서 열거나 다루게 됩니다.
FileReader
Blob 또는 File과 같은 자료형 객체로부터 데이터를 읽어 들이기위한 목적으로 사용되는 객체입니다.
(async () => {
const data = await fetch('https://play-lh.googleusercontent.com...');
const blob = await data.blob();
const reader = new FileReader();
reader.onload = () => {
const base64data = reader.result;
console.log(base64data)
}
reader.readAsDataURL(blob);
})()
BLOB은 Binary Large OBject의 약자로 주로 이미지, 오디오, 비디오와 같은 멀티미디어 파일 바이너리를 객체 형태로 저장한 것을 의미합니다.
멀티미디어 파일들은 대다수 용량이 큰 경우가 많기 때문에, 이를 데이터베이스에 효과적으로 저장하기 위해 고안된 자료형이라 볼 수 있습니다.
예를들어 데이터베이스에 이미지 파일을 그대로 데이터로 저장하고 싶을때, 바로 blob 포맷으로 변환한 뒤 저장하는 것입니다.
base64는 바이너리 데이터를 다루기 위해 텍스트(문자열) 형태로 저장한 포맷
blob이란 바이너리 데이터를 다루기 위해 객체(Object) 저장
앞서 base64 포맷으로 이미지 바이너리 파일을 브라우저에 넣으면 다음과 같이 문자열이 굉장히 길어져 가독성이 안좋을 뿐만 아니라, base64 이미지를 이곳저곳 여러개 사용할 경우 결과적으로 용량 문제 때문에 문서 자체를 로딩하는데 많은 시간이 걸려 오히려 느려질수 있다는 단점을 지니고 있습니다.
하지만 blob 데이터는 적절하게 object url로 변환만 해주면 다음과 같이 심플하게 브라우저에서 사용할수 있습니다.
const data = await fetch('https://play-lh.googleuserconte...');
const blob = await data.blob(); // 이미지 blob 객체 얻기
이렇게 blob 객체로 만들어 준뒤
<img src="" alt="" style="width:150px; display: block;">
<a download="img.jpg" href="#">이미지 다운로드</a>
<script>
fetch('https://www.busin...')
.then((response) => response.blob())
.then((blob) => {
const url = URL.createObjectURL(blob);
console.log(url)
});
</script>
이러한 blob 이미지는 현재 탭의 브라우저 메모리에 저장됩니다.
이러한 원리 때문에, base64와는 달리 짧은 문자열만으로도 원래의 Blob 객체에 접근이 가능하고 그에 따른 이미지 등의 파일을 가져올 수 있습니다. 그래서 변환된 URL은 항상 현재 문서에서만 유효하죠. (현재 브라우저 메모리에 적재된 상태니까)
변환된 URL을 현재 문서를 새로고침하거나 아니면 다른 페이지에서 사용하려고 한다면 제대로 사용할 수 없습니다.
이와 관련한 메모리 이슈도 존재합니다.
Blob 객체가 URL로 변환되어 매핑이 이루어진 채 메모리에 저장되게 되면, 명시적으로 해당 URL이 해제되기 전까지 브라우저는 해당 URL이 유효하다고 판단하기 때문에 자바스크립트 엔진에서 가비지 컬렉션이 이루어지지 않습니다.
따라서 blob URL을 사용한 이후, 더 이상 사용하지 않을 시점이라고 판단되면 명시적으로 해제해 주는 것이 좋습니다.
// Create Blob URL
const objectURL = window.URL.createObjectURL(blob);
// Revoke Blob URL after DOM updates..
window.URL.revokeObjectURL(objectURL);
예를 들어 이미지를 화면에 출력이 아닌 오로지 이미지 다운으로 blob을 사용한다고 하면, 동적으로 생성한 Blob 객체는 오직 다운로드 클릭 순간에만 필요하고 그 이후엔 필요하지 않기 때문에 해제를 통해 메모리 누수를 방지할 수 있습니다.
앞서 말했듯이 Blob 객체로 base64의 형태로 변환이 가능하고, 변환된 문자열을 바로 src 로써 사용할 수 있습니다. 이러한 URL은 보통 data url 이라고 부르는데, 이렇게 변환된 base64 형태의 URL은 위에서 URL.createObjectURL 메서드를 통해 변환한 형태와는 달리 어디에서나 사용이 가능합니다. (문자열 자체가 이미지 데이터 이기 때문에)
fetch('https://play-lh.googleusercontent.co...')
.then((res) => res.blob())
.then((blob) => {
const reader = new FileReader();
reader.onload = () => {
const base64data = reader.result;
console.log(base64data)
}
reader.readAsDataURL(blob);
})