有時候檔案太大,沒有辦法一次整個上傳,比如說影片、圖片等,這時候我們就需要將檔案拆成一小塊一小塊的 bytes 上傳
實作流程:
- 使用者上傳檔案
- 用
FileReader
讀取檔案內容
- 將檔案拆成一小塊一小塊後上傳
- 後端收到每一小塊資料後 append 到該檔案裡
前端的畫面,使用者先點選“選擇檔案”上傳檔案後,在點擊右邊的 “讀取&上傳”將檔案上傳到 Server
Client 部分
<body> <h1>My file uploader</h1> File: <input type="file" id='f'> <button id="btnUpload">Read & Upload</button> <div id="divOutput"></div> <script> const btnUpload = document.getElementById("btnUpload") const divOutput = document.getElementById('divOutput') const f = document.getElementById('f')
btnUpload.addEventListener("click", () => { const theFile = f.files[0]; const fileReader = new FileReader() fileReader.onload = async (e) => { const CHUNK_SIZE = 100000 const chunkCount = Math.ceil(e.target.result.byteLength/CHUNK_SIZE) const fileName = encodeURIComponent(Math.random() * 1000 + theFile.name) for (let chunkId = 0; chunkId < chunkCount + 1; chunkId++) { const chunk = e.target.result.slice( chunkId * CHUNK_SIZE, chunkId * CHUNK_SIZE + CHUNK_SIZE ) await fetch ("http://localhost:8080/upload", { "method": "POST", "headers": { "content-type": "application/octet-stream", "content-length": chunk.length, "file-name": fileName }, "body": chunk }) const completePercentage = Math.floor(chunkId * 100 / chunkCount) divOutput.textContent = `${completePercentage} %` } } fileReader.readAsArrayBuffer(theFile) }) </script> </body>
|
html 的結構
我們使用 FileReader 讀取使用者上傳的檔案,FileReader 讀到的資料存為 ArrayBuffer,基本上就是 bytes array, 然後我們用 slice 將這些 bytes 拆分為一塊一塊的 chunk (瀏覽有限制每個 Request 最高 100,000 bytes, 就是 100 kb),依序將每個 Chunk 上傳,我們也可以透過目前已上傳多少 Chunk 來計算上傳進度
Header 的 content-type
需要為 application/octet-stream
因為我們要上傳二進制的資料
注意:檔案名稱需要與 encodeUROComponent 轉為 Base64, 到後端再 decode 回來,因為傳送中文檔名會發生錯誤: Request请求:Failed to execute ‘setRequestHeader’ on ‘XMLHttpRequest’: String contains non ISO-8859-1 code point.
Server 部分
const fs = require('fs') const http = require('http') const httpServer = http.createServer()
httpServer.on("listening", () => "Listening") httpServer.on("request", (req, res) => { if (req.url === '/') { const file = fs.readFileSync('./index.html') res.end(file) return }
if (req.url === '/upload') { const fileName = decodeURIComponent(req.headers["file-name"]) req.on("data", (chunk) => { fs.appendFileSync(fileName, chunk) console.log(`received chunk! ${chunk.length}`) }) res.end("uploaded") } })
httpServer.listen(8080)
|
Server 拿到資料後 append 到該檔案即可
測試
上傳進度顯示正確
我們的資料夾也確實收到檔案