成熟的Web Service技术,例如Fast CGI、J2EE、php,必然会对代码异常有足够的保护,好的Web必然会在出错后给出友好的提示,而不是莫名其妙的等待504超时。
而node.js这里比较年轻,而开发人员就更年轻了,大家都没有太多的经验,也没有太多的参考。

###单进程+PM2


最简单的方式http处理方式,可以经常见到这样的模式:

var http = require('http');

http.createServer(function (req, res) {
  res.end('hello');
}).listen(80);

简单处理请求,没有任何的全局异常处理。也就是说,只在业务逻辑中加一些try catch,信心满满的说,不会出现未捕获的异常。

  1. 一般来说,这样都没太多问题,正常返回的时候一切都好。
  2. 但如果,哪天,某个代码出现了点bug~~ 谁都不是神,总有bug的时候。
var http = require('http');

http.createServer(function (req, res) {
  var a;
  a.b();
  res.end('hello');
}).listen(80);

例如这样,node.js进程马上会因为报错而挂掉~~~

聪明的孩子,也许早就听说世界上有forever、pm2等玩意。
于是,服务启动就变成

pm2 start index.js

这样的模式太常见,尤其是内部的小系统。pm2监控node.js进程,一旦挂掉,就重启。

似乎
这样也挺好的,简单易懂,整体的web服务不会受到太大影响。
这种就是最最最简单的模式:单进程+pm2。

###第一个全局处理:process.on(‘uncaughtException’)


不过,哪里出错了,似乎都不知道,也不大好,总得记录一下错误在哪里吧?
于是,聪明的孩子又找到了process.on(‘uncaughtException’)

process.on('uncaughtException', function(er){
  console.error("process.on('uncaughtException')", er);
});

这样通过log就可以发现哪里出错了。

而且因为截获了异常,所以进程也不会挂掉了~~~ 虽然按照官方的说法,一旦出现未处理的异常,还是应该重启进程,否则可能有不确定的问题。
好了,似乎
这个方式已经很完美了~~~ 程序不挂掉,也能输出log。那么聪明的孩子还要做更多的事吗?

###致命问题:出错后,没有任何返回


哪天老板体验了一下产品,正好逮到了一次出错,此时页面已经显示加载中,等了半天之后,终于出现“服务器错误”。
可以想象,老板肯定要发话了,做小弟的必然菊花一紧,好了,又有活干了。。。
那么,我们的目标就是,要在出错后,能友好的返回,告诉用户系统出错了,给个有趣的小图,引导一下用户稍后重试。
但,单靠process不可能完成啊,因为错误来自五湖四海,单凭error信息不可能知道当时的request和response对象是哪个。
此时只能翻书去。
在官网资料中,可以发现domain这个好东西。虽然官方说正在出一个更好的替代品,不过在这出来之前,domain还是很值得一用的。

This module is pending deprecation. Once a replacement API has been finalized, this module will be fully deprecated.

关于domain的文章,在网上挺多的,直接了当,列出最简单的Web处理方式吧

const domain = require('domain');
const http = require('http'); http.createServer(function (req, res) {
var d = domain.create(); d.on('error', function (er) {
console.error('Error', er);
try {
res.writeHead(500);
res.end('Error occurred, sorry.');
} catch (er) {
console.error('Error sending 500', er, req.url);
}
}); d.run(handler); function handler() {
var a;
a.b();
res.end('hello');
}
}).listen(80);

上边的代码片段,在每次request处理中,生成了一个domain对象,并注册了error监听函数。
这里关键点是run函数,在d.run(handler)中运行的逻辑,都会受到domain的管理,简单理解,可以说,给每一个request创建了独立的沙箱环境。(虽然,事实没有这么理想)
request的处理逻辑,如果出现未捕获异常,都会先被domain接收,也就是on('error')。
由于每个request都有自己独立的domain,所以这里我们就不怕error处理函数串台了。加上闭包特性,在error中可以轻松利用res和req,给对应的浏览器返回友好的错误信息。

###domain真的是独立的吗?


这里没打算故作玄虚,答案就是“独立的”。
看官方的说法:

The enter method is plumbing used by the run, bind, and intercept methods to set the active domain. It sets domain.active and process.domain to the domain, and implicitly pushes the domain onto the domain stack managed by the domain module (see domain.exit() for details on the domain stack).

有兴趣的同学可以深入看看domain的实现,node.js维护一个domain堆栈。
这里有一个小秘密,代码中执行process.domain将获取到当前上下文的domain对象,不串台。
我们做个简单试验:

