使用 stream 上傳檔案到 Server

有時候檔案太大,沒有辦法一次整個上傳,比如說影片、圖片等,這時候我們就需要將檔案拆成一小塊一小塊的 bytes 上傳

實作流程:

  • 使用者上傳檔案
  • FileReader 讀取檔案內容
  • 將檔案拆成一小塊一小塊後上傳
  • 後端收到每一小塊資料後 append 到該檔案裡

Image.png

前端的畫面,使用者先點選“選擇檔案”上傳檔案後,在點擊右邊的 “讀取&上傳”將檔案上傳到 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 // 100 kb
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 到該檔案即可

測試

上傳進度顯示正確

AnimatedImage.gif

我們的資料夾也確實收到檔案

Image.png

我們為什麼需要 TLS 證書? Node.js 效能提升
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×