温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么在NodeJS中利用Range请求实现一个下载功能

发布时间:2021-03-08 14:46:16 来源:亿速云 阅读:325 作者:Leah 栏目:web开发

怎么在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请求实现一个下载功能问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI