File API 정리하기

박성룡 ( Andrew park )
16 min readApr 26, 2020

--

최근 FileReader 를 사용할 일이 생겨 File API 를 정리 해보고자 한다.

HTML5 부터 브라우저는 File API 지원하기 시작한다.

File API 는 아래와 같이 정의되어 있다.

  • FileList : 파일 리스트
  • File : 파일 데이터
  • FileReader : 파일 읽기
  • Blob : 바이트 데이터

https://w3c.github.io/FileAPI/#FileReader-interface

간단예제

<!--html-->
<input id="input" type="file" accept="text/*">
<script>
var inputEl = document.querySelector('#input');
inputEl.addEventListener('change', function (event) {
// FileList
var fileList = inputEl.files || event.target.files;
// File
var file = fileList[0];
var reader = new FileReader(); reader.onload = function(progressEvent) {
console.log(progressEvent.target.result);
};
reader.readAsText(file);
});
</script>
```

해당 예제는 input[type=file] 로 입력받은 파일을 FileReader 로 내용을 Text 로 읽는 예제다.

input file type

기본적으로 브라우저에서는 보안상 로컬 파일에 직접 접근 할 수 없다.

input[type=file] 는 브라우저에서 유저가 직접 로컬의 파일을 선택할 수 있게 도와준다.

이렇게 선택한 파일은 File 로 정의되고 FileList 에 담기게 된다.

이때 multiple 설정 여부와 관계없이 ArrayLike 형태인 FileList 로 담긴다.

파일을 선택하면 input, change EventHandler 가 발생되며, 선택된 파일은 HTMLInputElement.files 에 저장된다.

oninput 와 onchange 는 input.files 이 변경될때, 발생하는 점은 유사 하다.

유일한 차이점은 onchange 는 포커스를 잃을 때 발생하고, oninput 은 요소 값이 변경된 직후에 발생한다.

때문에 순서상으로 oninput 이 먼저 발생한다.

input 의 속성

input[type=file] 은 value, accept, capture, files, multiple 속성을 갖을 수 있다.

  • value [DOMString] : 파일 경로
  • accept [MIME] : 사용 가능한 파일 종류
  • capture [string] : 파일 캡처 방법
  • multiple [boolean] : 여러 파일 선택 여부
  • files [FileList] : 선택된 파일들

input.value

input[type=file] value 에는 파일의 경로를 가진다.

브라우저에서는 보안상 로컬 파일에 직접 접근 할 수 없으며, 로컬 파일 구조의 노출을 막고자 C:\fakepath\ 를 포함하여, 숨긴다.

이 경로의 브라우저마다 구현된 형태가 다를 수 있다.

https://html.spec.whatwg.org/multipage/input.html#fakepath-srsly

input.accept

input[type=file] accept 는 선택 가능한 파일 종류를 설정할 수 있다.

파일은 , 로 구분하며, 아래와 같은 형태로 작성할 수 있다.

  • accept="image/*" : png 같은 이미지파일
  • accept="video/*" : mp4 같은 동영상파일
  • accept="audio/*" : wav 같은 오디오파일
  • accept=".pdf, .doc, .csv" : pdf, doc, css 확장자 파일

input.capture

input[type=file] capture 는 모바일 같은 특정 기기에서 capture 방법을 설정할 수 있다.

  • capture="camera" : 카메라
  • capture="camcorder" : 동영상
  • capture="microphone" : 마이크

capture 속성을 지원하지 않는 브라우저의 경우, accept="image/*;capture=camera" 으로 사용할 수도 있다.

<!--html-->
<input id="camera"
type="file"
capture="camera"
accept="image/*;capture=camera"

/>

FileList

input.files 에는 선택한 파일들을 FileList 로 가진다.

FileList 는 File 들을 가지는 객체이며, { [index]: File, length: number } 형태를 가진 array-like object 이다.

FileList[index] 혹은 FileList.item(index) 형태로 File 에 접근할 수 있다.

이 FileList 는 Symbol(Symbol.iterator) 가 정의되어 있어, 순차적으로 참조하기 위해서, for of 를 사용할 수 있다.

혹은 Array.from() 로 변환하여 참조 할 수 있다.

// javascript
function (event) {
var fileList = event.target.files;
for(const file of fileList) {
// ...
}
Array.from(fileList).forEach((file) => {
// ...
})
};

File

브라우저는 보안상 파일을 조작할 수 없다. 때문에 모든 값은 읽기 전용 이다.

File 은 아래 속성을 가질 수 있다.

  • name : 파일 이름
  • lastModified : 파일을 마지막으로 수정한 Unix Time
  • lastModifiedDate : 파일을 마지막으로 수정한 Date 객체
  • size : 파일의 크기 (Byte 값)
  • type : MIME 유형
File
name: "htm_20190729104652375824.jpg"
lastModified: 1586075186723
lastModifiedDate: Sun Apr 05 2020 17:26:26 GMT+0900 (대한민국 표준시) {}
size: 54973
type: "image/jpeg"

File 은 Blob 을 확장하여 구현되었다.

Blob (Binary Large Object)

Blob 객체는 파일를 text 나 2진 데이터 형태로 읽을 수 있다.

Blob 은 아래 속성을 가질 수 있다.

  • size : 파일의 크기 (Byte 값)
  • type : MIME 유형

https://www.w3.org/TR/FileAPI/#blob-section

Blob 은 2가지 방법으로 생성할 수 있다.

  • new Blob(ArrayBuffer | Blob | DOMString, {type?: MIME}): Blob
  • Blob.slice(start?, end?, contentType?): Blob
    Blob 의 바이트를 시작 및 끝 바이트 범위에서 복제해 새로운 Blob 객체를 생성하고 반환한다.
async function () {
const textBlob = new Blob(
['😀test txt'],
);
console.log( await textBlob.text() );
// 😀test txt
const uintBlob = new Blob(
[new Uint8Array([240, 159, 152, 128, 116, 101, 115, 116, 32, 116, 120, 116])],
{type: 'text/plain'}
);
console.log( await uintBlob.text() );
// 😀test txt
const obj = { test: '😀test txt'};
const jsonBlob = new Blob(
[JSON.stringify(obj, null, 2)],
{type : 'application/json'}
);
console.log( await jsonBlob.text() );
// {
// test: 😀test txt
// }
const copyBlob1 = textBlob.slice();
const copyBlob2 = new Blob([copyBlob1]);
console.log( await copyBlob1.text() );
// 😀test txt
console.log( await copyBlob2.text() );
// 😀test txt
}

Blob 의 내용은 3가지 방법으로 읽을 수 있다.

  • Blob.stream(): ReadableStream
  • Blob.text(): Promise<UTF-8>
  • Blob.arrayBuffer(): Promise<ArrayBuffer>
function (event) {
var file = event.target.files[0];
(async function (blob) {
const slice = blob.slice();
console.log(slice);
// Blob {size: 12, type: ""}
const stream = blob.stream();
console.log(stream);
// ReadableStream {locked: false}
const text = await blob.text();
console.log(text);
// 😀test txt
const arrayBuffer = await blob.arrayBuffer();
console.log(arrayBuffer);
// ArrayBuffer(12) {}
// Int8Array(7498)
// Int16Array(3749)
// Uint8Array(7498)
// byteLength: 7498
})(file);
};

Blob ReadableStream 와 ArrayBuffer

Blob.stream()ReadableStream 을 반환한다.

ReadableStreamStreams API 로 바이트 데이터 를 chunk 단위로 처리할 수 있게 도와준다.

ReadableStream.getReader() 는 read 를 요청하고 다른 stream 이 발생되지 않도록 lock 을 건다

read() 으로 반환된 Promise 에서 값을 조회할 수 있다.

new Blob(['😀test txt'])
.stream()
.getReader()
.read()
.then(({value, done}) => {
const uint8array = value;
return new TextDecoder('utf-8')
.decode(uint8array);
})
.then(console.log)
// 😀test txt

Blob.arrayBuffer()ArrayBuffer 는 반환한다.

ArrayBuffer 는 수정할 수 없는 바이트로 구성된 배열 이다.

ArrayBufferTypedArray (Int8Array | Int16Array | Int32Array | Uint8Array 등) 로 변환할 수 있다.

new Blob(['😀test txt'])
.arrayBuffer()
.then(arrayBuffer => {
return new TextDecoder('utf-8')
.decode(uint8array)
})
.then(console.log)
// 😀test txt
```

