专栏首页Web 开发NodeJS那些事

NodeJS那些事

下半年做了挺多活动型需求,因为我们业务人力有限,我在业务的策略是不依赖NodeJS。

而这些活动型需求,是可以用NodeJS来练练手。

ExpressJS

一个Web服务框架,几经转手,现在应该是IBM旗下的产品了。

以前我们用PHP来开发Web服务,语言层面屏蔽了很多HTTP协议的东西,可以专心业务逻辑。

而NodeJS不同,本身就跑Web服务(不管前面是否加个Nginx反向代理),所以挺多HTTP协议的细节需要我们深入了解的。

ExpressJS通过大量中间件,来帮我们屏蔽掉这些HTTP协议的细节,例如body-parsercookie-parser,帮我们解析HTTP Body和Cookies部分的内容。

app.use([path],function(req, res, next){})

整个ExpressJS,最重要的部分就是app.use()。不论是中间件?还是我们常见的app.get()\app.post(),都是从app.use()衍生出来。

每一个请求到达ExpressJS后,其处理流程是按顺序进入各个app.use()传入的回调函数中。

如果该app.use()带有path参数,则匹配path参数才会执行该回调函数。

如果该app.use()的回调函数最后还调用了next方法,则这次请求的处理流程会继续流向下一个app.use()

正因为如此,每个回调函数,只有一次调用的机会,你要么用来处理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()而导致的故障。平常我们的精力关注在路由上,中间件触发的故障会比较难发现和定位。

winston

在ExpressJS官网的最佳实践里有提到日志这点,平时我们用的console.log()是一个同步的语法,开发阶段问题不大,但不适合生产环节,官方推荐winstonBunyan两个库,我这里用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');

我们可以在ExpressJS的最后一个app.use()里面,做一个兜底的异常处理回调。

app.use(function(err, req, res){
    winston.error('unKnown Error, req: '+ req.originUrl, err);
    res.status(500);
});

这样就把我们没有预计到的异常,也兜底接住了,并记录在日志中便于回溯。

winston还有一个 winston.profile('name')的方法,用来记录两个点的时间间隔,可以做性能统计埋点。

Request & Promise

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()来做兜底异常处理。

child_process & PhantomJS

这里我要先先说说,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来保障执行,这样就能彻底解决由于脚本质量导致的服务假死问题。

PM2

一个给NodeJS服务用的守护进程,支持API和静态配置,非常强大,我建议大部分用NodeJS跑持久化服务的地方都用PM2。

PM2有多种启动方式,通常情况,建议将PM2的启动配置静态化到一个pm2.json文件中,然后通过 pm2 start pm2.json来启动。

PM2支持将log重定向,这对于多硬盘\多分区的服务器是非常友好的,我们服务器的根目录容量就非常小,如稍加注意,就会被这些NodeJS的log给撑爆,需要重定向到大容量目录下。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Framework7源码学习-1-概览

    https://raw.githubusercontent.com/qq286735628/Framework7/master/dist/js/framewor...

    libo1106
  • jQuery进阶,$.Deferred() 延迟对象

    JS里面有大量的异步方法,写着写着,代码就会变得>>。多层嵌套的回调,很影响后续代码的维护,也许今天你还记得这块回调逻辑,明天你就很有可能被这回调姿势给坑了。

    libo1106
  • 问卷星?问卷网?还是腾讯问卷

    在学生年代,我们肯定都被老师、团委、学生会坑过去做问卷调查。我们会以什么形式来收集我们所需的数据呢?是打印传单,扫楼这种传统形式?还是通过问卷星、问卷网等电子环...

    libo1106
  • 「持续更新中」JavaScript 设计模式精简代码实现

    逆葵
  • MIT研发光学深度神经网络:几乎不需能耗

    安妮 编译自 Phys.org 量子位出品 | 公众号 QbitAI ? 深度学习计算机系统是基于人工神经网络(Artificial Neural Networ...

    量子位
  • JavaScript之使用JavaScript模仿oop编程

    wfaceboss
  • 数据结构于JS也可以成为CP(三)栈

    Hello小伙伴们大家好,今天要为大家带来的是栈,这是数据结构中常用到的一种结构。它和列表有一点相似,又有些不同。相对于列表来说,栈更加高效,为啥呢,因为栈只能...

    萌兔IT
  • 译《领域驱动设计之PHP实现》架构风格(中)

    视图层可以从模型层和/或者控制层接收数据,也能向其发送数据。它的主要目的是向用户UI层呈现模型,同时在模型每次更新后刷新UI的呈现形式。一般来说,视图层接收的对...

    猿哥
  • MVVM 框架解析之双向绑定

    更好的阅读体验,点击 原文地址 ? 项目地址 MVVM 框架 近年来前端一个明显的开发趋势就是架构从传统的 MVC 模式向 MVVM 模式迁移。在传统的 M...

    牧云云
  • 聊下git pull --rebase

    有一种场景是经常发生的。 大家都基于develop拉出分支进行并行开发,这里的分支可能是多到数十个。然后彼此在进行自己的逻辑编写,时间可能需要几天或者几周。在这...

    王清培

扫码关注云+社区

领取腾讯云代金券