NodeJS 分块数据传输无法正常工作

r1zhe5dt  于 2023-04-11  发布在  Node.js
关注(0)|答案(2)|浏览(190)

我正在使用content-type:chunked以块的形式传输数据,以\r\n分隔,但客户端接收的数据不是以块的形式接收的,而是一起接收的。如何解决这个问题?
我目前正在使用content-type:chunked以块的形式传输数据,以\r\n分隔。但是,当客户端接收数据时,它似乎是一次接收所有数据,而不是以预期的块的形式接收。我不确定为什么会发生这种情况,并希望获得任何指导。
以下是有关我的设置的一些其他详细信息:
服务器正在正确地发送数据,并带有正确格式化的块头。我已经检查了客户端的实现,以确保它与分块数据传输兼容。我的网络连接没有明显的问题,但可能有防火墙或代理可能会干扰分块传输编码。
任何关于可能导致此问题或如何解决此问题的想法都将不胜感激。提前感谢!
客户端读取分块数据的代码是

fetch('https://xxxxxxxxxxxxx')
    .then((response) => {
      const stream = response.body.getReader();

      return new ReadableStream({
        async pull(controller) {
          try {
            const { done, value } = await stream.read();
            if (done) {
              controller.close();
            } else {
              controller.enqueue(value);
            }
          } catch (error) {
            controller.error(error);
          }
        },
      });
    })
    .then(async (stream) => {
      const reader = stream.getReader();
      while (true) {
        const { done, value } = await reader.read();
        if (done) {
          break;
        }
      }
    })
    .catch((error) => console.error(error));

服务器返回的数据示例为

{"x":1}\r\n{"x":1}\r\n{"x":1}\r\n{"x":1}\r\n
6jjcrrmo

6jjcrrmo1#

仅仅通过将\r\n插入到响应有效负载中并不能产生块。要使服务器使用分块编码,请使用res.write。但是如果块来得太快,消费客户端可能仍然会将其中的几个块组合成一个stream.read()响应。
下面的服务器端代码使您的客户端接收三个块,每个块9个字节。

http.createServer(function(req, res) {
  res.write(`{"x":1}\r\n`);
  setTimeout(function() {
    res.write(`{"x":1}\r\n`);
  }, 100);
  setTimeout(function() {
    res.end(`{"x":1}\r\n`);
  }, 200);
});

但是将超时时间减少到0会在客户端上产生一个27字节的块,即使服务器仍然使用分块编码。
总而言之,您的客户端不应该依赖于块边界,也不应该假设每个块本身都是格式良好的JSON。
也许你真正想要的是用换行符(\r\n)分隔响应。下面的客户端代码用两个for循环来实现这一点:外部缓冲区将块添加到缓冲区的末尾,内部缓冲区从缓冲区的开头开始使用行。因此,块的分离与行的分离无关。

fetch(...).then(async function(response) {
  var stream = response.body.pipeThrough(new TextDecoderStream()).getReader();
  var buffer = "";
  for (; ;) {
    var { value, done } = await stream.read();
    if (done) break;
    buffer += value;
    for (; ;) {
      var offset = buffer.indexOf("\r\n");
      if (offset >= 0) {
        console.log(buffer.substring(0, offset));
        buffer = buffer.slice(offset + 2);
      } else
        break;
    }
  }
});
t1qtbnec

t1qtbnec2#

Content-Type: chunked不是一个东西,你可能指的是Transfer-Encoding: chunked,但这需要在前面加上一行十六进制的块长度。
这种编码是为了在发送大量数据而事先不知道大小并让客户端以块的形式拥有数据时使用的。
你可以让它工作,但有一个更好的方法来做你想做的事情,你可以使用Server-sent events,它在浏览器中有原生支持。
服务器:

app.get("/sse", async (req, res) => {
    res.writeHead(200, {
        "Content-Type": "text/event-stream",
        "Connection": "keep-alive",
        "Cache-Control": "no-cache"
    });
    for (let i=0; i<10; i++) {
        let data = {
            i,
            time: (new Date()).toString(),
        };
        res.write("data: "+JSON.stringify(data)+"\n\n");
        await sleep(1000);
    }
    res.end();
});

客户:

const events = new EventSource("/sse");
events.onmessage = event => {
    const data = JSON.parse(event.data);
    console.log(data);
};

相关问题