如何在反向代理响应中打开正文标签后插入一个片段

How do I inject a snippet after the opening body tag in a reverse-proxied response

我从上游得到一个 HTML 页面,格式如下:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    ...
  </head>
  <body class="foo bar baz" data-foo="klaskassa" data-baz="lkaslkas" id="body">
    ...
  </body>
</html>

我有一个 HTML 形式的片段:

<div class="my-snippet">
  ...
</div>

我想在开始 body 标签后插入代码段,给我:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    ...
  </head>
  <body class="foo bar baz" data-foo="klaskassa" data-baz="lkaslkas" id="body">
    <div class="my-snippet">
      ...
    </div>
    ...
  </body>
</html>

限制

解决方案必须修改流,而不是在 运行 转换之前将正文收集到单个字符串中。此应用受内存限制,处理的请求太多,无法承受这种性能损失。

我尝试过的事情

  1. Harmon:显然你不能读写元素的内部。参见 this, this, and this
  2. 使用 replacestream 概述 here 但这没有用,事实上我的反应刚刚停止。
  3. Transformer-proxy: 但是data对象只能附加到.
  4. 在 Ruby 中花了 4 个小时写了一个 Rack 应用程序,但后来我醒悟过来并停止重写我的整个代码库。

请:

在答案中添加示例代码。由于这基本上是一个 connect 应用程序,我可以插入你给我的任何中间件。

所以我创建了一个简单的服务器来启动代理服务器和普通服务器

var http = require('http'),
    httpProxy = require('http-proxy');


proxy = httpProxy.createProxyServer({
    target:'http://localhost:9000',
}).listen(8000); 

//
// Create your target server
//
http.createServer(function (req, res) {
    let data = 'request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2);
    res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': data.length });
    res.write(data);
    res.end();
}).listen(9000);

然后使用下面的方法进行相同的测试

$ curl "localhost:8000"
request successfully proxied!
{
  "accept": "*/*",
  "user-agent": "curl/7.54.0",
  "host": "localhost:8000",
  "connection": "close"
}

然后在下面找到的文档中

selfHandleResponse true/false, if set to true, none of the webOutgoing passes are called and it's your responsibility to appropriately return the response by listening and acting on the proxyRes event

所以像下面这样更新代码

var http = require('http'),
    httpProxy = require('http-proxy');


proxy = httpProxy.createProxyServer({
    target:'http://localhost:9000',
    selfHandleResponse: true
}).listen(8000); // See (†)

proxy.on('proxyRes', function(proxyRes, req, res) {
    if (proxyRes.headers["content-type"] && proxyRes.headers["content-type"].indexOf("text/plain") >=0) {
        // We need to do our modification
        if (proxyRes.headers["content-length"]) {
            //need to remove this header as we may modify the response
            delete proxyRes.headers["content-length"];
        }
        var responseModified = false;
        proxyRes.on('data', (data) => {
            let dataStr = "";
            if (!responseModified && (dataStr = data.toString()) && dataStr.indexOf("proxied!") >= 0) {
                responseModified = true;
                dataStr = dataStr.replace("proxied!", "proxied? Are you sure?")
                res.write(Buffer.from(dataStr, "utf8"));
                console.log("Writing modified data");
            } else {
                res.write(data);
                console.log("Writing unmodified data");
            }
        });
        proxyRes.on('end', (data) => {
            console.log("data ended")
            res.end();
        });
    } else {
        proxyRes.pipe(res)
    }
});



//
// Create your target server
//
http.createServer(function (req, res) {
    let data = 'request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2);
    res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': data.length });
    res.write(data);
    res.end();
}).listen(9000);

再次测试

$ curl "localhost:8000"
request successfully proxied? Are you sure?
{
  "accept": "*/*",
  "user-agent": "curl/7.54.0",
  "host": "localhost:8000",
  "connection": "close"
}

现在服务器控制台上的输出如下

Writing modified data
data ended

这并不能确认我们是否真的只修改了部分流。所以我改变了下面的代码

http.createServer(function (req, res) {
    let data = 'request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2);
    res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': data.length * 5});
    res.write(data + data)

    setTimeout(() => {
        res.write(data + data + data);
        res.end();
    });

}).listen(9000);

并在浏览器中打开

如您所见,数据在流中被替换,并且根据逻辑,替换只发生一次,流的其余部分按原样传递