여기서 사용된 TextDecoder 는 텍스트를 UTF-8 | ISO-8859–2 같은 형태로 인코딩 혹은 디코딩 할 수 있게 도와주는 객체다.

TextDecoder.decode()ArrayBuffer 를 설정한 형태의 Text 로 decode 할 수 있다.

Blob URL

URL.createObjectURL 를 이용하면, Blob 객체를 가리키는 URL 을 생성할 수 있다.

https://www.w3.org/TR/FileAPI/#blob-url

URL 은 blob:${url origin}/${UUID} 로 생성된다.
ex) blob:https://example.org/9115d58c-bcda-ff47-86e5-083e9a2153041

해당 URL 은 생성된 탭에서만 쓸 수 있다.

Blob URL 은 페이지를 떠나기 전까지 유지되기 때문에, 필요하다면 revokeObjectURL 로 해지 해줄 수 있다.

var blob = new Blob([‘😀test txt’]);
var url = URL.createObjectURL(blob);
console.log(url);
// blob:http://localhost:8000/a1fa84dc-fd52-4435-bc22-b1cb2a7c5d84
fetch(url)
.then(res => res.text())
.then((text) => {
URL.revokeObjectURL(url);
console.log(text);
// 😀test txt
});

해당 URL 은 a href 에 설정해서 다운로드 받을 수도록 설정할 수 있다.

