파일 다운로드 기능 개발기 (with. React)

2023년 5월 15일

#React

진행 중인 프로젝트에서 사용자 권한과 요청에 맞게 파일 다운로드 기능을 제공해야했는데요.

대게 다운로드 기능을 제공해야하는 파일들은 바이너리 데이터이며,

JavaScript에서는 바이너리 데이터를 다루는 방법으로 BufferFileAPI를 제공하고 있습니다.

  • Buffer: 개발자가 메모리 관점에서 바이너리 데이터를 다룰 수 있습니다.
  • File API: 파일(png, mp3등)이나 입출력장치(마이크, 카메라, 화면 등)의 바이너리 데이터를 다룰 수 있습니다.

해당 포스팅에서는 FileAPI에 대해 정리 후,

FileAPI 중 하나인 Blob 객체를 활용한 서버 데이터 다운로드 React 예제를 정리했습니다.


FILE API 란?

FILE API텍스트, 이미지, 오디오, 비디오 등의 대용량 바이너리 데이터를 다루기 위한 API입니다.

구체적으로 File API는 Blob, File, FileReader, FileList 객체로 구성되어 있습니다.

  • Blob: 바이너리 데이터를 나타내는 불변 객체로, 이미지나 다운로드된 파일들을 처리합니다.
  • File: Blob을 상속 받는 객체로 사용자 시스템에서 가져온 파일을 나타냅니다. 주로 <input type="file"> 요소를 통해 얻어진 파일을 나타내곤 합니다.
  • FileReader: File이나 Blob에 저장된 바이너리 데이터를 읽어들이는 객체입니다. 데이터를 텍스트, 이진 데이터 또는 URL로 액세스할 수 있게 합니다.
  • FileList: HTML Input 엘리먼트를 통해 입력 받은 파일(File객체 형태)들을 저장하는 유사 배열 객체입니다. <input type="file" multiple>을 사용하여 여러 파일을 선택할 때 얻어집니다.

Blob (Binary Large Object)

JS에서 Blob은 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용할 수 있습니다.

대개 데이터의 크기(Byte)MIME 타입을 알아내거나, 데이터를 송수신을 위한 작은 Blob 객체로 나누는 등의 작업에 사용합니다.

const testBlob = new Blob(array, options);
  • Blob 은 Bolb 생성자 함수를 사용하여 Blob 객체를 생성합니다.
  • array와 options를 인자로 받습니다.
    • options 에는 type, endings 를 설정할 수 있습니다.
    • type : 데이터의 MIME 타입을 설정, 기본 값은 “” 입니다.
    • endings : \n을 포함하는 문자열 처리를 transparent 혹은 native로 지정, 기본값은 transparent 입니다

React 에서 Blob 객체로 파일 다운로드 기능 구현 (With. REST API)

  • 프론트 사용 스택 : React, JavaScript, React-Query, Axios
    • 백엔드에서는 Flask 로 REST API 를 설계하였으나, 해당 정리 내용에서는 프론트에 초점을 맞춰 정리하였습니다.
    • 다운로드 기능 구현에 해당하는 코드만 정리하였습니다. 이 외의 코드는 ... 으로 처리하였습니다.

프로젝트에서 다운로드 기능으로 제공하고자 한 파일들은 프로젝트 백엔드 서버 특정 경로에 함께 저장이 되어 있었습니다.

또한 백엔드 스택인 Flask 에서 send_file 이라는 메서드를 제공하였기 때문에, 백 서버 특정 경로에 저장되어 있는 파일을 압축하여, 클라이언트 서버로 보내기에 적절하였습니다.

따라서 REST API 형태로 파일 다운로드 하고자하는 파일을 보낼 수 있다는 것을 확인하였고, 작업을 진행하였습니다.

import Api from '../../modules/lib/customApi';

...

const fetchFileDownload = async () => {
  const response = await Api.post('/download-zip', {
    responseType: 'blob', // 파일 다운로드를 위해 blob 유형으로 설정
  });

  const filename = "DUMMY" // 컨벤션에 맞는 파일명으로 설정하기

  const url = window.URL.createObjectURL(new Blob([response]));
  const link = document.createElement('a');

  link.href = url;
  link.setAttribute('download', `${filename}.zip`);
  document.body.appendChild(link);
  link.click();
  link.remove();
};

export { ... , fetchFileDownload };

API 서버에서 blob type의 zip 파일을 받아옵니다. 여기서 Api는 진행 중인 프로젝트에서 사용하고 있는 커스텀된 Axios 인터셉터인데요.

기본적으로 요청 실패에 대한 catch 처리가 되어 있습니다. (서버 에러 메시지 토스트 or 지정된 오류 메시지 토스트 노출) 또한 세부적으로는 응답값에 대한 오류 처리가 case 별로 나눠져 있습니다. (ex. 인증 관련 오류시, 재로그인 유도 모달 노출 등)

요청 성공시에는 서버 response 에 대한 data 값을 받아오는 방식으로 셋팅이 되어 있습니다. (response.data)

받아 온 데이터를 JS Blob 객체 형태로 만든 후, a 태그의 download 속성을 활용하여 다운로드 로직을 작성하였습니다.

  • 코드 설명
    • window.URL.createObjectURL(new Blob([response])) : Blob 객체 생성
    • a 태그의 href 의 기능을 활용하여, 해당 로직 안에서 a 태그를 클릭하였을 때 link 로직을 동작시키기. Vanila JS 방식으로 구현하였습니다
    • a element를 생성하고 → 해당 element에 위에서 생성한 Blob 객체를 href에 심음 → download 속성에 filename.zip 을 등록 → body 에 element를 추가한 후, click으로 동작 → 동작을 마친 link는 제거
...
const { mutate: mutateKeyDownload, isLoading } =
    useMutation(fetchGetDownloadKey);
...
<Modal
  okText="다운로드"
  onOk={mutateKeyDownload}
  okButtonProps={{
    loading: isLoading,
    style: { backgroundColor: token.colorPrimary },
  }}
/>
...

다음으로는 다운로드 버튼이 있는 화면에서 mutate 문을 셋팅해주었습니다.

react-query 라이브러리를 활용하고 있기에, POST API 연동시 useMutation 을 활용할 수 있었고, 미리 셋팅해둔 axios fetch 문을 인자 값으로 넣어줍니다.

  • (추가) REST API 참고
@app.route('/download-zip')
def download_zip():
    # zip 파일 생성
    zip_filename = 'archive.zip'

    file1_path = '/path/to/file1.txt'  # zip에 담을 파일 경로 1
    file2_path = '/path/to/file2.txt'  # zip에 담을 파일 경로 2

    with zipfile.ZipFile(zip_filename, 'w') as zip_file:
        zip_file.write(file1_path, 'file1.txt')
        zip_file.write(file2_path, 'file2.txt')

    return send_file(zip_filename, as_attachment=True) # flask 의 sned_file 메서드 활용

추가로 REST API 의 간단한 예제입니다. 실제 프로젝트에서는 DB 서버 에 저장된 파일을 다루기 때문에 프로덕션 코드와 다릅니다!


참고

자바스크립트 File API 파헤치기: Blob, File, FileReader, FileList, BlobURL

Blob(블랍) 이해하기


Profile picture

주희(Joy)
가치를 고민하는 과정을 함께해요