下半年作了挺多活动型需求,由于咱们业务人力有限,我在业务的策略是不依赖NodeJS。javascript
而这些活动型需求,是能够用NodeJS来练练手。html
一个Web服务框架,几经转手,如今应该是IBM旗下的产品了。java
之前咱们用PHP来开发Web服务,语言层面屏蔽了不少HTTP协议的东西,能够专心业务逻辑。node
而NodeJS不一样,自己就跑Web服务(无论前面是否加个Nginx反向代理),因此挺多HTTP协议的细节须要咱们深刻了解的。ajax
ExpressJS经过大量中间件,来帮咱们屏蔽掉这些HTTP协议的细节,例如body-parser和cookie-parser,帮咱们解析HTTP Body和Cookies部分的内容。编程
整个ExpressJS,最重要的部分就是app.use()。不管是中间件?仍是咱们常见的app.get()\app.post(),都是从app.use()衍生出来。json
每个请求到达ExpressJS后,其处理流程是按顺序进入各个app.use()传入的回调函数中。api
若是该app.use()带有path参数,则匹配path参数才会执行该回调函数。服务器
若是该app.use()的回调函数最后还调用了next方法,则此次请求的处理流程会继续流向下一个app.use()。cookie
正由于如此,每一个回调函数,只有一次调用的机会,你要么用来处理req阶段,要么用来处理res阶段。(宣称是下一代Web框架的Koa,则是利用ES6里面的语法糖,实现了一个回调函数有屡次执行的机会)。
难道ExpressJS就不能让回调函数既处理req又处理res吗?
非也,app.get()\app.post()就能同时处理req和res,只是ExpressJS的把能同时处理req和res的称为路由(Routing),而只能处理其中一种的称为中间件(Middleware)。这样造成一个不成文的约定,用中间件来加工req,用路由来加工req和res。
其实中间件一次只能处理一个阶段是有好处的。HTTP有一个特性,是HTTP Header必须早于HTTP Body返回。若是中间件也用来处理res,就会有很是大概率出现res.send()早于res.header()而致使的故障。日常咱们的精力关注在路由上,中间件触发的故障会比较难发现和定位。
在ExpressJS官网的最佳实践里有提到日志这点,平时咱们用的console.log()是一个同步的语法,开发阶段问题不大,但不适合生产环节,官方推荐winston和Bunyan两个库,我这里用winston。
winston支持分级日志,自带info\warn\error三级
var winston = require('winston'); winston.info("127.0.0.1 - there's no place like home"); winston.warn("127.0.0.1 - there's no place like home"); winston.error("127.0.0.1 - there's no place like home");
还能够传递自定义等级
winston.log('level','log info'); winston.log('level','log info');
咱们能够在ExpressJS的最后一个app.use()里面,作一个兜底的异常处理回调。
app.use(function(err, req, res){ winston.error('unKnown Error, req: '+ req.originUrl, err); res.status(500); }); app.use(function(err, req, res){ winston.error('unKnown Error, req: '+ req.originUrl, err); res.status(500); });
这样就把咱们没有预计到的异常,也兜底接住了,并记录在日志中便于回溯。
winston还有一个 winston.profile('name')
的方法,用来记录两个点的时间间隔,能够作性能统计埋点。
NPM依赖榜排行第七的库(不知道是第七仍是第三),跑NodeJS服务常常能用到,用来调用第三方接口。
Request的具体用法和$.ajax()雷同。
因为Request是异步的,为了便于业务使用,最好用Promise对每一个具体的API调用进行封装。NodeJS和IO.js合并后,已经完美支持Promise语法了。
Promise的语法这里不展开,直接说怎么封装Request。
function getStaffInfoByName (name){ // 我习惯将外部接口都归集到一个api对象中便于查看 var getStaffInfoByName = api.getStaffInfoByName; // 这个外部接口,是经过GET方式传参数的,最近学会一种{}占位替换的写法,让接口更易读 var url = getStaffInfoByName.replace('{name}', name); // 返回外部一个Promise对象 return new Promise(function(resolve, reject){ // 其实接口的参数也能够这里配置,看我的习惯 var reqOpts = { url: url }; // 发起调用 request(reqOpts, function (error, response, body) { if (!error) { var json = JSON.parse(body); if (json.Ret === 0){ // 若是一切正常,就resolve,并传递数据 resolve(json.Data); } else { // 业务异常就reject,并传递错误信息 reject(json.ErrMsg); } } else { // 这里reject网络错误 reject(error); } }); }); } // 把这个方法暴露到外部 module.exports = getStaffInfoByName;
业务调用的时候,就能够安心处理正常逻辑,异常已经被屏蔽了
app.get('/getUserInfo', function(req, res, next){ var name = req.query.name; getStaffInfoByName(name).then(function(data){ res.render('user', data); }).catch(next); });
你看,如今的业务逻辑就是渲染。而其余异常,则会被一路抛出,直到最后一个app.use()来作兜底异常处理。
这里我要先先说说,NodeJS的到来,让我竟然有机会学习进程\线程的编程。
这里贴一个ChildProces的官方栗子。
var child_process = require('child_process'); // spawn的第一个参数是执行的命令,第二个则是命令的参数列表,返回值的该进程的句柄 var ls = child_process.spawn('ls', ['-lh', '/usr']); // 支持对进程stdout的监听 ls.stdout.on('data', function (data) { console.log('stdout: ' + data); }); // 支持对进程stderr的监听 ls.stderr.on('data', function (data) { console.log('stderr: ' + data); }); // 支持监听该进程的close事件 ls.on('close', function (code) { console.log('child process exited with code ' + code); });
说完语法,说应用。
咱们业务有两个地方用到了PhantomJS,PhantomJS支持CLI调用和Web服务两种方式,而其自身的Web服务是经过Mongoose实现的。
最初咱们在使用PhantomJS的Web服务的时候,常常遇到其假死的情况。因为是假死,各类守护进程的策略无法实施(侦测不到进程的任何异常),最后同事采用暴力的定时kill后重启策略。
也许上面的不稳定,是咱们PhantomJS的脚本写的有问题致使的,但无论怎么样,脚本问题致使服务不稳定是不能接受的,后来咱们改用CLI的方式调用PhantomJS。
app.get('/thumbnail', function(req, res, next){ // 经过GET ?target={url}的方式,传递截图目标网址 var url = req.query.target; var child_process = require('child_process'); // 以子进程的方式启动PhantomJS var phantom = child_process.spawn('phantomjs', ['thumbnail.js', url]); // 监听PhantomJS进程的exit事件 phantom.on('exit', function(code){ switch (code){ case 0: res.send('截图成功') break; case 1: res.send('截图失败,缘由是xxx'); break; default: res.send('截图失败,缘由未知'); break; } }); });
这里咱们用ExpressJS替换了PhantomJS自带的Mongoose来实现Web服务,并每次经过子进程的方式唤起PhantomJS,而咱们自己的ExpressJS由PM2来保障执行,这样就能完全解决因为脚本质量致使的服务假死问题。
一个给NodeJS服务用的守护进程,支持API和静态配置,很是强大,我建议大部分用NodeJS跑持久化服务的地方都用PM2。
PM2有多种启动方式,一般状况,建议将PM2的启动配置静态化到一个pm2.json文件中,而后经过 pm2 start pm2.json
来启动。
PM2支持将log重定向,这对于多硬盘\多分区的服务器是很是友好的,咱们服务器的根目录容量就很是小,如稍加注意,就会被这些NodeJS的log给撑爆,须要重定向到大容量目录下。
同步于个人博客