본문 바로가기
개발/React

리액트 - 파일 다운로드 경과 확인하기 (Check File Download Process)

by 피로물든딸기 2023. 7. 9.
반응형

리액트 전체 링크

 

참고

- 파일 브라우저에서 파일 다운로드하기

- 파일 다운로드 경과 로딩 과정 보여주기

 

파일 다운로드가 얼마나 진행되었는지 확인해 보자.

먼저 Node Server에서 구현한 내용에 파일 사이즈를 헤더의 content-length에 추가한다.

  let stats = fs.statSync(req.query.filePath);
  let fileSize = stats.size;

  ...

  res.setHeader("content-length", fileSize);

 

전체 코드는 다음과 같다.

//downloadFile.js
const express = require("express");
const router = express.Router();

const fs = require("fs");
const mime = require("mime");

router.get("/", (req, res) => {
  let spt = req.query.filePath.split("/");
  let fileName = spt[spt.length - 1]; // 파일명만 추출
  let mimetype = mime.getType(req.query.filePath); // 파일의 타입을 가져온다.

  let stats = fs.statSync(req.query.filePath);
  let fileSize = stats.size;

  console.log({ mimetype, fileSize });

  res.setHeader(
    "Content-disposition",
    `attachment; fileName ${encodeURI(fileName)}` // 다운 받을 파일 이름 설정
  );

  res.setHeader("Content-type", mimetype); // 파일의 형식 지정
  res.setHeader("content-length", fileSize);
  res.setHeader("Set-Cookie", `token=1`);

  let fileStream = fs.createReadStream(req.query.filePath);
  fileStream.pipe(res);
});

module.exports = router;

 

위에서 설정한 헤더는 리액트에서 파일을 다운로드할 때, Network 탭에서 확인이 가능하다.

참고로 size의 단위는 byte다.


Fetch로 파일 다운로드하기

 

버튼을 누르면 D 드라이브에 있는 download.zip 파일을 다운로드 받아보자.

import React from "react";

import Button from "@mui/material/Button";

const App = () => {
  const downloadFile = () => {
    let filePath = "D://download.zip";  
  };

  return (
    <div>
      <Button variant="outlined" onClick={downloadFile} sx={{ m: 2 }}>
        File Download
      </Button>
    </div>
  );
};

export default App;

 

fetch는 node 서버에서 downloadFile을 요청하게 된다. 예시는 다음과 같다.

  fetch(`http://192.168.55.120:3002/downloadFile?filePath=${filePath}`)
    .then((response) => response.blob())
    .then((data) => {
      let link = document.createElement("a");
      link.href = url;
      link.setAttribute("download", "download.zip");

      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);

      window.URL.revokeObjectURL(url);
    });

 

첫 번째 then (download start)에서 현재 진행 상황을 알 수 있도록 response.blob()을 변경한다.

두 번재 then (download end)에서 최종적으로 파일을 다운로드하는 코드를 유지한다.

    fetch(`http://192.168.55.120:3002/downloadFile?filePath=${filePath}`)
      .then((response) => {
        console.log("download start");
        
        ...
      })
      .then(() => {
        console.log("download end");
        
        ...
      });

 

file을 저장할 배열인 data와 dataType을 선언한다.

getData에서 data에 다운로드 받는 데이터 (data.push)와 길이(currentFileSize)를 누적한다. 

이 과정이 끝날 때까지 반복한다. (res.done === false)

  let data = [];
  let dataType;

  fetch(`http://192.168.55.120:3002/downloadFile?filePath=${filePath}`)
    .then((response) => {
      console.log("download start");

      const reader = response.body.getReader();
      const totalFileSize = Number(response.headers.get("content-length"));

      dataType = response.headers["content-type"];

      let currentFileSize = 0;
      function getData() {
        return reader.read().then((res) => {
          if (res.value) {
            data.push(res.value);
            currentFileSize += res.value.length;
            
            const per = Math.floor((currentFileSize / totalFileSize) * 100);

            console.log(`${currentFileSize} / ${totalFileSize} ${per}`);
          }

          if (res.done === false) return getData();
        });
      }

      return getData();
    })

 

