我知道这已经晚了,但我想提供一种其他人没有提供的解决方案。
此解决方案允许将文件流式传输到响应,同时仍然允许您修改内容,而无需模板引擎或将整个文件缓冲到内存中。
如果不关心请跳到底部"why"
我先描述一下原因res.sendFile
对于那些不知道的人来说是如此令人向往。
由于 Node 是单线程的,它的工作原理是连续执行大量非常小的任务 - 这包括从文件系统读取数据和回复 http 请求。
Node 在任何时候都不会停止正在执行的操作并从文件系统中读取整个文件。它会读一点,做别的事情,再读一点,做别的事情。
回复 http 请求和 Node 中的大多数其他操作也是如此(除非您明确使用sync
操作的版本 - 例如 readFileSync - 如果你能帮助它,就不要这样做,说真的,不要这样做 - 这是自私的)。
考虑这样一个场景:10 个用户请求同一文件。效率低下的做法是将整个文件加载到内存中,然后使用发送文件res.send()
.
即使是同一个文件,该文件在发送到浏览器之前也会被加载到内存中 10 次。然后,垃圾收集器需要在每次请求后清理这些混乱的情况。
代码会天真地写成这样:
app.use('/index.html', (req, res) => {
fs.readFile('../public/index.html', (err, data) => {
res.send(data.toString());
});
});
这看起来是对的,而且确实有效,但效率非常低。
由于我们知道 Node 以小块的形式执行操作,因此最好的办法是在从文件系统读取小数据块时将其发送到浏览器。
这些块永远不会存储在内存中,您的服务器现在可以处理更多数量级的流量。
这个概念称为流式传输,它就是res.sendFile
确实如此 - 它将文件从文件系统直接流式传输给用户,并为更重要的事情保留空闲内存。
如果您手动执行此操作,则结果如下:
app.use('/index.html', (req, res) => {
fs.createReadStream('../public/index.html')
.pipe(res);
});
Solution
如果您想继续将文件流式传输给用户,同时对其进行轻微修改,那么此解决方案适合您。
请注意,这并不是模板引擎的替代品,而是应该用于在传输文件时对文件进行小的更改。
下面的代码将一个带有数据的小脚本标记附加到 HTML 页面的正文中。它还展示了如何将内容添加到 http 响应流:
注意:正如评论中所提到的,原始解决方案可能存在会失败的边缘情况。为了解决这个问题,我添加了new-line https://www.npmjs.com/package/new-line包以确保数据块在新行发出。
const Transform = require('stream').Transform;
const parser = new Transform();
const newLineStream = require('new-line');
parser._transform = function(data, encoding, done) {
let value = data.toString();
value = value.replace('<html>', '<!-- Begin stream -->\n<html>');
value = value.replace('</body>', '<script>var data = {"foo": "bar"};</script>\n</body>\n<!-- End stream -->');
this.push(value);
done();
};
// app creation code removed for brevity
app.use('/index.html', (req, res) => {
fs
.createReadStream('../public/index.html')
.pipe(newLineStream())
.pipe(parser)
.pipe(res);
});