我正在使用Nodejs Express开发基于两个级别的自制RBAC系统:
,我创建了一个像这样的中间件:
exports.can = function (resource, action) {
return function (request, response, next) {
action = action || request.method;
if (!request.user) {
return next(new errors.UnauthorizedError());
}
request.user.can(request.user.role, request.user.currentPack, resource, action, function (can, error) {
if (error) return next(error);
if (!can) {
return next(new errors.UnauthorizedError());
}
return can;
});
return next();
};
};
我在我的用户模型中添加了这种方法:
const rbac = new RBAC(rbacJson);
const pack = new PACK(packJson);
schema.method('can', function (role, userPack, resource, action, next) {
let can = false;
action = action.toUpperCase();
can = rbac.can(role, resource, action);
if (can) {
can = pack.can(userPack, resource, action, function (can) {
return next(can);
});
}
return next(can);
});
在我的方法pack.can(...)中,我需要像这样执行一个mongoose查询:
PACK.prototype.can = function (pack, resource, action, next) {
let can = true;
// some sequantial code
Trader.count({/* some conditions */}, function (err, count) {
if(count == 0) return next(true);
return next(false);
});
return can;
};
我的问题是当Mongoose查询的返回是下一个(false)时,我有这个错误:
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
at ServerResponse.header (/home/invoice/node_modules/express/lib/response.js:730:10)
at ServerResponse.send (/home/invoice/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/home/invoice/node_modules/express/lib/response.js:256:15)
at ServerResponse.response.apiResponse (/home/invoice/server/config/middlewares/api.js:10:14)
at /home/invoice/server/controllers/api/invoice/traders.js:130:21
at /home/invoice/node_modules/mongoose/lib/model.js:3835:16
at /home/invoice/node_modules/mongoose/lib/services/model/applyHooks.js:162:20
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
经过调查,我发现错误可能是由于双回调呼叫:
can = pack.can(userPack, resource, action, function (can) {
return next(can);
});
return next(new errors.UnauthorizedError());
但我不知道如何解决这个问题。 我希望我能很好地解释我的问题。
答案 0 :(得分:2)
所以让我们从错误开始:
发送后无法设置标头。
十分之九,这是因为尝试向同一请求发送两个响应。虽然技术上是正确的,但对标题的引用有点误导。第二个响应将尝试做的第一件事是设置一些标题,这将失败,因为第一个响应已经将它们发送回客户端。
堆栈跟踪可以清楚地指示第二个响应的来源,包括文件名和行号。更难的是追踪第一个响应,通常我只是投入一些额外的控制台记录来解决它。
在你提到的问题中,你相信你已经找到了问题的根源,但在我看来它可能只是冰山一角。您反复使用相同的模式,即使您将其固定在一个可能不够的地方。
在开始讨论之前,让我们从这开始:
return next();
出于本示例的目的,您是否传递错误并不重要,例如: return next(err);
,重点是一致的。首先它调用next()
,返回undefined
。然后它从周围的函数返回undefined
。换句话说,它只是一个方便的简写:
next();
return;
我们返回的原因是为了确保在我们调用next()
之后没有其他事情发生,我们总是尽力确保调用next()
是我们在处理程序中执行的最后一件事,尤其是因为否则我们可能会遇到错误,我们会尝试两次发送响应。
您使用过的(反)模式看起来有点像这样:
obj.doSomething(function() {
return next(); // next 1
});
return next(); // next 2
同样,无论您是在调用next()
还是next(err)
,这都无关紧要,但这一切都非常重要。需要注意的关键是,下一个1的return
只是从传递给doSomething
的函数返回。它没有做任何事情来阻止下一个被击中。下一个和下两个都将被调用。
在某些地方,您的代码似乎不清楚它是否尝试同步或异步,同时使用回调和返回值。它确实有点难以确定'正确'代码应该是这样的。具体来说,can
的值是应该同步返回还是异步传递给回调?我怀疑它是后者,但目前的代码似乎在两者之间徘徊。确保在您为下一件事情做好准备之前不要致电next()
,这一点非常重要,因此,如果您在等待数据库查询,则必须等待#&} 39;打电话给next
直到它回来。
就个人而言,我重新命名你的回调,因此它们都不会被称为next
,我发现这真的很令人困惑。当我看到next
时,我希望它是Express next
函数,而不仅仅是一个任意回调。
这有点猜测,但我建议你的中间件应该是这样的:
exports.can = function (resource, action) {
return function (request, response, next) {
action = action || request.method;
if (!request.user) {
return next(new errors.UnauthorizedError());
}
request.user.can(request.user.role, request.user.currentPack, resource, action, function (can, error) {
if (error) {
next(error);
}
else if (can) {
next();
}
else {
next(new errors.UnauthorizedError());
}
});
// Do not call next() here
};
};
用户模型的相关部分将是:
if (can) {
pack.can(userPack, resource, action, function (can) {
next(can);
});
}
else {
next(can);
}