const domain = require('domain');
const http = require('http'); http.createServer(function (req, res) {
  var d = domain.create();
  d.id = '123';
  d.res = res;
  d.req = req;   d.on('error', function (er) {
    console.error('Error', er);
    var curDomain = process.domain;
    console.log(curDomain.id, curDomain.res, curDomain.req);
    try {
      curDomain.res.writeHead(500);
      curDomain.res.end('Error occurred, sorry.');
    } catch (er) {
      console.error('Error sending 500', er, curDomain.req.url);
    }
  });   var reqId = parseInt(req.url.substr(1));   setTimeout(function () {
  d.run(handler);
  }, (5-reqId)*1000);   function handler() {
    if(reqId == 3){
      var a;
      a.b();
    }
    res.end('hello');
  }
}).listen(80);

我们用浏览器请求http://localhost/2 ,1-5

node.js分别会等待5-1秒才返回,其中3号请求将会返回错误。
error处理函数中,没有使用闭包,而是使用process.domain,因为我们就要验证一下这个玩意是否串台。

根据fiddler的抓包可以发现,虽然3号请求比后边的4、5号请求更晚返回,但process.domain对象还是妥妥的指向3号请求自己。

###domain的坑


domain管理的上下文,可以隐式绑定,也可以显式绑定。什么意思呢?

隐式绑定

If domains are in use, then all new EventEmitter objects (including Stream objects, requests, responses, etc.) will be implicitly bound to the active domain at the time of their creation.

在run的逻辑中创建的对象,都会归到domain上下文管理;

显式绑定

Sometimes, the domain in use is not the one that ought to be used for a specific event emitter. Or, the event emitter could have been created in the context of one domain, but ought to instead be bound to some other domain.

一些对象,有可能是在domain.run以外创建的,例如我们的httpServer/req/res,或者一些数据库连接池。
对付这些对象的问题,我们需要显式绑定到domain上。
也就是domain.add(req)

看看实际的例子:

var domain = require('domain');
var EventEmitter = require('events').EventEmitter; var e = new EventEmitter(); var timer = setTimeout(function () {
  e.emit('data');
}, 10); function next() {
  e.once('data', function () {
    throw new Error('something wrong here');
  });
} var d = domain.create();
d.on('error', function () {
  console.log('cache by domain');
}); d.run(next);

上述代码运行,可以发现错误并没有被domain捕获,原因很清晰,因为timer和e都在domain.run之外创建的,不受domain上下文管理。需要解决这个问题,只需要简单的add一下即可。

d.add(timer);
//or
d.add(e);

例子终归是例子,实际项目中必然情况要复杂多了,redis、mysql等等第三方组件都可能保持长连,那么这些组件往往不在domain中管理,或者出一些差错。

所以,保底起见,都要再加一句process.on(‘uncaughtException’)

