Node.js和Request - 限制下载文件的大小

16

我想使用Request库下载一个文件。这很简单:

request({
    url: url-to-file
}).pipe(fs.createWriteStream(file));

由于URL是由用户提供的(在我的情况下),我希望限制应用程序下载的最大文件大小-比方说是10MB。我可以像这样依赖于content-length头部:

request({
    url: url-to-file
}, function (err, res, body) {
    var size = parseInt(res.headers['content-length'], 10);

    if (size > 10485760) {
        // ooops - file size too large
    }
}).pipe(fs.createWriteStream(file));

问题是-这有多可靠?我猜这个回调函数会在文件下载后被调用,对吗?但如果有人提供一个1GB的文件的URL,那么现在为时已晚了。我的应用程序将首先下载此文件的1 GB以检查(在回调中)其是否过大。

我还在考虑Node的http.get()方法。在这种情况下,我会这样做:

var opts = {
    host: host,
    port: port,
    path: path
};

var file = fs.createWriteStream(fileName),
    fileLength = 0;

http.get(opts, function (res) {
    res.on('data', function (chunk) {
        fileLength += chunk.length;

        if (fileLength > 10485760) { // ooops - file size too large
            file.end();
            return res.end();
        }

        file.write(chunk);
    }).on('end', function () {
        file.end();
    });
});

你会推荐哪种方法来限制文件的最大下载大小,而不需要在下载完整个文件并检查它的大小后再进行处理?

你会如何限制下载最大文件大小而不实际下载整个文件并检查其大小?


内容长度是可选的,而且经常不可用,因此永远不要依赖它。 - Alex K.
@RahilWazir 我怎么可能使用 fs 检查文件是否尚未在磁盘上?我想从互联网下载它并确保它不超过10MB。 - Pono
第二种方法看起来不错;既然你已经基本上回答了问题,把它放在代码审查上更有意义,我个人认为。 - jgillich
1
我不是很确定,但是难道没有一种方法可以监听写入流的“data”或“pipe”事件,然后访问已经写入的文件大小吗?如果是这样,你可以在那里进行干预,并在写入超过1mb时立即取消请求。 - David Losert
2个回答

15

我实际上会使用你讨论过的两种方法:检查content-length头和观察数据流以确保它不超出限制。

为此,我会首先对URL进行HEAD请求,以查看是否有content-length头。如果它大于您的限制,则可以在那里停止。如果不存在或小于您的限制,则进行实际的GET请求。由于HEAD请求仅返回标头而没有实际内容,因此这将有助于快速筛选具有有效content-length的大文件。

接下来,进行实际的GET请求并监视传入数据的大小以确保其不超过您的限制(可以使用请求模块完成;请参见下文)。无论HEAD请求是否发现了content-length头,都应进行此操作作为安全检查(服务器可能会欺骗content-length)。

类似以下示例:

var maxSize = 10485760;

request({
    url: url,
    method: "HEAD"
}, function(err, headRes) {
    var size = headRes.headers['content-length'];
    if (size > maxSize) {
        console.log('Resource size exceeds limit (' + size + ')');
    } else {
        var file = fs.createWriteStream(filename),
            size = 0;

        var res = request({ url: url });

        res.on('data', function(data) {
            size += data.length;

            if (size > maxSize) {
                console.log('Resource stream exceeded limit (' + size + ')');

                res.abort(); // Abort the response (close and cleanup the stream)
                fs.unlink(filename); // Delete the file we were downloading the data to
            }
        }).pipe(file);
    }
});

使用request模块观察传入数据大小的技巧是,在将数据导入文件流之前(就像您考虑使用http模块时一样),绑定到响应上的data事件。如果数据大小超过了您的最大文件大小,则调用响应的abort()方法。


1
出于安全考虑,应该加上res.unpipe(file)吗?或者中止取消管道操作会自动执行吗? - user1046334
1
@herby abort()会关闭套接字并忽略任何剩余的传入数据。只需调用abort()并完成即可,因为关闭流应该足够了。话虽如此,我在abort()代码中四处查找,但没有看到任何明确取消管道流的内容,因此在那里调用res.unpipe(file)也不会有害。 - Mike S
我看到的一个问题是,data 处理程序将开始读取流,忽略背压,而只有管道才会应用背压。 - felixfbecker

2

我曾遇到类似问题。现在我使用fetch来限制下载大小。

const response = await fetch(url, {
    method: 'GET',t
    size: 5000000, // maximum response body size in bytes, 5000000 = 5MB 
}).catch(e => { throw e })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接