Node Express 中的 res.sendfile 与传递数据

IT技术 javascript node.js express
2021-02-21 01:46:33

有什么方法可以从 Node.JS 应用程序重定向到 HTML 文件,例如:res.sendFileof express 并将 JSON 数据传递给 html 文件?

6个回答

我知道这已经晚了,但我想提供一个其他人没有提供的解决方案。此解决方案允许将文件流式传输到响应,同时仍然允许您修改内容而无需模板引擎或将整个文件缓冲到内存中。

如果你不在乎“为什么”,请跳到底部

让我首先描述为什么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);
});

解决方案

如果您想在对文件进行轻微修改的同时继续向用户流式传输文件,那么此解决方案适合您。请注意,这不是模板引擎的替代品,而是应该用于在流式传输文件时对文件进行小的更改。下面的代码将一个带有数据的小脚本标签附加到 HTML 页面的正文中。它还展示了如何在 http 响应流中添加或附加内容:

注意:如评论中所述,原始解决方案可能会出现失败的边缘情况。为了解决这个问题,我添加了新行包以确保数据块在新行发出。

const Transform = require('stream').Transform;
const parser = new Transform();
const newLineStream = require('new-line');

parser._transform = function(data, encoding, done) {
  const str = data.toString().replace('</body>', '<script>var data = {"foo": "bar"};</script></body>');
  this.push(str);
  done();
};

// app creation code removed for brevity

app.use('/index.html', (req, res) => {
    res.write('<!-- Begin stream -->\n');
    fs
    .createReadStream('../public/index.html')
    .pipe(newLineStream())
    .pipe(parser)
    .on('end', () => {
        res.write('\n<!-- End stream -->')
    }).pipe(res);
});
尝试使用此解决方案发送 json 文件。我进行了Error: write after end第二次刷新。
2021-04-30 01:46:33
“文件将被分别加载到内存中 10 次” - 如果您事先将这些文件加载​​到内存中,则不会,我正在使用 Promise.all() 在开始路由之前执行此操作。
2021-05-08 01:46:33
@ChristiaanWesterbeek - 这是一个合理的假设,尽管我发现这从未发生过。我不确定数据流的内部在节点中是如何工作的,但我想网络服务器永远不会发出这样的部分标签字符串,因为这可能会破坏浏览器中的增量渲染/用户体验。
2021-05-10 01:46:33
@Ajeet Lakhani:您必须将“解析器”放入 app.use 函数中,以便每次生成新的 Transform 实例(或者有人知道重置流方式?)
2021-05-10 01:46:33
但是如果数据是分块流式传输的。可能是一个块以 结尾,</bo下一个块以 开头dy>,对吗?在这种情况下,</body>永远不会执行的替换
2021-05-15 01:46:33

您从给定的请求中得到一个响应。您可以将多个事物组合成一个响应,也可以要求客户端发出单独的请求以获取单独的事物。

如果您想要做的是获取一个 HTML 文件并通过向其中插入一些 JSON 来修改它,那么您不能仅仅res.sendFile()因为它只是从磁盘或缓存读取文件并直接将其作为响应流式传输,提供没有机会修改它。

更常见的方法是使用模板系统,让您将内容插入 HTML 文件(通常用您自己的数据替换特殊标签)。有数以百计的模板系统和许多支持 node.js。node.js 的常见选择是 Jade (Pug)、Handlebars、Ember、Dust、EJS、Mustache。

或者,如果您真的想这样做,您可以将 HTML 文件读入内存,.replace()对它使用某种操作来插入您自己的数据,然后res.send()生成更改后的文件。

有用的答案。谢谢。
2021-05-10 01:46:33

嗯,它有点旧,但我没有看到任何足够的答案,除了“为什么不”。您确实可以在静态文件中传递参数。这很容易。考虑在您的原点上使用以下代码(使用 express):

    let data = fs.readFileSync('yourPage.html', 'utf8');
    if(data)
    res.send(data.replace('param1Place','uniqueData'));
    //else - 404

现在,例如,只需在yourPage.html 中设置一个 cookie,例如:

    <script>
    var date = new Date();
    document.cookie = "yourCookieName='param1Place';" + 
    date.setTime(date.getTime() + 3600) + ";path=/";
    </script>

您可以在 js 中的任何位置从 yourCookieName提取uniqueData 的内容

同步很糟糕。要么使用异步版本,要么更好;溪流。参见stackoverflow.com/a/38129612/5674976
2021-05-01 01:46:33

为什么不直接读取文件,应用转换,然后在回调中设置路由?

fs.readFile(appPath, (err, html) => {
   let htmlPlusData = html.toString().replace("DATA", JSON.stringify(data));

   app.get('/', (req, res) => {
       res.send(htmlPlusData);
   });
});

请注意,您不能动态更改data,您必须重新启动节点实例。

或者,在路由中进行这些替换。像 res.send(html.replace("DATA", JSON.stringify(data)));
2021-05-07 01:46:33

如果您确实想修改 HTML 文件中的某些内容,我认为Ryan Wheale发布的答案是最佳解决方案。您还可以使用cheerio处理复杂的逻辑。

但是对于这个特定的问题,我们只想将一些数据从服务器传递给客户端,实际上根本不需要读index.html入内存。

您可以简单地在 HTML 文件顶部的某处添加以下脚本标记: <script src="data.js"></script>

然后让 Express 为该文件提供所需的任何数据:

app.get("/data.js", function (req, res) {   
  res.send('window.SERVER_DATA={"some":"thing"}');
});

然后可以使用 window 对象轻松地在客户端应用程序中的任何位置引用此数据,如下所示: window.SERVER_DATA.some

React 前端的附加上下文

如果您的客户端和服务器在不同的端口上运行,例如在create-react-app的情况下,这种方法在开发过程中特别有用,因为代理服务器始终可以响应请求,data.js但是当您将某些内容插入index.html使用 Express 时在向其中index.html插入任何数据之前,您始终需要准备好生产版本