不过,如果异常真到了这一步,我们也没什么可以做的了,只能写好log,然后重启子进程了(关于nodejs多进程,大家可以看看下一篇文章:http://www.cnblogs.com/kenkofox/p/5431643.html)。

###domain带来的额外好处:request生命周期的全局变量


做一个webservice,一个请求的处理过程,往往会经过好几个js,接入、路由、文件读取、数据库访问、数据拼装、页面模版。。。等等
同时,有一些数据,也是到处需要使用的,典型的就是req和res。
如果不断在函数调用之间传递这些公用的数据,想必一定很累很累,而且代码看起来也非常恶心。
那么,能否实现request生命周期内的全局变量,存储这些公用数据呢?

这个全局变量,必须有两个特点:

1. 全局可访问
2. 跟request周期绑定,不同的request不串台

聪明的孩子应该想到了,刚才domain的特性就很吻合。
于是,我们可以借助domain,实现request生命周期内的全局变量。

简单代码如下:

const domain = require('domain');const http = require('http');

Object.defineProperty(global, 'window', {
  get : function(){
    return process.domain && process.domain.window;
  }
}); http.createServer(function (req, res) {
  var d = domain.create();
  d.id = '123';
  d.res = res;
  d.req = req;   d.window = {name:'kenko'};   d.on('error', function (er) {
  console.error('Error', er);
  var curDomain = process.domain;
  try {
    curDomain.res.writeHead(500);
    curDomain.res.end('Error occurred, sorry.');
  } catch (er) {
    console.error('Error sending 500', er, curDomain.req.url);
  }
});
d.add(req);
d.add(res);
d.run(handler); function handler() {
res.end('hello, '+window.name);
}
}).listen(80);

这里关键点是process.domainObject.defineProperty(global, 'window')
从此就能在任一个逻辑js中使用window.xxx来访问全局变量了。
更进一步,需要大家监听一下res的finish事件,做一些清理工作。

好了,domain的异常处理就说到这~~~

node.js WebService异常处理(domain)以及利用domain实现request生命周期的全局变量的更多相关文章

  1. node中session存储与销毁,及session的生命周期

    1.首先在使用session之前需要先配置session的过期时间等,在入口文件app.js中 app.use(express.session({ cookie: { maxAge: config.g ...

  2. 在nginx服务器里面搭建好node.js本地服务器后,利用Node.js的FS模块,实现简单数据的写入和读取

    先在server.js里面引入: var fs = require('fs');   然后写入  // 往writeme.txt文件 写入一些内容     fs.writeFile('./writem ...

  3. [Node.js]27. Level 5: URL Building & Doing the Request

    Let's create a page which calls the twitter search API and displays the last few results for Code Sc ...

  4. 【干货分享】Node.js 中文学习资料和教程导航

    这篇文章来自 Github 上的一位开发者收集整理的 Node.js 中文学习资料和教程导航.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念,它的目标是帮助程 ...

  5. node.js中文资料导航 Mark

    Node.js HomePage Infoq深入浅出Node.js系列(进阶必读) Node.js中文文档 被误解的 Node.js Node.js C++ addon编写实战系列 热门node.js ...

  6. 【干货分享】Node.js 中文资料导航

    这篇文章与大家分享一批高质量的的 Node.js 中文资料.Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的, 易于扩展的网络应用 Node ...

  7. node.js中文资料导航

    以下资料来自gitHUb上面:https://github.com/youyudehexie/node123 Node.js HomePage Node官网七牛镜像 Infoq深入浅出Node.js系 ...

  8. Node.js之错误处理与断言处理

    Node.js之错误处理与断言处理 1. 使用 domain 模块处理错误 try..catch 多用于捕捉同步方法中的抛出错误,但不能用try..catch捕捉异步方法中抛出de错误 如: 1 va ...

  9. 转 node.js里面的http模块深入理解

    问题1:HTTP服务继承了TCP服务模型,是从connection为单位的服务到以request为单位的服务的封装,那么request事件何时触发? 注意:在开启keepalive后,一个TCP会话可 ...

随机推荐

  1. RHEL7网络管理之nmcli

    在RHEL7中默认使用NetworkManager 守护进程来监控和管理网络设置.nmcli是命令行的管理NetworkManager的工具,会自动把配置写到/etc/sysconfig/networ ...

  2. 比较两个Long对象值

    比较两个Long对象的值是否相等,不可以使用双等号进行比较,(long int Integer可以用双等号进行比较)可以采用如下方式: 1.使用equals方法进行比较 Long a=new Long ...

  3. Android Studio 初使用

    Android Studio 更改Eclipse快捷键 Android Studio 更改编码 Android Studio 导包

  4. 关于ajax提交表单参数序列化和时间戳转换

    ajax提交form表单, 序列化表单的参数 //var a = $("#addfm").serialize(); //将表单的内容序列化成为一个字符串 var a = $(&qu ...

  5. thymeleaf 中文乱码问题

    使用thymeleaf后,即使使用org.springframework.web.filter.CharacterEncodingFilter也不能解决中文乱码问题了, 后来发现在org.thymel ...

  6. Ubuntu遇到Please ensure that adb is correctly located at '...adb.exe' and can be executed 问题解决方法

    上次我们在SDK更新的到最新的Android L版本之后,我发现我的ADT和android指定的版本不对应,我的ADT是22版本的,android L需要23版本以上的,版本不对应的话就无法加载这个S ...

  7. 第四章 Leader选举算法分析

    Leader选举 学习leader选举算法,主要是从选举概述,算法分析与源码分析(后续章节写)三个方面进行. Leader选举概述 服务器启动时期的Leader选举 选举的隐式条件便是ZooKeepe ...

  8. php插入排序

    //php版插入排序 $arr=array('','5','3','7','6','4','8','2');     for($i=2;$i<count($arr);$i++)     {   ...

  9. 设置阿里云maven中央仓库的settings.xml

    本来想找一个可用的设置文件,结果乱七八糟的,干脆自己做了一个,同时还放上了Spring的SNAPSHOT和MILESTONE/RELEASE仓库,希望能帮到一些人. <?xml version= ...

  10. 『TCP/IP详解——卷一:协议』读书笔记——01

    从今日起开始认真研读TCP/IP详解这本经典制作,一是巩固我薄弱的计算机网络知识,二来提高我的假期的时间利用率.将心得与思考记录下来,防止白看-哦耶 2013-08-14 18:47:06 第一章 概 ...