怎么在NodeJS中利用Range請(qǐng)求實(shí)現(xiàn)一個(gè)下載功能?針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

為連云港等地區(qū)用戶(hù)提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及連云港網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站建設(shè)、做網(wǎng)站、連云港網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶(hù)提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶(hù)的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
服務(wù)端的實(shí)現(xiàn)
通過(guò) http 模塊創(chuàng)建服務(wù)器處理 Range 請(qǐng)求,在服務(wù)器代碼中我們?yōu)榱藴p少回調(diào)嵌套使用 async 函數(shù),所以需要將異步的操作方法轉(zhuǎn)換成 Promise,以往我們使用 util 的 promisify 來(lái)一個(gè)一個(gè)轉(zhuǎn)換異步方法,比較麻煩,我們這次使用第三方模塊 mz 并直接引入轉(zhuǎn)換好的替代模塊。
使用 mz 之前需要先安裝:
npm install mz
服務(wù)端代碼如下:
// 文件:server.js
const http = require("http");
const path = require("path");
const url = require("url");
// 引入 mz 模塊轉(zhuǎn)換成 Promise 的 fs 模塊
const fs = require("mz/fs");
// 請(qǐng)求處理函數(shù)
async function listener(req, res) {
// 獲取 range 請(qǐng)求頭,格式為 Range:bytes=0-5
let range = req.headers["range"];
// 下載文件路徑
let p = path.resovle(__dirname, url.parse(url, true).pathname);
// 存在 range 請(qǐng)求頭將返回范圍請(qǐng)求的數(shù)據(jù)
if (range) {
// 獲取范圍請(qǐng)求的開(kāi)始和結(jié)束位置
let [, start, end] = range.match(/(\d*)-(\d*)/);
// 錯(cuò)誤處理
try {
let statObj = await fs.stat(p);
} catch (e) {
res.end("Not Found");
}
// 文件總字節(jié)數(shù)
let total = statObj.size;
// 處理請(qǐng)求頭中范圍參數(shù)不傳的問(wèn)題
start = start ? ParseInt(start) : 0;
end = end ? ParseInt(end) : total - 1;
// 響應(yīng)客戶(hù)端
res.statusCode = 206;
res.setHeader("Accept-Ranges", "bytes");
res.setHeader("Content-Range", `bytes ${start}-${end}/${total}`);
fs.createReadStream(p, { start, end }).pipe(res);
} else {
// 沒(méi)有 range 請(qǐng)求頭時(shí)將整個(gè)文件內(nèi)容返回給客戶(hù)端
fs.createReadStream(p).pipe(res);
}
}
// 創(chuàng)建服務(wù)器
const server = http.createServer(listener);
// 監(jiān)聽(tīng)端口
server.listen(3000, () => {
console.log("server start 3000");
});在上面服務(wù)端的代碼中,需要兼容 Range 請(qǐng)求和普通請(qǐng)求,兩種請(qǐng)求的區(qū)別是,如果客戶(hù)端發(fā)送的是 Range 請(qǐng)求,會(huì)攜帶 Range:bytes=0-5 格式的請(qǐng)求頭,我們可以通過(guò) req 的 headers 屬性獲取,在獲取請(qǐng)求頭時(shí),原本大寫(xiě)字母開(kāi)頭 NodeJS 統(tǒng)一處理成小寫(xiě),所以獲取時(shí)應(yīng)小寫(xiě)。
如果是 Range 請(qǐng)求則通過(guò)可讀流讀取對(duì)應(yīng)的內(nèi)容返回客戶(hù)端,如果不是,則通過(guò)可讀流讀取整個(gè)文件返回客戶(hù)端,在響應(yīng) Range 請(qǐng)求的過(guò)程中需要設(shè)置響應(yīng)狀態(tài)為 206,需要設(shè)置響應(yīng)頭 Accept-Ranges 值為 bytes,需要設(shè)置響應(yīng)頭 Content-Range 值為 byte 0-5/100 的格式,0 為返回?cái)?shù)據(jù)開(kāi)始的索引,5 為結(jié)束的索引(包含),100 為文件的總字節(jié)數(shù)。
在通過(guò) url 和 path 模塊解析和拼接下載文件路徑時(shí),應(yīng)該進(jìn)行錯(cuò)誤檢測(cè),如果文件不存在則直接返回客戶(hù)端 Not Found。
我們可以使用 curl 命令來(lái)檢測(cè)我們的服務(wù)端代碼,在命令行工具中輸入下面命令,在命令窗口查看返回值是否正確。
curl -v --header "Range:bytes=0-5" http://localhost:3000
客戶(hù)端的實(shí)現(xiàn)
在上面使用 curl 命令來(lái)訪(fǎng)問(wèn)我們的服務(wù)器時(shí),只能請(qǐng)求固定范圍的數(shù)據(jù),而不是類(lèi)似于下載功能,每次都下載一個(gè)范圍的數(shù)據(jù),但是想要多次下載并自動(dòng)維護(hù) Range 的范圍需要借助我們自己實(shí)現(xiàn)的客戶(hù)端邏輯。
為了簡(jiǎn)便,我們的下載客戶(hù)端是在命令行窗口運(yùn)行的,通過(guò)指令來(lái)模擬實(shí)際項(xiàng)目中的開(kāi)始下載、暫停和恢復(fù)按鈕,當(dāng)在窗口中輸入 s 指令時(shí)開(kāi)始下載,輸入 p 指令時(shí)暫停下載,輸入 r 指令時(shí)恢復(fù)下載。
// 文件:client.js
const http = require("http");
const fs = require("fs");
const path = require("path");
// 請(qǐng)求配置
let config = {
host: "localhost",
port: 3000,
path: "/download.txt"
};
let start = 0; // 請(qǐng)求初始值
let step = 5; // 每次請(qǐng)求字符個(gè)數(shù)
let pause = false; // 暫停狀態(tài)
let total; // 文件總長(zhǎng)度
// 創(chuàng)建可寫(xiě)流
let ws = fs.createWriteStream(path.resolve(__dirname, config.path.slice(1)));
// 下載函數(shù)
function download() {
// 配置,每次范圍請(qǐng)求 step 個(gè)字節(jié)
config.headers = {
"Range": `bytes=${start}-${start + step - 1}`;
};
// 維護(hù)下次 start 的值
start += step;
// 發(fā)送請(qǐng)求
http.request(config, res => {
// 獲取文件總長(zhǎng)度
if (typeof total !== "number") {
total = res.headers["content-ranges"].match(/\/(\d*)/)[1];
}
// 讀取返回?cái)?shù)據(jù)
let buffers = [];
res.on("data", data => buffers.push(data));
res.on("end", () => {
// 合并數(shù)據(jù)并寫(xiě)入文件
let buf = Buffer.concat(buffers);
ws.write(buf);
// 遞歸進(jìn)行下一次請(qǐng)求
if (!pause && start < total) {
download();
}
});
}).end();
}
// 監(jiān)控輸入
process.stdin.on("data", data => {
// 獲取指令
let ins = data.toString().match(/(\w*)\/r/)[1];
switch (ins) {
case "s":
case "r":
pause = false;
download();
break;
case "p":
pause = true;
break;
}
});在上面代碼中下載的文件通過(guò) config 中的 path 屬性配置,每次調(diào)用 download 函數(shù)下載時(shí)都會(huì)重新計(jì)算當(dāng)前范圍請(qǐng)求的初始位置和結(jié)束位置,并設(shè)置 Range 請(qǐng)求頭,下一次請(qǐng)求靠遞歸 download 來(lái)實(shí)現(xiàn)。
在執(zhí)行時(shí)需先啟動(dòng)我們的服務(wù)器,在通過(guò)命令行輸入 node client.js 來(lái)啟動(dòng)客戶(hù)端,在命令窗口輸入對(duì)應(yīng)的指令進(jìn)行開(kāi)始下載、暫停下載和恢復(fù)下載操作。
關(guān)于怎么在NodeJS中利用Range請(qǐng)求實(shí)現(xiàn)一個(gè)下載功能問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
網(wǎng)頁(yè)名稱(chēng):怎么在NodeJS中利用Range請(qǐng)求實(shí)現(xiàn)一個(gè)下載功能
文章網(wǎng)址:http://chinadenli.net/article10/jpcjgo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、電子商務(wù)、網(wǎng)站營(yíng)銷(xiāo)、虛擬主機(jī)、服務(wù)器托管、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)