node.js WebService异常处理(domain)以及利用domain实现request生命周期的全局变量
成熟的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,信心满满的说,不会出现未捕获的异常。
- 一般来说,这样都没太多问题,正常返回的时候一切都好。
- 但如果,哪天,某个代码出现了点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.domain和Object.defineProperty(global, 'window')
从此就能在任一个逻辑js中使用window.xxx来访问全局变量了。
更进一步,需要大家监听一下res的finish事件,做一些清理工作。
好了,domain的异常处理就说到这~~~
node.js WebService异常处理(domain)以及利用domain实现request生命周期的全局变量的更多相关文章
- node中session存储与销毁,及session的生命周期
1.首先在使用session之前需要先配置session的过期时间等,在入口文件app.js中 app.use(express.session({ cookie: { maxAge: config.g ...
- 在nginx服务器里面搭建好node.js本地服务器后,利用Node.js的FS模块,实现简单数据的写入和读取
先在server.js里面引入: var fs = require('fs'); 然后写入 // 往writeme.txt文件 写入一些内容 fs.writeFile('./writem ...
- [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 ...
- 【干货分享】Node.js 中文学习资料和教程导航
这篇文章来自 Github 上的一位开发者收集整理的 Node.js 中文学习资料和教程导航.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念,它的目标是帮助程 ...
- node.js中文资料导航 Mark
Node.js HomePage Infoq深入浅出Node.js系列(进阶必读) Node.js中文文档 被误解的 Node.js Node.js C++ addon编写实战系列 热门node.js ...
- 【干货分享】Node.js 中文资料导航
这篇文章与大家分享一批高质量的的 Node.js 中文资料.Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的, 易于扩展的网络应用 Node ...
- node.js中文资料导航
以下资料来自gitHUb上面:https://github.com/youyudehexie/node123 Node.js HomePage Node官网七牛镜像 Infoq深入浅出Node.js系 ...
- Node.js之错误处理与断言处理
Node.js之错误处理与断言处理 1. 使用 domain 模块处理错误 try..catch 多用于捕捉同步方法中的抛出错误,但不能用try..catch捕捉异步方法中抛出de错误 如: 1 va ...
- 转 node.js里面的http模块深入理解
问题1:HTTP服务继承了TCP服务模型,是从connection为单位的服务到以request为单位的服务的封装,那么request事件何时触发? 注意:在开启keepalive后,一个TCP会话可 ...
随机推荐
- UTF-8 <==> unicode(WCHAR)
static int fetchWordFromUTF8(const chConstStringA& strText, WCHAR& result) { int nLength = s ...
- 初步认识JUnit
初步认识JUnit 目前大多数的基于Java的企业应用软件,肯定少不了单元测试,程序员通过编写单元测试来验证自己程序的有效性:管理者通过持续自动的执行单元测试和分析单元测试覆盖率来确保软件本身的质量. ...
- 理解AOP
http://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html Aspect Oriented Programming 面向切面编程. ...
- iOS中POST异步请求
POST异步请求(代理) 1.遵循<NSURLConnectionDataDelegate> @interface ViewController ()<NSURLConnection ...
- MySQL数据库指定字符集
mysql 创建数据库时指定编码很重要,很多开发者都使用了默认编码,但是我使用的经验来看,制定数据库的编码可以很大程度上避免倒入导出带来的乱码问题. 我们遵循的标准是,数据库,表,字段和页面或文本的编 ...
- DNS主从TSIG加密传输
BIND服务程序为了能够安全的提供解析服务而支持了TSIG加密机制,TSIG主要是利用密码编码方式保护区域信息的传送(Zone Transfer),也就是说保证了DNS服务器之间传送区域信息的安全. ...
- IOS 进度条与手势
//进度条#import "ViewController.h" @interface ViewController () { UIImageView* _animaImageV; ...
- Backbone.js入门教程
原文: Getting Started with Backbone.js 不像其它的Web开发语言,过去Javascript很少可用的架构.令人感到高兴的是,最近几年这种情况得到非常大的改善. 今天我 ...
- C# Delegate 匿名 Delegate
C#6.0新添加了 lambda的强力支持,用lambda的确可以节省好多代码,让代码看起来更简洁,更直观: 这里做一个笔记,C#的匿名委托 Demo class Program { static v ...
- Python批量修改文件名
处理语料库时,有些文件名字很不规则,为了方便处理,同义按数字顺序修改名称,主要是用到os模块: import os def RenameFiles(srcdir): #将目录下所有的文件命名为数字开头 ...