Nodejs 这是什么bug

Nodejs 这是什么bug

我想通过domain把截获的错误返回给客户端(下面的实现是模拟一个异步异常),第一次请求没有问题,但是第二次以后浏览器都会一直处于pending状态,我的理解是第二次请求使用了第一次请求domain里面的对像,也就是说B域访问了A域下面的变量,这样跨域访问数据导致第二次以后的请求拿到的response对像都是A域这个上下文中的,而第一次请求后response已经无效了,也就是说A域这个response没用了,每次都调用response.writeHead都是无效的。当一个进程中有公共变量的时候就会出现这样的问题,求解大神解决这个问题!!

var http = require('http'),
	events = require('events');
	domain = require('domain'),
	net = require('net'),
	util = require('util'),
	shareObject = null;

function InheritEventObject()
{
	events.EventEmitter.apply(this);
}
util.inherits(InheritEventObject, events.EventEmitter);

var cnt = 0;
http.createServer(function(request, response) {
	var rdomain = domain.create();
	rdomain.data = ++cnt;
	rdomain.on('error', function(err) {
		console.log(this.data);//每次都是1
		response.writeHead(500);
		response.end(err.stack);
	});
	rdomain.run(function(){
		var obj = getShareObject();
		obj.emit("throwError");
	});
}).listen(8080);

function getShareObject()
{
	if(shareObject === null)
	{
		shareObject = new InheritEventObject();
		shareObject.on("throwError", function(){
			throw new Error("hello error");
		});
	}
	return shareObject;
}

6 回复

根据你的描述,你希望捕获并处理异步错误,并将这些错误返回给客户端。但你遇到了一个在多次请求中响应对象失效的问题。这主要是因为你共享了一个全局的对象 shareObject,导致不同请求之间的数据相互影响。

分析问题

在你的代码中,shareObject 是一个全局对象,每个请求都会共享这个对象。在 getShareObject 函数中,如果 shareObject 尚未初始化,则会创建一个新的实例,并绑定一个事件处理器来抛出错误。由于所有请求都共享同一个 shareObject 实例,这意味着所有请求都会触发相同的错误处理逻辑。

解决方案

为了解决这个问题,你需要确保每个请求都有自己独立的对象实例。你可以通过在请求处理函数内部创建新的 InheritEventObject 实例来实现这一点。

修改后的代码

var http = require('http'),
    events = require('events'),
    domain = require('domain');

function InheritEventObject() {
    events.EventEmitter.apply(this);
}
util.inherits(InheritEventObject, events.EventEmitter);

http.createServer(function(request, response) {
    var rdomain = domain.create();

    rdomain.on('error', function(err) {
        console.error(err.stack);
        response.writeHead(500);
        response.end(err.stack);
    });

    rdomain.run(function() {
        var obj = new InheritEventObject(); // 每个请求创建一个新的对象实例
        obj.on("throwError", function() {
            throw new Error("hello error");
        });
        
        obj.emit("throwError");
    });
}).listen(8080, function() {
    console.log("Server running at http://127.0.0.1:8080/");
});

解释

  • 每请求独立的对象:通过在请求处理函数内部创建 InheritEventObject 的新实例,我们确保每个请求都有自己的独立对象,不会互相干扰。
  • 错误处理:在 rdomain 的错误处理函数中,我们捕获并记录错误,然后向客户端返回错误信息。
  • 避免全局共享:删除了全局的 shareObject 变量,防止多个请求间的数据污染。

这样修改后,每个请求将拥有独立的事件对象实例,从而避免了跨请求的数据冲突问题。


改为setHeader吧,write只能写一次

在rdomain的error里面,每次拿到的response都是第一次的。setHeader也没用。

function InheritEventObject()
{
    // 删掉下面这一行就正常了,暂时没看是神马原因
    // 但有一点可以肯定的是,你这种写法跟后面的util.inherits()是重复的
    // events.EventEmitter.apply(this);
}
util.inherits(InheritEventObject, events.EventEmitter);

大概的原因是: 本来你想实现继承EventEmitter,直接执行util.inherits(InheritEventObject, events.EventEmitter);即可,但是你又在InheritEventObject()中执行了events.EventEmitter.apply(this);,导致把当前的Domain对象绑定到了新创建的InheritEventObject对象上(其实你这种写法本来就是不对的)。以后在每次访问请求时,用的是同一个InheritEventObject对象,那么其出错的时候,也是触发同一个Domain对象的error事件,而里面的Response对象早已经关闭了,所以只要第一次请求能返回,后续的请求都没法返回出错信息。

而若在rdomain.on('error', function () {})处理后,执行rdomain.dispose()来销毁当前的Domain对象,则原来的InheritEventObject对象所绑定的Domain也失效,程序能正确找到当前真正的Domain,所以能正常运行:

 rdomain.on('error', function(err) {
        console.log(this.data);//每次都是1
        response.writeHead(500);
        response.end(err.stack);
        rdomain.dispose();
    });

EventEmitter的源码:

function EventEmitter() {
  this.domain = null;
  if (exports.usingDomains) {
    // if there is an active domain, then attach to it.
    domain = domain || require('domain');
    if (domain.active && !(this instanceof domain.Domain)) {
      this.domain = domain.active;
    }
  }
  this._events = this._events || null;
  this._maxListeners = this._maxListeners || defaultMaxListeners;
}

从你提供的代码来看,问题出在shareObject对象的共享上。你在getShareObject函数中创建了一个全局的事件对象,并且这个对象在整个应用程序运行期间只有一个实例。这意味着所有的请求都会共享同一个事件对象,从而导致域(domain)机制失效。

具体来说,你希望每个请求都有自己独立的域来处理错误,但因为所有的请求都使用了同一个shareObject,所以当其中一个请求抛出错误时,其他请求也会受到该错误的影响。

解决方案

为了让每个请求都有自己的域,应该避免使用全局共享的对象。你可以将shareObject的创建逻辑放在域内部,确保每个请求都有独立的对象。

下面是修改后的代码:

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

http.createServer(function(request, response) {
    var rdomain = domain.create();
    rdomain.on('error', function(err) {
        console.error(err);
        response.writeHead(500);
        response.end(err.stack);
    });

    rdomain.run(function() {
        // 在这里创建独立的事件对象
        var shareObject = new events.EventEmitter();
        shareObject.on("throwError", function() {
            throw new Error("hello error");
        });
        shareObject.emit("throwError");
    });
}).listen(8080, function() {
    console.log('Server is running on port 8080');
});

解释

  • 每个请求都有自己的域(rdomain)。
  • 在域内部创建一个新的事件对象(shareObject),确保每个请求使用独立的事件对象。
  • 使用domain.create()创建一个新域,并将其绑定到当前请求。
  • 如果发生错误,错误会在域内部被捕获并处理,不会影响其他请求。

这样可以确保每个请求都能正确地捕获和处理错误,而不会相互干扰。

回到顶部