使用原生node.js搭建HTTP服务器,支持MP4视频、图片传输,支持下载rar文件
前言
如何安装node.js,如何搭建一个简易的http服务器我这里就不再赘述了,不懂的同学可以先去学习一下。当然了,我写的也就属于简易版的增强版,大家有什么高见的欢迎提出,然后进入正题。
目录结构
|-server.js |-router.js
|-test.html
|-css|-test.css
|-js |-test.js
server.js
//原生模块 var http = require('http'); var fs = require('fs'); var url = require('url');
//自定义模块 var router = require('./router.js');
http.createserver(function(request,response){ //获取客户端访问的路径 var pathname = url.parse(request.url).pathname; //如果用户只输入域名就改变访问路径,并发送主页的内容给客户端 if(pathname == "/"){ pathname = "/index.html"; } //获取当前请求客户端的ip地址 var ipv4 = get_client_ipv4(request); //输出日志到控制台 showlog(ipv4,("请求"+decodeuri(pathname))); //判断文件是否存在 fs.exists(__dirname + decodeuri(pathname),function(exists){ if(exists){ //使用router模块的函数 router.readfilebysuffixname(pathname,fs,request,response); }else{ console.log(decodeuri(pathname)+"文件不存在!"); //文件不存在,向客户端发送404状态码,并发送该文件不存在的字符串 response.writehead(404,{"content-type":"text/plain"}); response.end(pathname+"文件不存在!"); } }); }).listen(80); //监听80端口 console.log('web服务已运行!'); /** * @desc 获取ipv4地址 * @param req htttp.request * @return string 32位ip地址 */ function get_client_ipv4(req) { //获取任意浏览器的ip地址, var ip = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteaddress || req.socket.remoteaddress || req.connection.socket.remoteaddress || ''; //获取到的ip地址中存在ipv4和ipv6的地址,我们只需要ipv4的地址 if(ip.split(',').length>0){ ip = (ip.split(',')[0]).match(/(\d+\.\d+\.\d+\.\d+)/)[0]; } return ip; }; /** * @desc 向控制台输出日志,自动在头部添加时间、地址 * @param ipv4 string * @param message string */ function showlog(ipv4,message){ //获取当前时间 var date = new date(); //转换为本地时间的字符串形式并输入到控制台 console.log(date.tolocaledatestring() + " " + date.tolocaletimestring() + " " + ipv4 + " " + message); }
首先引入模块,使用http.createserver创建http服务器,并监听80端口;http.createserver的回调函数接收两个值,一个request请求对象,一个response响应对象,request对象可以获取到客户端请求的信息,response对象用来返回数据到客户端;上面创建了两个简单的工具函数,分别用来获取客户端的ipv4地址、向控制台输出日志;使用fs.exists函数判断客户端请求的文件是否存在,如果不存在则返回404状态码,如果存在,则使用下面router.js中创建的readfilebysuffixname函数,读取相应的文件并根据后缀名设置响应头,然后发送数据到客户端。
router.js
/** * @desc 根据后缀名读取文件 * @param pathname string 文件路径 url.parse(request.url).pathname * @param fs fs * @param request htttp.request * @param response https.response */ exports.readfilebysuffixname = function(pathname,fs,request,response){ var ext = pathname.match(/(\.[^.]+|)$/)[0];//取得后缀名 switch(ext){ //根据后缀名读取相应的文件,设置响应头,并发送到客户端 case ".css": case ".js": //读取文件 fs.readfile("."+request.url,'utf-8',function(err,data){ if(err) throw err; response.writehead(200,{ //根据不同的后缀设置不同的响应头 "content-type":{ ".css":"text/css", ".js":"application/javascript", }[ext] }); response.write(data); //发送文件数据到客户端 response.end(); //发送完成 }); break; //jpg、gif、png后缀的图片 case ".jpg": case ".gif": case ".png": //二进制读取文件 fs.readfile("."+decodeuri(request.url),'binary',function(err,data){ if(err)throw err; response.writehead(200,{ "content-type":{ ".jpg":"image/jpeg", ".gif":"image/gif", ".png":"image/png", }[ext] }); response.write(data,'binary'); //发送二进制数据 response.end(); }); break; case ".mp4": //读取文件的状态 fs.stat('.'+decodeuri(request.url),function(err,stats){ if(err){ if(err.code === 'enoent'){ return response.sendstatus(404); } response.end(err); } //断点续传,获取分段的位置 var range = request.headers.range; if(!range){ //206状态码表示客户端通过发送范围请求头range抓取到了资源的部分数据 //416状态码表示所请求的范围无法满足 return response.sendstatus(416); } //替换、切分,请求范围格式为:content-range: bytes 0-2000/4932 var positions = range.replace(/bytes=/,"").split("-"); //获取客户端请求文件的开始位置 var start = parseint(positions[0]); //获得文件大小 var total = stats.size; //获取客户端请求文件的结束位置 var end = positions[1] ? parseint(positions[1],10):total -1; //获取需要读取的文件大小 var chunksize = (end-start) + 1; response.writehead(206,{ "content-range":"bytes "+ start+"-"+end+"/"+total, "accept-ranges":"bytes", "content-length":chunksize, "content-type":"video/mp4" }); //创建读取流 var stream = fs.createreadstream('.'+decodeuri(request.url),{start:start,end:end}) .on("open",function(){ stream.pipe(response); //读取流向写入流传递数据 }).on("error",function(err){ response.end(err); }); }); break; case ".rar": //同步读取文件状态 var stats = fs.statsync("." + decodeuri(request.url)); response.writehead(200,{ "content-type": "application/octet-stream", //相应该文件应该下载 //模板字符串 "content-disposition": `attachment; filename = ${pathname.replace("/","")}`, "content-length":stats.size }); //管道流 fs.createreadstream("." + decodeuri(request.url)).pipe(response); break; //以上都不匹配则使用默认的方法 default: fs.readfile('.'+pathname,'utf-8',function(err,data){ response.writehead(200,{ "content-type":"text/html" }); response.write(data); response.end(); }); } }
router.js文件中只有一个readfilebysuffixname函数,该函数的作用是判断客户端访问文件的后缀名,css、js、图片、mp4视频、rar文件等都能成功返回到客户端;其中视频和下载文件使用流传输;因为如果不使用流的话,服务器要先缓存文件,然后再发送文件到客户端;使用html5视频的客户端会发送一个content-range的值到服务器,服务器根据这个range值读取一个文件指定的部分,并返回这个特定的部分数据到客户端,就实现了视频的断点续传,你可以随意的跳转到视频的任意一部分了!
进入项目文件夹,输入
node ./server.js
服务器端输出日志和测试页面
favicon.ico文件是该页面的图标文件,第一次进入页面浏览器会自动请求。
上一篇: [Linux] 进程间通信