NodeJS服务器篇之简单静态文件合并
来源:互联网 发布:怎么优化 编辑:程序博客网 时间:2024/05/18 03:46
NodeJS
是一个基于Chrome V8
引擎的JavaScript
运行环境,其使用了事件驱动、异步I/O
机制,具有运行速度快,性能优异等特点,非常适合在分布式设备上运行数据密集型的实时应用。
本文主要介绍一下通过搭建简单的NodeJS
服务器,实现静态文件的合并,并通过浏览器访问输出的功能;同时,还会进行功能的完善,通过不断的迭代开发,从易用性、性能、安全性等等方面,较为全面的介绍一下NodeJS
服务器的开发过程,为以后的进一步学习做准备。
在下面的内容开始之前,假定您对JavaScript
已经有了一定的了解,如果您之前没有了解过,请先熟悉一下七天学会NodeJS,本文主要参考上述资料的最后一部分,为作者的开源奉献精神表示感谢。下面正式开始介绍服务器的具体实现:
需求
实现一个静态文件合并的服务器,通过请求的链接(URL
)指定需要合并的文件,之后把文件内容返回给客户端。参考链接如下:
http://127.0.0.1:8300/??a.js,b.js
分析
链接中的??
是一个分隔符,前面是需要合并的文件路径,后面是需要合并的文件名,多个文件名之间用,
分隔,因此服务器处理这个URL后返回的是各个文件的路径;之后,通过递归读取文件内容,再进行拼接合并;最后,通过响应数据输出给客户端。这是整个服务器的全部分析过程。
由于涉及到文件操作,所以需要fs
模块、path
模块;加上服务器模块http
,一共需要三个模块:fs、path、http
。
第一版
源码如下:
var fs = require('fs'), path = require('path'), http = require('http');var MIME = { '.css': 'text/css', '.js': 'application/javascript'};// 合并文件内容function combineFiles(pathnames, callback) { var output = []; (function next(i, len) { if (i < len) { fs.readFile(pathnames[i], function (err, data) { if (err) { callback(err); } else { output.push(data); next(i + 1, len); } }); } else { const data = Buffer.concat(output); console.log(data); callback(null, data); } }(0, pathnames.length));}function main(argv) { // 从文件读取配置参数 // var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')), // root = config.root || '.', // port = config.port || 80; // 直接给定配置参数 var root = __dirname; var port = 8300; http.createServer(function (request, response) { var urlInfo = parseURL(root, request.url); console.log(urlInfo); combineFiles(urlInfo.pathnames, function (err, data) { if (err) { response.writeHead(404); response.end(err.message); } else { response.writeHead(200, { 'Content-Type': urlInfo.mime }); response.end(data); } }); }).listen(port);}// 解析文件路径function parseURL (root, url) { var base, pathnames, parts; if (url.indexOf('??') === -1) { url = url.replace('/', '/??'); } parts = url.split('??'); base = parts[0]; pathnames = parts[1].split(',').map(function(value) { var filePath = path.join(root, base, value); return filePath; }); return { mime: MIME[path.extname(pathnames[0])] || 'text/plain', pathnames: pathnames };}main(process.argv.slice(2));/*测试URL: 127.0.0.1:8300/??a.js,b.js输出: hello kelvin world */
以上代码完整实现了服务器的功能,可以用测试URL
请求,就会输出其后的内容。其中,有几点需要注意:
- 命令行参数可以通过读取
JSON
配置文件,或者直接在main
函数内设定(缺点是修改不方便,配置不灵活) - 入口
main
函数开启了http
服务器;combineFiles
函数负责异步读取文件内容,并合并文件内容;parseULR
函数负责解析URL
,并返回文件的MIME
类型(在返回数据给客户端时,指定数据的类型)和文件名数组,。
服务器的工作流程如下:
发送请求 等待服务端响应 接收响应---------+----------------------+-------------> -- 解析请求 ------ 读取a.js ------ 读取b.js ------ 读取c.js -- 合并数据 -- 输出响应
第二版
由于第一版中,代码是把文件内容全部读取到内存后,再进行数据合并的,这会导致如下问题:
- 当请求的文件较多,需要合并的数据量又比较大时,串行读取文件会比较耗时,拖慢服务的相应时间
- 每次都完整的把数据读到内存缓存起来,当服务器并发数较大时,就会有较大的内存开销
针对上面的第一个问题,如果改为并行读取方式,对于机械磁盘来说,需要不停的切换磁头,反而会降低I/O
效率。而对于固态硬盘,是存在多个并行的I/O
的,对单个请求采用并行也不会提高效率。因此,采用流式读取方式:一遍读取,一遍输出,把相应的输出时机提前至读取第一个文件的时刻,这样就能解决上述的问题。
修改后的服务器工作流程如下:
发送请求 等待服务端响应 接收响应---------+----+-------------------------------> -- 解析请求 -- 检查文件是否存在 -- 输出响应头 ------ 读取和输出a.js ------ 读取和输出b.js ------ 读取和输出c.js
可以看到,调整后的代码是边读取边输出,即快速响应请求,有减少了内存的压力。
源码如下:
var fs = require('fs'), path = require('path'), http = require('http');var MIME = { '.css': 'text/css', '.js': 'application/javascript'};function main(argv) { var root = __dirname; var port = 8300; http.createServer((request, response) => { var urlInfo = parseURL(root, request.url); validateFiles(urlInfo.pathnames, (err, pathnames) => { if (err) { response.writeHead(404); response.end(err.message); } else { response.writeHead(200, { 'Content-Type': urlInfo.mime }); outputFiles(pathnames, response); } }) }).listen(port);}function outputFiles(pathnames, writer) { (function next(i, len) { if (i <len) { var reader = fs.createReadStream(pathnames[i]); reader.pipe(writer, {end: false}); reader.on('end', function() { next(i + 1, len); }) } else { writer.end(); } }(0, pathnames.length));}function validateFiles(pathnames, callback) { (function next(i, len) { if (i < len) { fs.stat(pathnames[i], (err, stats) => { if (err) { callback(err); } else if (!stats.isFile()){ callback(new Error()); } else { next(i + 1, len); } }); } else { callback(null, pathnames); } }(0, pathnames.length));}function parseURL (root, url) { var base, pathnames, parts; if (url.indexOf('??') === -1) { url = url.replace('/', '/??'); } parts = url.split('??'); base = parts[0]; pathnames = parts[1].split(',').map(function(value) { var filePath = path.join(root, base, value); return filePath; }); return { mime: MIME[path.extname(pathnames[0])] || 'text/plain', pathnames: pathnames };}main();
第三版
服务器的功能和性能已经得到初步满足,接下来我们要考虑稳定性。由于没有系统是绝对的稳定,都存在一定的宕机风险,而这一问题不可避免,所以我们要尽量减少宕机的时间,比如增加一个守护进程,在服务器挂掉后立即重启。并且NodeJS
官方也建议在出现异常时重启,因为这时系统处于一种不稳定的状态。
所以,我们利用NodeJS
的进程管理机制,将守护进程作为父进程,将服务器进程作为子进程,让父进程监控子进程的运行状态,在其异常时立即退出重启子进程。
守护进程代码如下:
var cp = require('child_process');var worker;function spawn(server, config) { worker = cp.spawn('node', [server, config]); worker.on('exit', (code) => { console.log("code: " + code) if (code != 0) { console.log('自动重启'); spawn(server, config); } });}function main(argv) { spawn('server2.js', argv[0]); process.on('SIGTERM', () => { worker.kill(); process.exit(0); });}main(process.argv.slice(2));
服务器代码也要在main
函数里做如下调整:
function main(argv) { ... server = http.createServer((request, response) => { var urlInfo = parseURL(root, request.url); validateFiles(urlInfo.pathnames, (err, pathnames) => { ... }) }).listen(port); process.on('SIGTERM', () => { server.close(() => { process.exit(0); }); });}
这样调整后,守护进程会进一步启动和监控服务器进程。此外,为了能够正常终止服务,我们让守护进程在接收到SIGTERM信号时终止服务器进程。而在服务器进程这一端,同样在收到SIGTERM信号时先停掉HTTP服务再正常退出。至此,我们的服务器程序就靠谱很多了。
至此,NodeJS
合并文件的服务器开发完成,当然还有许多不足之处,比如:提供日志通知访问量、充分利用多核CPU
等等。如有兴趣,可以在此基础之上,做进一步的开发。
源码地址
https://github.com/BirdandLion/NodeJSCombineFiles
参考资料
七天学会NodeJS
Node.js官网
- NodeJS服务器篇之简单静态文件合并
- NodeJS 创建简单的静态服务器
- nodejs之搭建简单服务器
- 【深入浅出Node.js系列十】一个简单的静态文件合并服务器
- nodejs小记之简单的node服务器
- nodejs静态服务器anywhere
- python编写简单服务器提供静态文件
- nodejs之静态页面
- nodejs小记之handlebars视图与静态文件布置
- NodeJs 读取服务器文件
- 【nodejs】http加载静态文件
- nodejs静态资源文件管理
- Nodejs express之静态资源
- Nodejs之静态资源处理
- 利用nodeJs的anywhere搭建静态服务器
- nodejs之文件操作
- nodejs 之文件上传
- 用nodeJS搭建简单的静态服务
- 项目发布到Tomcat的三种方法
- Flood Fill Algorithm
- Maven开发Android指南 3 使用Android Maven Archetypes 创建新项目
- Google I/O ‘17 推出的物理动画库PhysicsBasedAnimation
- Maven开发Android指南 4 与Eclipse整合(m2e-android )
- NodeJS服务器篇之简单静态文件合并
- Jsp9个内置对象详解
- Maven开发Android指南 5 调试
- Apache CXF开发Web Service 开发Web Service之Kick Start
- plot画图时可以设定线条参数
- java实现2048小游戏
- 考研数学——高数18讲
- Apache CXF开发Web Service 目录
- (7)学习css