ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Binary / Base64 / Blob / ArrayBuffer / File
    Language/JavaScript 2022. 12. 22. 23:20

    1. Binary

    • Binary란 이진 데이터를 의미하며 1과 0만을 사용하여 2개의 수를 나타내는 진법을 뜻하는, 컴퓨터를 다루는데 있어 가장 근본이 되는 체계라고 한다.

    2. Base64

    • 컴퓨터는 모든 데이터를 0과 1로 저장한다. 그렇다면 컴퓨터 안에 저장된 바이너리 데이터를 꺼내 쓸 수 있을까? 메모리에 저장된 바이너리 데이터를 변수에 적재해놓고, 필요하면 변수를 호출해 바이너리 데이터를 다룬다.
    • 그렇다면 숫자, 스트링이 아닌 이미지 비디오 같은 멀티미디어 파일들은? 이때 Base64 인코딩을 활용하는데, Bas64는 0과 1로 이루어진 바이너리 데이터를 인코딩하여 텍스트 형식으로 변환하는 것을 말한다.
    • 변수에 이미지 url(웹 url이든 로컬의 file path든)을 저장하는 건 링크라는 징검다리를 저장하는 것이지 이미지 데이터 자체를 저장하는 것이 아니다. 그러나 Base64로 변환해주면 우리가 직접 소스코드단에 이미지 데이터 자체를 저장할 수 있게 된다. 즉, 변수에 이미지를 저장할 수 있는 것이다.
    • 이렇게 Base64로 변환하면 바이너리 데이터를 텍스트로 바꾸는 64진법 인코딩을 통해, 바이너리 데이터 대비 33%의 데이터가 증가하게 된다는 단점은 있다. 하지만 데이터 길이가 늘어도 Base64를 사용하는 가장 큰 이유는 Binary 데이터를 텍스트 기반 규격으로 다룰 수 있기 때문이다.
    • 또한, 순수 바이너리 데이터보다 전송/저장이 훨씬 쉽고 안정적이다. 바이너리 데이터는 통신 과정에서 손상될 확률이 높은데, Base64인코딩 시, 문자 포맷을 A-Z, a-z, 0-9, /+만을 사용하기 때문에 문자포맷이 달라서 데이터를 손상시킬 수 있는 시스템 간에도 안정적으로 전송할 수 있기 때문이다.

    3. Binary 이미지 다루기

    • 자바스크립트 코드를 이용해 변환하는 방법은 다음과 같다.

    먼저 fetch api로 이미지를 읽어들이고, 이미지를 blob 형식으로 변환한다 (blob은 바로 뒤에서 배운다)

    그리고 FileReader 객체를 통해 blob 데이터를 base64 형식으로 읽어들어 변환시킨다.

    (async () => {
        const data = await fetch('<https://play-lh.googleusercontent.com/hYdIazwJBlPhmN74Yz3m_jU9nA6t02U7ZARfKunt6dauUAB6O3nLHp0v5ypisNt9OJk>');
        const blob = await data.blob();
        const reader = new FileReader();
        reader.onload = () => {
            const base64data = reader.result;
            console.log(base64data)
        }
        reader.readAsDataURL(blob);
    })()
    
    • FileReader는 Blob 또는 File과 같은 자료형 객체로부터 데이터를 읽어 들이기위한 목적으로 사용되는 객체이다. 읽어들인 데이터는 주로 이벤트를 사용하여 필요한 타이밍에 데이터를 전달한다. (밑에서 자세히 배운다)

    실제로 출력된 base64 데이터값을 브라우저에 쳐보면, 다음과 같이 이미지가 출력되는 것을 확인 할 수 있다.

    4. Blob (Binary Large Object)

    • Blob은 주로 이미지, 오디오, 비디오와 같은 멀티미디어 파일 바이너리를 객체 형태로 저장한 것을 의미한다. 그래서, DB에 이미지 파일을 그대로 데이터로 저장하고 싶을 때, 바로 Blob 포멧으로 변환한 뒤 저장하면 된다. 브라우저에서도 JS로 Blob 데이터에 접근하고 사용할 수 있다.
    • Base64와 다른점은,
      • Base64는 바이너리 데이터를 다루기 위해 텍스트 형태로 저장한 포멧
      • Blob은 바이너리 데이터를 다루기 위해 객체 형태로 저장한 포멧
    • 그리고 Blob은 객체이기 때문에 다양한 코드 활용성을 지니고 있어, base64로도 변환할 수 있고 buffer로도 변환할 수도 있다.

    5. Blob 이미지 다루기

    const data = await fetch('<https://play-lh.googleusercontent.com/hYdIazwJBlPhmN74Yz3m_jU9nA6t02U7ZARfKunt6dauUAB6O3nLHp0v5ypisNt9OJk>');
    const blob = await data.blob(); // 이미지 blob 객체 얻기
    

    5.1. Blob → ObjectURL 변환

    • 이렇게 만들어진 Blob 객체는 url 타입으로 따로 변환을 해야 한다.(바로 사용 불가)
     
    이미지 다운로드
        fetch('<<a href=https://www.business2community.com/wp-content/uploads/2014/04/Free.jpg>https://www.business2community.com/wp-content/uploads/2014/04/Free.jpg</a>>')
          .then((response) => response.blob())
          .then((blob) => {
            **const url = URL.createObjectURL(blob); // 이게 중요~!!**
            document.querySelector('img').src = url;
            document.querySelector('a').href = url;
          });
    
    • URL.createObjectURL메서드를 이용하면 Blob 객체를 가지고 고유한 URL을 생성할 수 있다. 이때 생성되는 URL의 형태는 blob:/의 형태를 띄게 된다. 그리고 변환된 URL은 모든 src를 속성으로 가지는 모든 HTML 태그와 CSS 속성에서 사용이 가능하다.
    • Blob 객체에는 별도로 type(image/png) 을 명시하기 때문에 Blob 객체를 다운로드/업로드 하는 과정에서 네트워크 요청에서의 Content-Type은 자연스레 명시된 type으로 매칭된다.
    • 이때 변환된 URL은 현재 탭의 브라우저 메모리에 저장되고 저장된 URL은 매핑된 Blob 객체를 참조하고 있는 형태이다. 이런 원리로, Base64와는 달리 짧은 문자열만으로도 원래의 Blob 객체에 접근이 가능하고 그에 따른 이미지등의 파일을 가져올 수 있는 것이다. 그래서 변환된 URL은 항상 현재 문서에서만 유효하다. 변환된 URL을 현재 문서를 새로고침하거나 아니면 다른 페이지에서 사용하려고 한다면 제대로 사용할 수 없다.
    • 그러나 이와 관련한 메모리 이슈가 존재한다. Blob 객체가 URL로 변환되어 매핑이 이루어진 채 메모리에 저장되면 명시적으로 해당 URL이 해제되기 전까지 브라우저는 해당 URL이 유효하다고 판반하기 때문에 JS 엔진에서 가비지 컬렉션이 이루어 지지 않는다
      • cf) 가비지 컬렉션 ?
    • 따라서 Blob URL을 사용한 이후, 더이상 사용하지 않을 시점이라고 판단되면 명시적으로 해제해주는 것이 좋다.
    // Create Blob URL
    const objectURL = window.URL.createObjectURL(blob);
    
    // Revoke Blob URL after DOM updates..
    window.URL.revokeObjectURL(objectURL);
    
    • 변환은 URL.createObjectURL 메서드를 통해 진행했고, 해제의 경우에는 URL.revokeObjectURL 메서드를 사용한다. 이는 내부적으로 매핑되어 있는 참조를 지우는 메소드로, 메모리에 상주하고 있는 Blob 객체를 지울 수 있다.
    • 예를 들어 이미지를 화면에 출력이 아닌 오로지 이미지 다운로드로 blob을 사용하고자 한다면 동적으로 생성한 Blob 객체는 오직 다운로드 클릭 순간에만 필요하고 그 이후엔 필요하지 않기 때문에 해제를 통해 메모리 누수를 방지할 수 있다.

    5.2. Blob → Base64 변환

    • Blob 객체는 Base64 형태로 변환이 가능하고, 변환된 문자열을 바로 src로 사용할 수 있다. 이러한 URL은 보통 data url 라고 부르는데 이렇게 변환된 base64 형태의 URL은 문자열 자체가 이미지 데이터이기 때문에 위에서 URL.createObjectURL메서드를 통해 반환한 형태와 달리 어디서나 사용이 가능하다.
    • Blob을 Base64로 반환하는 방식은 위 Base64로 인코딩 하는 방법과 같다.

    6. ArrayBuffer

    • ArrayBuffer 객체는 이미지, 동영상과 같은 멀티미디어 데이터 덩어리를 브라우저(표준 자바스크립트)에서 다루기 위해 도입 됐다.
    • 일반적으로 실시간 방송과 같이 영상내용을 송출할 때는 영상이라고 하는 실시간 데이터를 계속해서 전달해줘야 유저들이 이것을 볼 수 있다. 즉, 어떤 식으로든 커다란 데이터를 잘게 쪼개서 전송해야 하는 상황이 발생한다는 것이다.
    • 여기서 버퍼라는 개념이 등장하는데, 일정 구획만큼의 데이터를 쪼개서 전달되는 stream을 저장한 후 일정 크기가 도달하면 출력장치나 동영상 플레이어로 전달해주는 중개자 역할을 하는 객체라고 보면 된다.(버퍼링)
    • 따라서 JS 용도가 다양해지면서 오디오, 비디오 같은 바이너리 데이터들도 다룰 필요가 생겼고 필요한 메모리 공간을 적절히 할당해서 사용할 수 있는 유연성이 필요해져서 ArrayBuffer를 만들게 되었다고 한다.
    • ArrayBuffer는 자바스크립트에서 바이너리 데이터를 직접 다루는 수단으로 사용되며, 이는 메모리를 개발자가 수동으로 관리할 수 있게 해준다. 지금까지 배운 Base64와 Blob은 사람이 읽고 다루기 편하게 가공된 데이터 타입이라면, ArrayBuffer가 보다 오리지널에 가깝다고 보면 된다. 레퍼런스 타입으로 되어있고 고정된 길이의 연속된 메모리 공간을 할당해 사용하겠다고 알려주는 역할을 한다. (참고로, 오디오 전용인 AudioBuffer, 미디어 전용인 SourceBuffer도 있다고 한다)
    • 특히 성능에 민감한 이슈를 다룬다거나 아니면 Blob드으이 큰 용량 파일 데이터를 다루는 경우에 ArrayBuffer를 사용해 유연하고 효율적으로 작업할 수 있다.

    7. Buffer

    • 버퍼는 고정된 크기의 데이터 덩어리를 표현하기 위한 타입이며, Node.js의 많은 API들이 Buffer 타입을 지원한다. fs 모듈로 로컬 파일을 가져오는 것도 사실 버퍼로 가져오는 것이다. 클라이언트 단에서 ArrayBuffer로 바이너리 데이터를 다뤘다면 서버단에서는 Buffer 객체로 바이너리 데이터를 다룬다고 이해하면 된다.
    • 즉, Node.js에서 멀티미디어 파일을 다룬다고 하면 무조건 버퍼를 이용해 다룬다고 이해하면 된다.
    const fetch = require('node-fetch');
    
    //* 이미지 가져오기
    const img = await fetch('이미지URL 혹은 base64이미지') // 이미지 url을 fetch
      .then((res) => res.blob()) // 반환 이미지를 blob으로 변환
      .then(async (blob) => {
         const arrayBuffer = await blob.arrayBuffer(); // blob을 arrayBuffer로 변환
         return Buffer.from(arrayBuffer); // 클라이언트용 버퍼인 arrayBuffer룰 다시 서버용 buffer로 변환
      });
    
    1. 이미지를 fetch한다.
    2. 그리고 결과값을 blob으로 변환해준다.
    3. blob을 비동기를 통해 arraybuffer로 변환해준다.
    4. 클라이언트용 버퍼인 arraybuffer를 buffer로 변환해준다.
    • 사실 fetch 모듈에서 바로 버퍼로 변환할 수 있는 헬퍼 메서드를 제공하니 아래와 같이 간단하게 사용하면 된다. 내부적으로는 위와 같이 동작한다.
    const fetch = require('node-fetch');
    
    const img = await fetch(이미지URL 혹은 base64이미지) // 이미지 url을 fetch
          .then((res) => res.buffer()) // 반환 이미지를 blob으로 변환
    

    8. File & FileReader

    • 파일은 우리에게 매우 익숙한 타입이지만, 자바스크립트에서의 File객체는 blob 객체를 확장한 객체로 주로 파일시스템과 관련된 기능을 담당한다. 파일 시스템은 OS/서버단의 영역인데 브라우저상에서도 파일을 주고 받는 등의 기능이 필요하기 때문에 이를 지원하기 위한 규격으로 볼 수 있다. 브라우저에서 자바스크립트를 이용해 파일을 다루기 위한 방법으로는 File 객체를 이용하거나 html의 input 태그를 이용하는 두 가지가 있다.
    new File(fileParts, fileName, [options]);
    /*
    - fileParts : Blob / BufferSource / String 과 같은 배열
    - fileName : 파일 이름 (문자열)
    - options : 옵셔널 값. lastModified : 마지막 수정이 일어난 때의 timestamp
    */
    
    <input type="file" onchange="showFile(this)">
    
    <script>
      function showFile(input) {
      	// input type='file' 의 경우 추가적으로 multiple 옵션을 지정할 수 있는데 이 경우에는 다량의 File이 배열 형태로 전달되게 된다. 
        // 이때를 고려하여 단일 파일만 전달되는 경우에도 File은 항상 유사 배열로 전달되기 때문에 하나의 파일만 다루는 경우에도 인덱스로 접근하는 것에 주의하자
        let file = input.files[0];
        
        alert(`File name: ${file.name}`); // 파일 이름
        alert(`Last modified: ${file.lastModified}`); // 지막 수정이 일어난 때의 timestamp
      }
    </script>
    

    9. FileReader 객체

    • FileReader는 blob 또는 file과 같은 객체로부터 데이터를 읽어들이기 위한 목적으로 사용되는 객체이다. 읽어들인 데이터는 주로 이벤트를 사용하여 필요한 타이밍에 데이터를 전달한다.
    • 생성된 FileReader 객체에서 사용할 수 있는 주요 메서드는 아래와 같다.
    const reader = new FileReader();
    
    reader.readAsArrayBuffer(blob) // ArrauBuffer 형태로 데이터를 읽어 변환
    
    reader.readAsText(blob, [encoding]) // encoding 방식에 맞게 텍스트 형태로 데이터를 읽어 변환 (기본 인코딩 방식 - utf-8)
    
    reader.readAsDataURL(blob) // base64 형태의 data url로 데이터를 읽어 변환
    
    • readAsArrayBuffer(blob) : ArrauBuffer 형태로 데이터를 읽음
      • readAsArrayBuffer의 경우 바이너리 파일 대상으로 로우 레벨의 바이너리 작업이 필요한 경우에 유용하다. 대부분 하이레벨에서 하는 작업의 경우엔 File 객체가 Blob을 상속받고 있기 때문에 별도의 읽기 과정없이 즉각적으로 slice 등의 메서드 호출이 가능하다.
    • readAsText(blob, [encoding]) : encoding 방식에 맞게 텍스트 형태로 데이터를 읽음 (기본 인코딩 방식 - utf-8)
      • readAsText의 경우 텍스트 형태의 문자열이 필요한 경우 유용하다.
    • readAsDataURL(blob) : base64 형태의 data url로 데이터를 읽음
      • readAsDataURL의 경우 img 태그와 같이 src 속성에 리소스를 다루어야 하는 경우 유용하다.또는 이전 챕터에서 다룬바와 같이 URL.createObjectURL을 이용하는 방법도 있다.
    • abort() : 작업을 즉시 중단
    • FileReader 객체를 이용해 데이터를 읽는 과정에서 발생하는 이벤트 목록은 다음과 같다.
    const reader = new FileReader();
    reader.readAsDataURL(blob) // base64 형태의 data url로 데이터를 읽어 변환
    
    // 데이터를 모두 읽으면 onload 이벤트를 발생
    reader.onload = () => {
        const data = reader.result; // 결과 저장
        console.log(data)
    }
    
    • loadstart : 로딩이 시작될 때
    • progress : 읽기를 수행하고 있는 중
    • load : 에러 없이 리딩이 완료된 때
    • abort : abort() 메서드가 호출된 때
    • error : 에러가 발생한 때
    • loadend : 성공/실패 여부 상관없이 리딩이 완료된 때

    만약 읽기가 모두 완료되었다면 그에 대한 결과는 다음과 같은 프로퍼티로 접근이 가능하다.

    • reader.result : 성공 시 읽어들인 결과
    • reader.error : 실패 시 발생한 에러
    • 다음은 FileReader를 이용해, 브라우저에서 파일첨부를 하면 파일을 base64형태로 변환해 이미지로 띄우는 예제 코드이다.
    <img src="">
    <input type='file' onchange='readFile(this)' />
    
    <script>
      function readFile(input) {
        const file = input.files[0]; // 첨부된 파일을 가져옴
        
        const reader = new FileReader();
       
        reader.readAsDataURL(file); // 파일을 base64로 변환
      
        reader.onload = function() {
          console.log(reader.result); // 읽은 파일 소스단에 출력
          document.querySelector('img').src = reader.result;
        };
      
        reader.onerror = function() {
          console.log(reader.error);
        };
      }
    </script>
    

    댓글