여기까지 구현한 내용을 실행해보자.

 

실제 다운받은 데이터의 크기가 일치하는 것을 알 수 있다.

 

마지막으로 누적된 data와 dataType을 Blob을 이용하여 다운로드를 하면 된다.

  .then(() => {
    console.log("download end");
    
    const url = window.URL.createObjectURL(
      new Blob(data, { type: `${dataType}` })
    );

    let link = document.createElement("a");
    link.href = url;
    link.setAttribute("download", "download.zip");

    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    window.URL.revokeObjectURL(url);
  });

 

정상적으로 파일이 다운로드 되는 것을 알 수 있다.

 

전체 코드는 다음과 같다.

import React from "react";

import Button from "@mui/material/Button";

const App = () => {
  const downloadFile = () => {
    let filePath = "D://download.zip";

    let data = [];
    let dataType;

    fetch(`http://192.168.55.120:3002/downloadFile?filePath=${filePath}`)
      .then((response) => {
        console.log("download start");

        const reader = response.body.getReader();
        const totalFileSize = Number(response.headers.get("content-length"));

        dataType = response.headers["content-type"];

        let currentFileSize = 0;
        function getData() {
          return reader.read().then((res) => {
            if (res.value) {
              data.push(res.value);
              currentFileSize += res.value.length;

              const per = Math.floor((currentFileSize / totalFileSize) * 100);

              console.log(`${currentFileSize} / ${totalFileSize} ${per}`);
            }

            if (res.done === false) return getData();
          });
        }

        return getData();
      })
      .then(() => {
        console.log("download end");

        const url = window.URL.createObjectURL(
          new Blob(data, { type: `${dataType}` })
        );

        let link = document.createElement("a");
        link.href = url;
        link.setAttribute("download", "download.zip");

        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

        window.URL.revokeObjectURL(url);
      });
  };

  return (
    <div>
      <Button variant="outlined" onClick={downloadFile} sx={{ m: 2 }}>
        File Download
      </Button>
    </div>
  );
};

export default App;

Axios로 파일 다운로드하기

 

파일 브라우저에서 파일 다운로드하기의 경우 axios를 이용하였다.

axios에서 onDownloadProgress를 이용하면 fetch 처럼 번거롭게 구현할 필요가 없다.

아래와 같이 onDownloadProgress에서 현재 파일의 loaded된 size전체 size를 제공하기 때문이다.

  onDownloadProgress: (event) => {
    const per = Math.round(
      (event.loaded * 100) / event.total
    );

    console.log(
      `${event.loaded} / ${event.total} ${per}`
    );
  },

 

axios를 이용한 코드는 아래와 같으며, 결과도 fetch와 동일하다.

import React from "react";

import Button from "@mui/material/Button";
import axios from "axios";

const App = () => {
  const downloadPC = (response, fileName) => {
    const url = window.URL.createObjectURL(
      new Blob([response.data], { type: `${response.headers["content-type"]}` })
    );

    let link = document.createElement("a");
    link.href = url;
    link.setAttribute("download", fileName);

    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    window.URL.revokeObjectURL(url);
  };

  const downloadFile = () => {
    let filePath = "D://download.zip";

    axios
      .get(`http://192.168.55.120:3002/downloadFile?filePath=${filePath}`, {
        responseType: "arraybuffer",
        onDownloadProgress: (event) => {
          const per = Math.round(
            (event.loaded * 100) / event.total
          );

          console.log(
            `${event.loaded} / ${event.total} ${per}`
          );
        },
      })
      .then((res) => {
        downloadPC(res, "download.zip");
      })
      .catch((error) => console.log(error));
  };

  return (
    <div>
      <Button variant="outlined" onClick={downloadFile} sx={{ m: 2 }}>
        File Download
      </Button>
    </div>
  );
};

export default App;

 

로그 출력을 Circular Progress에 적용하면 화면에서 볼 수 있게 된다.

반응형

댓글