이때 만약 revokeObjectURL 로 해지했다면 다운로드에 실패하게 된다.

<!--html-->
<a href="http://localhost:8000/a1fa84dc-fd52-4435-bc22-b1cb2a7c5d84"
download="file_name.txt"
>
download_button
</a>

Blob Canvas 와 createImageBitmap

Canvas 로 만든 ImagetoBlob 으로 Blob 으로 만들 수 있다.

이때 기본 type 은 image/png 이다.

CanvasRenderingContext2D.drawImage() 를 이용하면, BlobCanvas 에 그릴 수 도 있다.

이때 직접적으로 Blob 을 전달할 수 없고, Image 로 변환하거나, createImageBitmapImageBitmap 를 생성해서 전달해야 한다.

createImageBitmapImageData | Blob 을 전달 받아 ImageBitmap 를 생성할 수 있다.

<!--html-->
<canvas id="canvas" width="5" height="5"></canvas>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var dataURL = canvas.toDataURL();
console.log(dataURL);
// data:image/png;base64,iVBORw0…
canvas.toBlob(blob => {
console.log(blob)
// Blob {size: 72, type: "image/png"}

createImageBitmap(blob)
.then(imageBitmap => {
console.log(imageBitmap);
// ImageBitmap {width: 5, height: 5}
ctx.drawImage(img, 0, 0);
});
});
</script>

FileReader

FileReader 은 File 이나 Blob 의 내용을 읽을 수 있게 도와준다.

보안상 직접적인 Local Storage 에는 접근할 수 없다.

FileReader 에는 4가지 방법으로 파일을 전달 할 수 있다.

  • readAsArrayBuffer(file|blob) [ArrayBuffer]
  • readAsBinaryString(file|blob) [0..255 범위의 문자열]
  • readAsDataURL(file|blob) [Base64]
  • readAsText(file|blob) [UTF-16|UTF-8 문자열]

FileReader 에서 전달 받은 파일을 읽기 성공하면 load EventLinser 에 등록한 function 이 호출된다.

이외에 loadstart | progress | loadend | error 로 읽는 상태에 따라 function 이 호출 된다.

function (event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (progressEvent) => {
console.log(progressEvent.target.result);
};
reader.readAsArrayBuffer(file);
// readAsArrayBuffer ArrayBuffer(12) {}
reader.readAsDataURL(file);
// readAsDataURL data:text/plain;base64,8J+YgHRlc3QgdHh0
reader.readAsBinaryString(file);
// readAsBinaryString 😀test txt
reader.readAsText(file);
// readAsText 😀test txt
};
```

Network 와 File

XMLHttpRequestFetch 를 이용해 Network 상의 파일을 가져올 수 있다.

이때 XMLHttpRequest.responseType 의 설정 값에 따라 응답 데이터 유형 을 설정할 수 있다.

XMLHttpRequest.responseTypearraybuffer | blob | document | json | text 로 설정할 수 있으며 기본값을 text 이다.

var imageUrl = 'http://localhost:8000/image.jpg';var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer' || 'blob';
xhr.onload = function(e) {
var arraybuffer = xhr.response || e.target.response;
}
xhr.open('GET', imageUrl);
xhr.send();

fetch 는 응답 데이터 가 Response 객체 로 생성되어 전달된다.

Response 는 4 가지 변환 할 수 있다.

  • Response.arrayBuffer()
  • Response.blob()
  • Response.json()
  • Response.text()

만약 해당 형태로 변환하지 못한다면 SyntaxError 가 발생된다.

var imageUrl = ‘http://localhost:8000/image.jpg';fetch(imageUrl)
.then(res => res.blob())
.then(console.log);
// Blob {size: 75677, type: "image/jpg"}
fetch(imageUrl)
.then(res => res.arrayBuffer())
.then(console.log);
// ArrayBuffer(75677) {}

--

--

박성룡 ( Andrew park )
박성룡 ( Andrew park )

Written by 박성룡 ( Andrew park )

Javascript is great We may not be great

No responses yet