国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > JS > 正文

NodeJS使用Range請求實(shí)現(xiàn)下載功能的方法示例

2024-05-06 16:46:23
字體:
供稿:網(wǎng)友

前言

本篇使用 NodeJS 的 HTTP 服務(wù)創(chuàng)建客戶端,使用 Range 請求實(shí)現(xiàn)下載功能,并通過本篇的 Demo 擴(kuò)展在業(yè)務(wù)中實(shí)現(xiàn)斷點(diǎn)續(xù)傳等功能的思路。

服務(wù)端的實(shí)現(xiàn)

我們通過 http 模塊創(chuàng)建服務(wù)器處理 Range 請求,在服務(wù)器代碼中我們?yōu)榱藴p少回調(diào)嵌套使用 async 函數(shù),所以需要將異步的操作方法轉(zhuǎn)換成 Promise,以往我們使用 util 的 promisify 來一個(gè)一個(gè)轉(zhuǎn)換異步方法,比較麻煩,我們這次使用第三方模塊 mz 并直接引入轉(zhuǎn)換好的替代模塊。

使用 mz 之前需要先安裝:

npm install mz

服務(wù)端代碼如下:

// 文件:server.jsconst http = require("http");const path = require("path");const url = require("url");// 引入 mz 模塊轉(zhuǎn)換成 Promise 的 fs 模塊const fs = require("mz/fs");// 請求處理函數(shù)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 請求頭將返回范圍請求的數(shù)據(jù)  if (range) {    // 獲取范圍請求的開始和結(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;    // 處理請求頭中范圍參數(shù)不傳的問題    start = start ? ParseInt(start) : 0;    end = end ? ParseInt(end) : total - 1;    // 響應(yīng)客戶端    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 請求頭時(shí)將整個(gè)文件內(nèi)容返回給客戶端    fs.createReadStream(p).pipe(res);  }}// 創(chuàng)建服務(wù)器const server = http.createServer(listener);// 監(jiān)聽端口server.listen(3000, () => {  console.log("server start 3000");});

在上面服務(wù)端的代碼中,需要兼容 Range 請求和普通請求,兩種請求的區(qū)別是,如果客戶端發(fā)送的是 Range 請求,會攜帶 Range:bytes=0-5 格式的請求頭,我們可以通過 req 的 headers 屬性獲取,在獲取請求頭時(shí),原本大寫字母開頭 NodeJS 統(tǒng)一處理成小寫,所以獲取時(shí)應(yīng)小寫。

如果是 Range 請求則通過可讀流讀取對應(yīng)的內(nèi)容返回客戶端,如果不是,則通過可讀流讀取整個(gè)文件返回客戶端,在響應(yīng) Range 請求的過程中需要設(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ù)開始的索引,5 為結(jié)束的索引(包含),100 為文件的總字節(jié)數(shù)。

在通過 url 和 path 模塊解析和拼接下載文件路徑時(shí),應(yīng)該進(jìn)行錯(cuò)誤檢測,如果文件不存在則直接返回客戶端 Not Found。

我們可以使用 curl 命令來檢測我們的服務(wù)端代碼,在命令行工具中輸入下面命令,在命令窗口查看返回值是否正確。

curl -v --header "Range:bytes=0-5" http://localhost:3000

客戶端的實(shí)現(xiàn)

在上面使用 curl 命令來訪問我們的服務(wù)器時(shí),只能請求固定范圍的數(shù)據(jù),而不是類似于下載功能,每次都下載一個(gè)范圍的數(shù)據(jù),但是想要多次下載并自動(dòng)維護(hù) Range 的范圍需要借助我們自己實(shí)現(xiàn)的客戶端邏輯。

為了簡便,我們的下載客戶端是在命令行窗口運(yùn)行的,通過指令來模擬實(shí)際項(xiàng)目中的開始下載、暫停和恢復(fù)按鈕,當(dāng)在窗口中輸入 s 指令時(shí)開始下載,輸入 p 指令時(shí)暫停下載,輸入 r 指令時(shí)恢復(fù)下載。

// 文件:client.jsconst 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; // 每次請求字符個(gè)數(shù)let pause = false; // 暫停狀態(tài)let total; // 文件總長度// 創(chuàng)建可寫流let ws = fs.createWriteStream(path.resolve(__dirname, config.path.slice(1)));// 下載函數(shù)function download() {  // 配置,每次范圍請求 step 個(gè)字節(jié)  config.headers = {    "Range": `bytes=${start}-${start + step - 1}`;  };  // 維護(hù)下次 start 的值  start += step;  // 發(fā)送請求  http.request(config, res => {    // 獲取文件總長度    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ù)并寫入文件      let buf = Buffer.concat(buffers);      ws.write(buf);      // 遞歸進(jìn)行下一次請求      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;  }});

在上面代碼中下載的文件通過 config 中的 path 屬性配置,每次調(diào)用 download 函數(shù)下載時(shí)都會重新計(jì)算當(dāng)前范圍請求的初始位置和結(jié)束位置,并設(shè)置 Range 請求頭,下一次請求靠遞歸 download 來實(shí)現(xiàn)。

在執(zhí)行時(shí)需先啟動(dòng)我們的服務(wù)器,在通過命令行輸入 node client.js 來啟動(dòng)客戶端,在命令窗口輸入對應(yīng)的指令進(jìn)行開始下載、暫停下載和恢復(fù)下載操作。

總結(jié)

相信現(xiàn)在已經(jīng)了解什么是范圍請求,范圍請求客戶端和服務(wù)端需要做些什么,其實(shí)說白了就是對應(yīng)的請求頭和響應(yīng)頭的使用,需要注意的是范圍請求的響應(yīng)狀態(tài)碼為 206,這樣的需求在一些上傳、下載資源的網(wǎng)站也很常見,其目的就是為了讓我們實(shí)現(xiàn)斷點(diǎn)續(xù)傳,不至于一次沒有上傳或下載完成的資源文件,在下一次的做同樣操作時(shí)需要重新來過,可以接著上次的位置繼續(xù),范圍請求在視頻網(wǎng)站上也廣泛應(yīng)用,邊請求邊觀看,不至于一次加載整個(gè)視頻資源,節(jié)省流量,節(jié)省時(shí)間。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VeVb武林網(wǎng)。


注:相關(guān)教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 江山市| 莆田市| 武宁县| 台湾省| 渭源县| 南陵县| 洪泽县| 新平| 屯留县| 宿松县| 盱眙县| 元氏县| 临潭县| 舟曲县| 枝江市| 桑植县| 建湖县| 镇安县| 江口县| 姚安县| 诏安县| 石河子市| 南川市| 安吉县| 黄陵县| 鄂托克前旗| 安溪县| 滁州市| 翁源县| 长寿区| 灵川县| 林州市| 新干县| 鄱阳县| 民和| 义马市| 湟中县| 永胜县| 湟中县| 冀州市| 黑河市|