在实现简单的静态服务中间件时,我在以下代码中偶然发现了这样一个事实:
fs = require('fs');
util = require('util');
stream = fs.createReadStream('/');
stream.on('open', function(fd) { console.log('opened! ' + util.inspect(fd)); });
stream.on('error', function(err) { console.log('error! ' + util.inspect(err)); });
当参数为directory时,会触发open
和error
事件!
$ node test.js opened! 11 error! { [Error: EISDIR, read] errno: 28, code: 'EISDIR' }
我假设我有open
或error
,从来没有同时。
区分好的open
和坏的open
而不需要显式地测试fd
和stat
作为目录的方法是什么?还有其他类似的案例吗?
当您在Node中创建可读流时,代码将尝试打开文件并读取内容(如果没有提供fd
)。你可以通过下面的代码看到:
调用createReadStream()
创建一个ReadStream
对象,如果fd
不是一个数字,它将调用this.open()
:
if (!util.isNumber(this.fd))
this.open();
这调用fs.open
,在一个目录上,我猜打开目录?(为什么允许这样工作而没有错误,这有点奇怪,但代码最终会到达本机绑定层并继续运行。)打开后,代码发出"open"事件并开始读取文件的内容(链接到源文件):
ReadStream.prototype.open = function() {
var self = this;
fs.open(this.path, this.flags, this.mode, function(er, fd) {
if (er) {
if (self.autoClose) {
self.destroy();
}
self.emit('error', er);
return;
}
self.fd = fd;
self.emit('open', fd);
// start the flow of data.
self.read();
});
};
在调用fs.read
期间导致错误,该错误被发送给ReadStream
的回调onRead
(链接到源代码):
function onread(er, bytesRead) {
if (er) {
if (self.autoClose) {
self.destroy();
}
self.emit('error', er);
因此,由于createReadStream
调用立即打开并开始读取文件的内容(如果没有传递文件描述符),因此将像您所看到的那样发出事件。首先是open
事件,然后尝试读取文件,但失败了,然后触发error
事件。
由于这是一个可读流,您可能需要考虑侦听data
事件而不是open
事件,以判断这是否是一个可接受的文件,因为您正在立即读取。类似这样:
fs = require('fs');
util = require('util');
stream = fs.createReadStream('/');
stream.on('data', function(chunk) { console.log('data! ' + chunk.toString()); });
stream.on('error', function(err) { console.log('error! ' + util.inspect(err)); });
在测试中,您将在一个文件上点击data
,在一个目录上点击error
。
我以以下方法结束:
-
fs.open(path, 'r', function(err, fd) { ... })
获取文件描述符fd
; -
fs.fstat(fd, function(err, stats) { ... })
到stat
; -
stats.isDirectory()
处理EISDIR
; -
stream = fs.createReadStream(null, { fd: fd, flags: 'r' })
最终获得可读文件流;
通过这种方式,我可以保护自己免受可能的文件与目录交换,反之亦然,在检查之后但在打开之前,因为即使有人做了欺骗,其文件描述符仍然有效。