当前位置: 首页 > 后端技术 > Node.js

NodeJS使用Range请求实现下载功能

时间:2023-04-03 22:30:25 Node.js

阅读原文前言本文使用NodeJSHTTP服务创建客户端,使用Range请求实现下载功能,通过Demo拓展业务中实现断点续传等功能的思路这篇文章的延伸。服务器端的实现我们通过http模块创建一个服务器来处理Range请求。在服务器代码中,我们使用异步函数来减少回调嵌套,所以我们需要将异步操作方法转换为Promise。以前我们都是使用util的promisify来将异步方法一一转换,比较麻烦,这次我们使用第三方模块mz,直接引入转换后的替换模块。使用mz之前需要先安装:npminstallmz服务器代码如下://file:server.jsconsthttp=require("http");constpath=require("路径");consturl=require("url");//将mz模块导入Promisefs模块constfs=require("mz/fs");//请求处理函数asyncfunctionlistener(req,res){//获取范围请求header,格式为Range:bytes=0-5letrange=req.headers["range"];//下载文件路径letp=path.resovle(__dirname,url.parse(url,true).pathname);//存在范围请求头将返回范围请求的数据if(range){//获取范围请求的开始和结束位置let[,start,end]=range.match(/(\d*)-(\d*)/);//错误处理try{letstatObj=awaitfs.stat(p);}catch(e){res.end("未找到");}//文件中的总字节数lettotal=statObj.size;//处理requestheader没有传range参数的问题start=start?ParseInt(开始):0;结束=结束?ParseInt(结束):总计-1;//响应客户端res.statusCode=206;res.setHeader("Accept-Ranges","bytes");res.setHeader("Content-Range",`bytes${开始}-${结束}/${总计}`);fs.createReadStream(p,{开始,结束}).pipe(res);}else{//当没有range请求头时,返回整个文件内容给Clientfs.createReadStream(p).pipe(res);}}//创建服务器constserver=http.createServer(listener);//监听端口server.listen(3000,()=>{console.log("serverstart3000");});在上面的服务端代码中,需要兼容Range请求和普通请求。两种请求的区别在于,如果客户端发送Range请求,会携带Range:bytes=0-5的格式,我们可以通过req的headers属性获取请求头。NodeJS在获取请求头的时候,本来是大写字母开头的,统一处理成小写,所以获取的时候应该是小写的。如果是Range请求,则通过可读流读取相应的内容返回给客户端。如果没有,则通过可读流读取整个文件,返回给客户端。在响应Range请求的过程中,需要设置响应状态为206,需要设置响应头Accept-Ranges的值为bytes,需要设置响应头Content-Range的值为byte0-5/100格式,0为返回数据的起始索引,5为结束索引(含),100为文件总字节数。通过url和path模块解析拼接下载文件路径时,需要进行错误检测,如果文件不存在,则直接返回NotFound给客户端。我们可以使用curl命令检测我们的服务器代码,在命令行工具中输入如下命令,在命令窗口中查看返回值是否正确。curl-v--header"Range:bytes=0-5"http://localhost:3000执行客户端使用curl命令访问我们服务器时,只能请求固定范围的数据,不能类似downloading函数,每次下载一个范围的数据,但是想要多次下载并自动维护Range的范围,需要借助我们自己的客户端逻辑。为了简单起见,我们的下载客户端在命令行窗口中运行,并使用命令来模拟实际项目中的开始下载、暂停和恢复按钮。当在窗口中输入s命令时,开始下载,输入p命令暂停下载。输入r命令后恢复下载。//文件:client.jsconsthttp=require("http");constfs=require("fs");constpath=require("path");//请求配置letconfig={host:"localhost",port:3000,path:"/download.txt"};letstart=0;//请求初始值letstep=5;//每个请求的字符数letpause=false;//暂停状态lettotal;//文件总长度//创建可写流letws=fs.createWriteStream(path.resolve(__dirname,config.path.slice(1)));//下载函数functiondownload(){//配置,每个范围请求步骤bytesconfig.headers={"Range":`bytes=${start}-${start+step-1}`;};//保持下一个开始的值start+=step;//发送请求http.request(config,res=>{//获取文件的总长度if(typeoftotal!=="number"){total=res.headers["content-ranges"].match(/\/(\d*)/)[1];}//读取返回数据letbuffers=[];res.on("data",data=>buffers.push(data));res.on("end",()=>{//合并数据并写入文件letbuf=Buffer.concat(buffers);ws.write(buf);//递归下一个请求if(!pause&&start{//getCommandletins=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请求的起始位置和结束位置,并设置Range请求头。下一个请求是通过递归下载实现的。执行时,我们需要先启动我们的服务器。通过命令行输入nodeclient.js启动客户端,在命令窗口输入相应的命令开始下载。,暂停下载和恢复下载操作。综上所述,相信到此我已经明白了什么是范围请求,以及范围请求的客户端和服务端需要做什么。其实就是使用对应的请求头和响应头。需要注意的是范围请求的响应状态码是206,所以这种需求在一些网站上也很常见,用于上传下载资源。它的目的是让我们可以断点续传,这样下次做同样的操作时,一次没有上传或下载过的资源文件需要重新开始。您可以从之前的位置继续。范围请求在视频网站上也被广泛使用。可以边点播边看,不需要一次性加载整个视频资源,节省流量和时间。