前言
本篇使用 NodeJS 的 HTTP 服務創建客戶端,使用 Range 請求實現下載功能,并通過本篇的 Demo 擴展在業務中實現斷點續傳等功能的思路。
服務端的實現
我們通過 http 模塊創建服務器處理 Range 請求,在服務器代碼中我們為了減少回調嵌套使用 async 函數,所以需要將異步的操作方法轉換成 Promise,以往我們使用 util 的 promisify 來一個一個轉換異步方法,比較麻煩,我們這次使用第三方模塊 mz 并直接引入轉換好的替代模塊。
使用 mz 之前需要先安裝:
npm install mz
服務端代碼如下:
// 文件:server.jsconst 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。
新聞熱點
疑難解答
圖片精選