成熟的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. Selenium2+python自动化16-alert\confirm\prompt

    前言 不是所有的弹出框都叫alert,在使用alert方法前,先要识别出到底是不是alert.先认清楚alert长什么样子,下次碰到了,就可以用对应方法解决. alert\confirm\prompt ...

  2. 企业项目如何打包成.ipa文件

    首先准备好企业的项目,真机和申请好的正式证书,关于企业证书的申请此处不再写,可以参考网上相关的教程,本人并未参与证书申请,所以此处不敢乱写. 1.找到正式证书与描述文件,双击打开(需要密码,这个要问申 ...

  3. NGUI Draw Calls优化(思路)

    用NGUI做界面的时候发现不注意GameObject(或者说Widget)的depth的话,单独运行界面时,Draw Calls挺高的: 网上搜了一下,大把的博客说的都是类似以下的原则: (PS:以下 ...

  4. 深入理解C语言的函数调用过程

    本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解.     先看一个最简单的程序: 点击(此处)折叠或打开 /*test.c*/ #include stdio. ...

  5. mongodb+pycharm使用报错,无法往mongodb存文件

    1 mongodb必须在启用状态下,启用如下 C:/mongodb/bin>mongod --dbpath C:\mongodb\data\db 不能关闭cmd 2 其他问题,貌似在Connec ...

  6. 检查python模块是否成功安装

    例如,检查HTMLTestRunner模块是否成功安装(血淋淋的例子) 一个模块未成功安装,在直接运行python程序是不会报错,但是会提示类似于以下的错误 AttributeError: 'xxxx ...

  7. Struts2中请求参数的接收方式和ModelDriven机制及其运用

    odelDriven 为什么需要ModelDriven 所谓ModelDriven,意思是直接把实体类当成页面数据的收集对象.比如,有实体类User如下: package cn.com.leadfar ...

  8. js之事件

    1.事件类型 鼠标事件 onclick事件 鼠标点击某个对象 ondblclick事件 鼠标双击某个对象 onmousedown事件 鼠标按下 onmouseup事件 鼠标松开 onmouseover ...

  9. I hate it

    Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少.这让很多学生很反感. 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老 ...

  10. 套题 codeforces 359

    A题:Free Ice Cream 注意要使用LL,避免爆int #include <bits/stdc++.h> #define scan(x,y) scanf("%d%d&q ...