본문 바로가기
FE/browser

[browser] Blob 다루기

by livemehere 2023. 10. 5.

이전 포스팅에서 다루었던 ArrayBuffer 와 TypedArray(View) 는 ECMA 의 표준 스펙입니다.

바이너리 데이터를 자바스크립트에서 다루는 표준인 셈이에요.

btoa(), atob() 로 base64 인코딩을 하거나,

TextEncoder, TextDecoder 로 utf-8 인코딩을 할 수 있음을 알아보았습니다.

 

하지만 이것만으로는 브라우저에서, 이미지, 동영상 등 파일을 다루기에는 부족함이 많습니다.

예를들어서, 이것으로 비트맵을 표현하는 이미지를 만들어낸다고 하면, 3비트씩 직접 색상을 일일이 찍어내고, <img> 태그 에 호환이 되도록 만들어야되죠. 또 XMLHttpRequest, fetch 등의 네트워크 통신간에 파일을 업로드, 다운로드하기 위한 규격에도 맞추어야 하구요!

 

브라우저는 한단게 더 높은 추상화를 거친 Blob 이라는 객체를 제공하고 있습니다.

blob 은 브라우저에서 바이너리 데이터를 OS의 File 처럼 다룰 수 있다고 생각하시면 됩니다.!

 

Blob 의 구성

blob 은 MIME-type +blob or string or bufferSource 로 구성되어있습니다.

코드로 생성해보면 아래와 같습니다.

 

String 으로 blob 만들기

const blob = new Blob(['Kong World'],{type:'text/plain'})
console.log(blob) // Blob {size: 10, type: 'text/plain'}

 

buffer 로 blob 만들기

const KongWorld = new Uint8Array([75, 111, 110, 103, 32, 87, 111, 114, 108, 100]);
const blob = new Blob([KongWorld],{type:'text/plain'});
blob.text().then(console.log); // Kong World

 

blob 으로 blob 만들기

const blob2 = new Blob([blob,blob], {type:'text/plain'});
blob2.text().then(console.log); // Kong WorldKong World

 

이렇게 Blob 객체를 만들었는데, 만들기만 하면 단순히 string, buffer 값을 만든것과 다름이 없습니다.
Blob은 서두에 설명했듯 File 처럼 다룰 수 있습니다. 실제로 Blob 을 상속한 File 객체가 따로있지만, 쉽게 생각해서요 ㅎㅎ :)
Blob 은 초기에 생성했던 MIME-type 의 확장자 형태로 값을 메모리에 저장할 수 있습니다.
그렇게 된다면 이미지, html, text, 동영상 등의 형태를 브라우저에 저장할 수 있다는 것이고, 이는 <img> , <video> , <a> 태그 등에 사용될 수 있고, 또 파일로 다운로드, 업로드가 가능하다는 것이 됩니다.

 

Blob as URL

blob 으로 만들어진 객체를 이곳 저곳에서 활용하기 위해서는 URL 형태의 참조 주소를 얻어내야합니다.

앞서서 blob 은 메모리에 MIME-type 에 맞게 저장된다고 했었죠!

URL.createObjectURL() 메서드를 사용하여 blob 의 url 을 얻어낼 수 있습니다.

URL.createObjectURL() = blob:<origin>/<uuid>
const KongWorld = new Uint8Array([75, 111, 110, 103, 32, 87, 111, 114, 108, 100]);
const blob = new Blob([KongWorld],{type:'text/plain'});
const url = URL.createObjectURL(blob); // blob:http://localhost:63342/05e7073f-8dc3-4cd4-8d58-3acfb1f384c9

url 의 반환값 blob:~~~ url 을 같은 브라우저에 입력하면 브라우저에서 파일을 열람한것 처럼 접근이됩니다.

❗️브라우저의 메모리에 저장하기 때문에, 반드시 같은 브라우저 내에서만 접근이 가능합니다

 

 

 

그렇다면 이 url 을 a 태그에 href 속성으로 넣고, download 가 가능하도록 작성해볼 수 있어요!

const KongWorld = new Uint8Array([75, 111, 110, 103, 32, 87, 111, 114, 108, 100]);
const blob = new Blob([KongWorld],{type:'text/plain'});
const url = URL.createObjectURL(blob); // blob:http://localhost:63342/05e7073f-8dc3-4cd4-8d58-3acfb1f384c9

const a = document.createElement('a');
a.href = url;
a.text= 'Download KongWorld.txt';
a.download = 'KongWorld.txt';
document.body.appendChild(a);

 

여기서 한가지 주의할 점은, URL.createObjectURL 은 blob 에 대한 고유한 URL 을 만들어 내고, 가비지 컬렉터가 동작하지 않아요. 즉 브라우저의 라이프사이클과 함께 쭉 유지됩니다. 점점 쌓인다면 메모리 누수가 발생하게 되서, 더 이상 사용하지 않는다면 직접 레퍼런스를 제거해주어야합니다.

URL.revokeObjectURL(url)

 

Blob as DataURL

위 방법은 blob 을 메모리에 저장하고, 그 레퍼런스 값을 얻어 브라우저 내에서만 접근 가능하도록 하는 방식입니다.

한가지 방식이 더 있는데, 바로 dataURL 을 생성하는 것입니다.

dataURL 은 data: prefix 가 붙은 scheme 입니다.

 

dataURL 의 구성

data:[<mediatype>][;base64],<data>

data: + MIME-type + base64 로 구성됩니다.

직접 base64 로 인코딩하고, 데이터 조합을 만들어도 되겟지만

blob 은 이미 MINE-type 과 데이터를 가지고있기 때문에, 바로 dataURL 로 만들어낼 수 있습니다.

그러기 위해서 blob 을 다양한 포맷으로 읽어내는 FileReader 를 활용합니다.

FileReader 가 이벤트 기반으로 동작하기 때문에, 간단히 Promise 로 감싸서 활용해보았습니다.

 

const KongWorld = new Uint8Array([75, 111, 110, 103, 32, 87, 111, 114, 108, 100]);
const blob = new Blob([KongWorld],{type:'text/plain'});

readUrlAsync(blob).then(url=>{
    const a = document.createElement('a');
    a.href = url; // data:text/plain;base64,S29uZyBXb3JsZA==
    a.text= 'Download KongWorld.txt';
    a.download = 'KongWorld.txt';
    document.body.appendChild(a);
})


/**
 * @param blob
 * @returns {Promise<string>}
 */
function readUrlAsync(blob){
    return new Promise(resolve => {
        const reader = new FileReader();
        reader.readAsDataURL(blob);
        reader.onloadend = () => {
            resolve(reader.result);
        }
    })
}
😄 FileReader 는 다음 포스팅에서 좀 더 자세히 다루어보겠습니다.

 

URL.createObjectURL(blob) vs DataURL

두가지 방식 중 어떤 것을 사용해도 동일한 결과를 얻어낼 수 있지만, 각 장단점이 존재하기에 적절히 상황에 맞게 선택해서 사용합니다.

구분 URL.createObjectURL(blob) DataURL
단점 메모리 누수를 방지하기 위해 revoke 를 일일이 해주어야 하는 base64 로 encoding 해야하기 때문에, 큰 데이터라면 메모리와 퍼포먼스 저하가 우려되는 
장점 encoding/decoding 없이 blob 그 자체에 접근 가능한 메모리 누수 걱정이 없음

 

반응형