웹개발을 하다보면 네트워크를 통해 파일을 받게 되는데, 이때 파일은 주로 2진 데이터로 이루어진 것이 일반적이다. (0과 1로 이루어진 세계)
다만, 가끔 보면 이 2진데이터에 대해서 상당히, 혼란스러울정도로 다채로운 분류가 있음을 알 수 있는데 예를들어 ArrayBuffer, Blob, File 등등의 형태가 이에 해당한다. 이번에 데이터베이스에 이미지 하나 넣으려는데 자꾸 Blob이니 뭐니 알수없는 이야기가 나와서 정리를 좀 해두고 싶었다.
우선 가장 기초가 되는 데이터 형태는 바로 ArrayBuffer이다. 레퍼런스 타입으로 되어있고, 고정된 길이의 연속된 메모리 공간을 할당해 사용하겠다고 알려주는 역할을 한다. (참고로, 오디오 전용인 AuidoBuffer, 미디어 전용인 SourceBuffer도 있다고 한다)
아래와 같은 형태로 생성이 가능하다
let arrBuffer = new ArrayBuffer(16); // 이렇게 생성하면 연속된 16바이트만큼의 메모리 공간을 버퍼로 할당해 사용하겠다고 선언하는 것과 같다.
참고로, 이름에 array가 있다고 하여 이것이 배열이라고 착각하면 안된다.
배열과는 다르게, ArrayBuffer은 그 길이가 변동되지 않고 고정되어 있다.
또한, ArrayBuffer은 정확하게 메모리의 어느 바이트만큼 사용하겠다고 명시하는 역할을 하고 있으며 해당 퍼버의 내용에 접근하기 위해 배열처럼 array[0] 과 같은 행위의 get은 불가능하다.
ArrayBuffer의 공간 안에 있는 내용을 조작하려면, 특수한 "view"역할을 하는 객체들을 사용해야한다.
(이것을 TypedArray라고 부른다)
그것이 바로 위의 프로토타입에서 확인할 수 있는 내용들과 같다.
실제 코드로 보면 아래와 같다.
let buffer = new ArrayBuffer(16); // 16바이트(128비트) 메모리공간 할당할게요
let view = new Uint32Array(buffer) // 4바이트(32비트) 꼴로 버퍼에 있는 내용 읽어들일게요
//즉, buffer은 [00000000 00000000 ...... (총 16개)] 로 형성되어있고
// view가 이것을 인식할 때 4바이트씩, 즉 8개짜리 4개씩 단위로 끊어 읽는다는 소리다.
// [00000000 00000000 00000000 00000000, 00000000 00000000 ......]
console.log(view.length) // 분할해서 읽는 공간의 갯수가 총 4개네요 (16바이트 / 4바이트)
console.log(view.byteLength) // 총 바이트크기는 16바이트구요
view[0] = 123456; // 분할공간 중 첫번째에 123456이라는 숫자데이터를 넣어주세요
console.log(view) // 0:123456, 1:0, 2:0, 3:0
여기서 그럼 짚고 넘어가야 할 것이, 이런 객체가 왜 있어야 하는가 하는 본질적인 의문이 들수밖에 없다.
여기서 Stream과 Buffer의 개념을 한번 알고 가야한다.
예를 들어, 엄청나게 커다란 동영상 파일이 CDN 서버에 캐싱되어 저장되어 있고, 유저가 이것을 요청했다고 가정하자.
일반적으로 동영상 재생이라고 하는 것은 이 영상 파일을 통째로 내려받은 후, 재생하는 것을 의미한다.
혹은 실시간 방송과 같이 영상내용을 송출할 때에는 이 영상이라고 하는 실시간 데이터를 계속해서 전달해줘야 유저들이 이것을 볼 수 있다. 즉, 어떤 식으로든 커다란 데이터를 잘개 쪼개서 전송해야 하는 상황이 발생한다는 것이다.
Buffer이란 영어 뜻과 같이 일정 구획만큼의 데이터를 쪼개서 전달되는 stream을 저장한 후 일정 크기가 도달하면 출력장치나 동영상 플레이어로 전달해주는 중개자 역할을 하는 객체라고 볼 수 있다. 즉, Buffer란 binary 데이터를 임시로 받아 저장할 수 있는 기능을 가지고 있다고 할 수 있다.
기존에는 자바스크립트에서 메모리에 할당되는 데이터의 크기를 관여할 수 없었다고 한다.
let name = 10; // 알아서 자바스크립트 엔진이 이것의 타입이 숫자임을 확인하고 메모리 공간을 자동 확보했다
그러나 자바스크립트 용도가 다양해지면서 오디오나 비디오와 같은 binary data들 역시 다룰 필요성이 생기게 되자, 필요한 메모리 공간을 적절하게 할당해서 사용할 수 있는 유연성이 필요해졌다고 한다.
Blob은 Binary Large Object의 줄임말로, 보통 이미지/ 사운드/ 비디오와 같이 2진데이터이긴 한데 너어어어어무 커다란 데이터들을 손쉽게 처리할 수 있도록 만들어진 생성자이다.
일반적으로 만들 때에는 기본적으로 아래와 같이 형성이 가능하다
const blob = new Blob(new ArrayBuffer(바이트), {type: "video/mp4"})
//참고로 Blob 생성자의 첫번째 인자로는 ArrayBuffer, ArrayBufferView, Blob, DOM string 단독 혹은 배열이 올 수 있다고 한다.
new Blob([new ArrayBuffer(8)], { type: 'video/mp4' });
new Blob(new Uint8Array(buffer), { type: 'image/png' });
new Blob(['<div>Hello Blob!</div>'], {
type: 'text/html',
endings: 'native'
});
만약, 서버에 이미지 데이터를 전송하고 싶어진다고 가정하자
대략 43KB의 png 이미지를 Blob으로 가공한다면 아래와 같은 결과를 만들어준다
해당 객체의 프로퍼티인 size는 바이트 크기를 의미하며, type은 MIME타입을 의미한다.
MIME타입이란 전송 데이터의 타입으로, 보통 데이터 응답자가 리소스를 내려받았을 때에 어떤 동작을 기본적으로 해야 하는지를 결정하도록 만들어주는 지표이다. 일반적으로 "/"로 구분되는 두개의 문자열로 이루어져 있다
예를 들어 text/plain, text/html, image/jpeg, audio/* 와 같은 형태가 이에 해당한다.
만약 해당 Blob에 들어가는 데이터가 너무 크기가 클 경우, 한번에 Blob객체 한방으로 보내는 것은 부담스러울 수도 있다.
이럴 때에 Blob객체 내에 내장되어 있는 slice 메소드를 이용하면 큰 객체를 작은 단위로 쪼갤 수 있다.
const blob = new Blob(buffer, {type: "image/jpeg");
const chunk = blob.slice(0, 1024, "image/jpeg") // 0바이트부터 1KB까지 짜르고, 타입은 jpeg로
이 쪼갤 수 있다는 기능을 이용해서 데이터를 보낼 때 chunk 단위로 쪼개서 보내는 것도 가능해지고, 첫째 인자로 blob객체의 배열들을 받을 수 있다는 점을 이용하여 쪼개진 chunk들을 병합하는 것도 가능하다.
const chunks = [];
chunkSize = Math.ceil(blob.size / 10) // 큰 blob데이터를 10개로 쪼개봅시다.
for(let i = 0; i < 10; i++){
const offset = chunkSize * i;
chunks.push(
blob.slice(
offset, offset + chunkSize, blob.type
)
)
} // chunks에는 blob객체들이 모여있습니다
const mergeBlob = new Blob(chunks, {type : blob.type}) // 모든 chunk들이 하나로 합쳐서 데이터로 사용할 수 있게 되었습니다.
<img src={window.URL.createObjectURL(mergeBlob)}/>
//createObjectURL 은 병합된 Blob 객체를 가리키는 URL을 만들어 DOM에서 사용할 수 있게 합니다.
아직 찾아보면서 공부중이지만, Blob으로 형성된 총 사이즈와 개별 blob들을 전해주면 데이터를 효율적으로 분리하여 전달하는 것이 가능하지 않을까... 이게 TCP에 동봉되는 데이터의 정체인가? 하고 생각해보는 시간이 되었다.
아직 멀었지만... 그래도 데이터 전송하는 파일들에 친숙해지는 시간이 된 것 같다.
참고로
보통 input type을 text로 지정하면 그 노드의 데이터로 텍스트 데이터가 들어간다.
그런데 만약 type이 file이고, file을 전송한다면 이때 노드에 저장되는 데이터는 File객체가 된다.
const target = document.querySelector("#test");
console.log(target.files) // FileList{0: File, length: 1}
<input type="file" id="test" />
이때 만들어진 File객체의 형태는 아래와 같은데
const file = {
lastModified: 1580961046732,
lastModifiedDate: 'Thu Feb 06 2020 12:50:46 GMT+0900 (대한민국 표준시) {}',
name: 'dom.png',
size: 192045,
type: 'image/png',
}
이 File객체 역시 name과 lastModifiedDate만 추가된 Blob객체라고 한다.
즉, 우리가 브라우저에 파일을 전송하면, 브라우저는 그 데이터를 버퍼를 통해 받은 뒤 일정량이 되면 Blob객체로 만들어 저장하고 있는 것이다.