참고
파일 다운로드가 얼마나 진행되었는지 확인해 보자.
먼저 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에 적용하면 화면에서 볼 수 있게 된다.
'개발 > React' 카테고리의 다른 글
리액트 - 깃허브 RESTful API 한글 깨짐 현상 해결하기 (0) | 2023.07.10 |
---|---|
React Material - 파일 다운로드 경과 로딩 과정 보여주기 (Mui Loading Circular Progress with File Download) (0) | 2023.07.09 |
React Material - 비동기 함수 로딩 중 보여주기 (Mui Loading CircularProgress with Async Function) (0) | 2023.07.09 |
리액트 - 키보드 이벤트를 감지하여 복사 방지하기 (Detect Copy Keyboard Event) (0) | 2023.07.06 |
리액트 CSS - pre 태그로 입력한 그대로 보여주기 (0) | 2023.06.28 |
댓글