基于node如何實(shí)現(xiàn)http傳輸大文件?下面本篇文章給大家介紹一下基于nodejs的幾種http文件傳輸實(shí)踐方案,希望對(duì)大家有所幫助!
基于nodejs的http文件傳輸方案在現(xiàn)階段的前后端全棧開發(fā)中有都很重要的作用,本文我將通過幾種方案實(shí)現(xiàn)http傳輸大文件。在實(shí)現(xiàn)功能之前,我們先通過nodejs的fs模塊寫入一個(gè)大文件,并在項(xiàng)目中生成一個(gè)本地文件:
const fs = require('fs'); const writeStream = fs.createWriteStream(__dirname + "/file.txt"); for(let i = 0;i <= 100000; i++) { writeStream.write(`${i} —— 我是${i}號(hào)文件n`, "utf-8"); } writeStream.end();
以上代碼成功運(yùn)行后,在當(dāng)前的執(zhí)行目錄下將會(huì)生成一個(gè)大小為 3.2MB 大小的文本文件,該文件將作為以下方案的 “大文件素材”。在列出大文件傳輸方案之前,我們先封裝后面即將用到的兩個(gè)公共方法: 文件讀取方法
和 文件壓縮方法
:
// 封裝讀取文件的方法 const readFile = async (paramsData) => { return new Promise((resolve, reject) => { fs.readFile(paramsData, (err, data) => { if(err) { reject('文件讀取錯(cuò)誤'); } else { resolve(data); } }) }) } // 封裝文件壓縮方法 const gzip = async (paramsData) => { return new Promise((resolve, reject) => { zlib.gzip(paramsData, (err, result) => { if(err) { reject('文件壓縮錯(cuò)誤'); } else { resolve(result); } }) }) }
1. 通過大文件在數(shù)據(jù)壓縮后傳輸
瀏覽器在發(fā)送請(qǐng)求時(shí),都會(huì)攜帶 accept
和 accept-*
請(qǐng)求頭信息,用于告訴服務(wù)器當(dāng)前瀏覽器所支持的文件類型、支持的壓縮格式列表和支持的語言。請(qǐng)求頭中的 Accept-Encoding
字段,用于將客戶端能夠理解的內(nèi)容編碼方式(通常是某種壓縮算法)告訴給服務(wù)端。服務(wù)端會(huì)選擇一個(gè)客戶端所支持的方式,并通過響應(yīng)頭 Content-Encoding
來通知客戶端該選擇,響應(yīng)頭告訴瀏覽器返回的 JS 腳本,是經(jīng)過 gzip
壓縮算法處理過的
// 請(qǐng)求頭 accept-encoding: gzip, deflate, br
// 響應(yīng)頭 cache-control: max-age=2592000 content-encoding: gzip content-type: application/x-javascript
基于 Accept-Encoding
和 Content-Encoding
字段的了解,我們來驗(yàn)證一下未開啟 gzip
和開啟 gzip
的效果。
// 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文件讀取服務(wù)器(沒有開啟gzip) const server = http.createServer(async (req, res) => { res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8", }); const buffer = await readFile(__dirname + '/file.txt'); res.write(buffer); res.end(); }) server.listen(3000, () => { console.log(`server啟動(dòng)成功`) })
// 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文件讀取服務(wù)器(開啟gzip) const server = http.createServer(async(req, res) => { res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8", "Content-Encoding": "gzip" }); const buffer = await readFile(__dirname + '/file.txt'); const gzipData = await gzip(buffer); res.write(gzipData); res.end(); }) server.listen(3000, () => { console.log(`server啟動(dòng)成功`) })
2. 通過數(shù)據(jù)分塊傳輸
有場(chǎng)景需要用從數(shù)據(jù)庫(kù)中查詢獲得的數(shù)據(jù)生成一個(gè)大的 HTML 表格的時(shí)候,或者需要傳輸大量的圖片的時(shí)候,可以通過分塊傳輸實(shí)現(xiàn)。
Transfer-Encoding: chunked Transfer-Encoding: gzip, chunked
響應(yīng)頭 Transfer-Encoding
字段的值為 chunked
,表示數(shù)據(jù)以一系列分塊的形式進(jìn)行發(fā)送。需要注意的是 Transfer-Encoding
和 Content-Length
這兩個(gè)字段是互斥的,也就是說響應(yīng)報(bào)文中這兩個(gè)字段不能同時(shí)出現(xiàn)。
// 數(shù)據(jù)分塊傳輸 const spilitChunks = async () =>{ const buffer = await readFile(__dirname + '/file.txt'); const lines = buffer.toString('utf-8').split('n'); let [chunks, i, n] = [[], 0, lines.length]; while(i < n) { chunks.push(lines.slice(i, i+= 10)); }; return chunks; } const server = http.createServer(async(req, res) => { res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8", "Transfer-Encoding": "chunked", "Access-Control-Allow-Origin": "*", }); const chunks = await spilitChunks(); for(let i =0; i< chunks.length; i++) { setTimeout(() => { let content = chunks[i].join("&"); res.write(`${content.length.toString(16)}rn${content}rn`); }, i * 1000); } setTimeout(() => { res.end(); }, chunks.length * 1000); }) server.listen(3000, () => { console.log(`server啟動(dòng)成功`) })
3. 通過數(shù)據(jù)流的形式傳輸
當(dāng)使用 Node.js
向客戶端返回大文件時(shí),使用流的形式來返回文件流能避免處理大文件時(shí),占用過多的內(nèi)存。具體實(shí)現(xiàn)方式如下所示。當(dāng)使用流的形式來返回文件數(shù)據(jù)時(shí),HTTP 響應(yīng)頭 Transfer-Encoding
字段的值為 chunked
,表示數(shù)據(jù)以一系列分塊的形式進(jìn)行發(fā)送。
const server = http.createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8", "Content-Encoding": "gzip", "Transfer-Encoding": "chunked" }); fs.createReadStream(__dirname + "/file.txt") .setEncoding("utf-8") .pipe(zlib.createGzip()) .pipe(res); }) server.listen(3000, () => { console.log(`server啟動(dòng)成功`) })