javascript 节点Express中的res.sendfile,并沿着传递数据

pftdvrlh  于 11个月前  发布在  Java
关注(0)|答案(8)|浏览(151)

有没有什么方法可以从Node.JS应用程序重定向到一个HTML文件,比如:res.sendFile的express,并将JSON数据沿着到HTML文件?

jmp7cifd

jmp7cifd1#

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

如果你不关心 “为什么”,请跳到底部

让我先描述一下为什么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响应流中添加或附加内容:
注意事项:正如在注解中提到的,原始的解决方案可能有一个边缘情况,这将失败。为了解决这个问题,我添加了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);
});

wvmv3b1j

wvmv3b1j2#

你可以从一个给定的请求中得到一个响应。你可以将合并中的多个东西组合成一个响应,也可以要求客户端发出单独的请求来得到单独的东西。
如果你想做的是获取一个HTML文件并通过插入一些JSON来修改它,那么你不能只使用res.sendFile(),因为它只是从磁盘或缓存中读取一个文件,并直接将其作为响应流,没有机会修改它。
更常见的方法是使用模板系统,让您将内容插入到HTML文件中(通常用您自己的数据替换特殊标记)。实际上有数百个模板系统,其中许多都支持node.js。node.js的常见选择是Jade(Pug),Handlebars,Ember,Dust,EJS,Mustache。
或者,如果你真的想这样做,你可以把HTML文件读入内存,对它使用某种.replace()操作来插入你自己的数据,然后res.send()结果改变文件。

enyaitl3

enyaitl33#

好吧,它有点旧,但我没有看到任何足够的答案,除了“为什么不”。你确实有办法在静态文件中传递参数。这很容易。考虑在你的源代码上执行以下代码(使用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中的任何位置从您的CookieName中直接提取 uniqueData 的内容

baubqpgj

baubqpgj4#

我认为Ryan Wheale的答案是最好的解决方案,如果你真的想修改HTML文件中的一些东西。你也可以使用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.SERVER_DATA.some

React前端的附加上下文

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

rks48beu

rks48beu5#

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

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

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

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

odopli94

odopli946#

你只能从服务器返回一个响应。最常见的做法是在服务器上用nunjucks或jade创建你的文件模板。另一种选择是在客户端呈现文件,然后使用JavaScript向服务器发出一个匿名调用来获取额外的数据。我想你也可以在cookie中设置一些数据,然后通过javascript在客户端读取。

bogh5gae

bogh5gae7#

(除非你想对html文件进行模板化,以便将json数据插入到脚本标记中)。你需要在express中公开一个API端点,将数据沿着发送到页面,并在页面上有一个函数来访问它。例如,

// send the html
app.get('/', (req, res) => res.sendFile('index'));

// send json data
app.get('/data', (req, res) => res.json(data));

字符串
现在,在客户端,您可以创建一个访问此端点的请求

function get() {
  return new Promise((resolve, reject) => {
    var req = new XMLHttpRequest();
    req.open('GET', '/data');
    req.onload = () => resolve(req.response);
  });
}           
 // then to get the data, call the function

get().then((data) => {
  var parsed = JSON.parse(data);
  // do something with the data
});


编辑:
所以箭头函数可能还不能在客户端工作。确保在真实的代码中用function(){}替换它们

nbysray5

nbysray58#

这是很容易做到的使用Cookie.简单地做:
在服务器端-

response.append('Set-Cookie', 'LandingPage=' + landingPageCode);
response.sendFile(__dirname + '/mobileapps.html');

字符串
在客户端-

<!DOCTYPE html>
<html>
    <body onload="showDeferredLandingPageCode()">
        <h2>Universal Link Mobile Apps Page</h2>
        <p>This html page is used to demostrate deferred deeplinking with iOS</p>
    </body>

    <script language="javascript">
        function showDeferredLandingPageCode() {
            alert(document.cookie);
        }
    </script>
</html>

相关问题