您好,登錄后才能下訂單哦!
怎么在NodeJS中利用Range請求實現一個下載功能?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
服務端的實現
通過 http 模塊創建服務器處理 Range 請求,在服務器代碼中我們為了減少回調嵌套使用 async 函數,所以需要將異步的操作方法轉換成 Promise,以往我們使用 util 的 promisify 來一個一個轉換異步方法,比較麻煩,我們這次使用第三方模塊 mz 并直接引入轉換好的替代模塊。
使用 mz 之前需要先安裝:
npm install mz
服務端代碼如下:
// 文件:server.js const http = require("http"); const path = require("path"); const url = require("url"); // 引入 mz 模塊轉換成 Promise 的 fs 模塊 const fs = require("mz/fs"); // 請求處理函數 async function listener(req, res) { // 獲取 range 請求頭,格式為 Range:bytes=0-5 let range = req.headers["range"]; // 下載文件路徑 let p = path.resovle(__dirname, url.parse(url, true).pathname); // 存在 range 請求頭將返回范圍請求的數據 if (range) { // 獲取范圍請求的開始和結束位置 let [, start, end] = range.match(/(\d*)-(\d*)/); // 錯誤處理 try { let statObj = await fs.stat(p); } catch (e) { res.end("Not Found"); } // 文件總字節數 let total = statObj.size; // 處理請求頭中范圍參數不傳的問題 start = start ? ParseInt(start) : 0; end = end ? ParseInt(end) : total - 1; // 響應客戶端 res.statusCode = 206; res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Range", `bytes ${start}-${end}/${total}`); fs.createReadStream(p, { start, end }).pipe(res); } else { // 沒有 range 請求頭時將整個文件內容返回給客戶端 fs.createReadStream(p).pipe(res); } } // 創建服務器 const server = http.createServer(listener); // 監聽端口 server.listen(3000, () => { console.log("server start 3000"); });
在上面服務端的代碼中,需要兼容 Range 請求和普通請求,兩種請求的區別是,如果客戶端發送的是 Range 請求,會攜帶 Range:bytes=0-5 格式的請求頭,我們可以通過 req 的 headers 屬性獲取,在獲取請求頭時,原本大寫字母開頭 NodeJS 統一處理成小寫,所以獲取時應小寫。
如果是 Range 請求則通過可讀流讀取對應的內容返回客戶端,如果不是,則通過可讀流讀取整個文件返回客戶端,在響應 Range 請求的過程中需要設置響應狀態為 206,需要設置響應頭 Accept-Ranges 值為 bytes,需要設置響應頭 Content-Range 值為 byte 0-5/100 的格式,0 為返回數據開始的索引,5 為結束的索引(包含),100 為文件的總字節數。
在通過 url 和 path 模塊解析和拼接下載文件路徑時,應該進行錯誤檢測,如果文件不存在則直接返回客戶端 Not Found。
我們可以使用 curl 命令來檢測我們的服務端代碼,在命令行工具中輸入下面命令,在命令窗口查看返回值是否正確。
curl -v --header "Range:bytes=0-5" http://localhost:3000
客戶端的實現
在上面使用 curl 命令來訪問我們的服務器時,只能請求固定范圍的數據,而不是類似于下載功能,每次都下載一個范圍的數據,但是想要多次下載并自動維護 Range 的范圍需要借助我們自己實現的客戶端邏輯。
為了簡便,我們的下載客戶端是在命令行窗口運行的,通過指令來模擬實際項目中的開始下載、暫停和恢復按鈕,當在窗口中輸入 s 指令時開始下載,輸入 p 指令時暫停下載,輸入 r 指令時恢復下載。
// 文件:client.js const http = require("http"); const fs = require("fs"); const path = require("path"); // 請求配置 let config = { host: "localhost", port: 3000, path: "/download.txt" }; let start = 0; // 請求初始值 let step = 5; // 每次請求字符個數 let pause = false; // 暫停狀態 let total; // 文件總長度 // 創建可寫流 let ws = fs.createWriteStream(path.resolve(__dirname, config.path.slice(1))); // 下載函數 function download() { // 配置,每次范圍請求 step 個字節 config.headers = { "Range": `bytes=${start}-${start + step - 1}`; }; // 維護下次 start 的值 start += step; // 發送請求 http.request(config, res => { // 獲取文件總長度 if (typeof total !== "number") { total = res.headers["content-ranges"].match(/\/(\d*)/)[1]; } // 讀取返回數據 let buffers = []; res.on("data", data => buffers.push(data)); res.on("end", () => { // 合并數據并寫入文件 let buf = Buffer.concat(buffers); ws.write(buf); // 遞歸進行下一次請求 if (!pause && start < total) { download(); } }); }).end(); } // 監控輸入 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; } });
在上面代碼中下載的文件通過 config 中的 path 屬性配置,每次調用 download 函數下載時都會重新計算當前范圍請求的初始位置和結束位置,并設置 Range 請求頭,下一次請求靠遞歸 download 來實現。
在執行時需先啟動我們的服務器,在通過命令行輸入 node client.js 來啟動客戶端,在命令窗口輸入對應的指令進行開始下載、暫停下載和恢復下載操作。
關于怎么在NodeJS中利用Range請求實現一個下載功能問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。