NodeJS - 如何在不缓冲的情况下流式传输请求正文

Posted

技术标签:

【中文标题】NodeJS - 如何在不缓冲的情况下流式传输请求正文【英文标题】:NodeJS - How to stream request body without buffering 【发布时间】:2013-07-31 09:18:15 【问题描述】:

在下面的代码中,我无法弄清楚为什么 req.pipe(res) 不起作用,但也不会引发错误。预感告诉我这是由于 nodejs 的异步行为,但这是一个非常简单的情况,没有回调。

我错过了什么?

http.createServer(function (req, res) 

  res.writeHead(200,  'Content-Type': 'text/plain' );

  res.write('Echo service: \nUrl:  ' + req.url);
  res.write('\nHeaders:\n' + JSON.stringify(req.headers, true, 2));

  res.write('\nBody:\n'); 

  req.pipe(res); // does not work

  res.end();

).listen(8000);

这是卷曲:

➜  ldap-auth-gateway git:(master) ✗ curl -v -X POST --data "test.payload" --header "Cookie:  token=12345678" --header "Content-Type:text/plain" localhost:9002 

这是调试输出(请参阅上传的正文):

  About to connect() to localhost port 9002 (#0)
  Trying 127.0.0.1...
    connected
    Connected to localhost (127.0.0.1) port 9002 (#0)
  POST / HTTP/1.1
  User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8x zlib/1.2.5
  Host: localhost:9002
  Accept: */*
  Cookie:  token=12345678
  Content-Type:text/plain
  Content-Length: 243360
  Expect: 100-continue

  HTTP/1.1 100 Continue
  HTTP/1.1 200 OK
  Content-Type: text/plain
  Date: Sun, 04 Aug 2013 17:12:39 GMT
  Connection: keep-alive
  Transfer-Encoding: chunked

并且服务响应而不回显请求正文:

Echo service: 
Url:  /
Headers:

  "user-agent": "curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8x zlib/1.2.5",
  "host": "localhost:9002",
  "accept": "*/*",
  "cookie": "token=12345678",
  "content-type": "text/plain",
  "content-length": "243360",
  "expect": "100-continue"

... 最后的 curl 调试是

Body:
 Connection #0 to host localhost left intact
 Closing connection #0

此外,当我对大型请求正文进行压力测试时,我收到 EPIPE 错误。我怎样才能避免这种情况?

-- 编辑:通过反复试验,我确实让它工作了,它仍然指向一个时间问题。虽然这仍然很奇怪,因为超时会导致返回有效负载,但不介意超时持续时间。换句话说,无论我将超时设置为 5 秒还是 500 秒,有效负载都会正确地通过管道返回到请求并终止连接。

这里是编辑:

http.createServer(function (req, res) 

    try 
      res.writeHead(200,  'Content-Type': 'text/plain' );
      res.write('Echo service: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
      res.write('\nBody:"\n');
      req.pipe(res);
     catch(ex) 
      console.log(ex);
      // how to change response code to error here?  since headers have already been written?
     finally 
      setTimeout((function() 
        res.end();
      ), 500000);
    

).listen(TARGET_SERVER.port);

?

【问题讨论】:

请注意,您将看到向 9002 发出请求。这是一个反向代理(简单的 node-http-proxy 到目标 8000)。直接击中目标会产生相同的结果。 【参考方案1】:

管道请求到资源。 Req 是可读流,响应是可写流。它应该可以工作

   http.createServer(function (req, res) 

       res.writeHead(200,  'Content-Type': 'text/plain' );    
       res.write('Echo service: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));

       // pipe request body directly into the response body
       req.pipe(res);       

   ).listen(9002);

【讨论】:

这会起作用... 有时 ...管道调用有些异步。机器越快,这根本不起作用的情况就越多。我可以通过在调用 res.end() 之前等待来使其工作。赏金是谁可以解释为什么我能够通过添加睡眠来解决这个问题。 (请参阅我在问题底部的编辑) PIPE 调用负责在 req 流调用 close / end 时调用 res.end。管道后无需再次调用res.end。您是否在没有使用res.end()的情况下尝试了上面的代码? 添加睡眠后代码工作的原因(在 nodejs 中不是正确的术语:-))是管道实际上有时间在请求流结束后关闭响应流。res.end () in settimeout 只是关闭关闭的流。 接受您的回答,但包括 Wyatt 的部分回答,因为他更详细地说明了原因:由于节点中的 IO 是异步的,因此当您发出 .pipe 命令时,控制权立即返回到当前上下文,而管道在后台工作。当你下次调用 res.end() 时,你关闭了流,阻止了更多的数据被写入。这里的解决方案是让 .pipe 结束流本身,这是默认的。 为什么req.pipe(res) 有效?它怎么知道只发送req.body 而不是所有的标头等?【参考方案2】:

首先,您的 curl 似乎已关闭,已发布数据的文件名应以 @as shown here 开头。否则你只会发布文件名。

除此之外,Chandu 说对res.end() 的调用是这里的问题是正确的。

由于节点中的 IO 是异步的,当你发出.pipe 命令时,控制立即返回到当前上下文,而管道在后台工作。下次调用 res.end() 时,您将关闭流 preventing any more data to be written。

这里的解决方案是让.pipe结束流本身,which is the default。

我认为时间会起作用,因为在不同的机器和不同的数据大小上,异步 IO 理论上可以在可写流上的结束事件完全处理之前完成(小数据集的快速 IO)。

我建议this blog post 了解更多上下文。

【讨论】:

这很有意义。谢谢你的解释。 重新卷曲帖子。这是故意的,因为我同时做这两件事.. 去掉 @ 以测试几个字符与文件中包含的相对较大的有效负载。 所以如果我能理解“有趣的”超时行为......管道需要 500 毫秒,然后关闭连接。超时仍然发生(即使设置为未来 500 秒),但在调用 res.close 时根本不做任何事情,因为 res 已经关闭。我希望会报告某种错误。 提出错误肯定是有用的.. 尽管这里可能还有其他工作。我只是尝试在调用end 之后添加一些res.writes,但它们也默默地失败了。他们应该抛出错误:the docs。有趣..!

以上是关于NodeJS - 如何在不缓冲的情况下流式传输请求正文的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有 Content-Length 标头的情况下流式传输 HTTP 文件上传?

webRTC 真的可以在没有噪音的情况下流式传输高质量的音频吗?

TCP Socket SendAsync操作是否可以在不传输BufferList中的所有字节的情况下完成?

如何显式清空频道?

如何在不关闭 TCP 连接的情况下关闭处理 TCP 请求的线程?

如何在不产生死锁的情况下拥有一个缓冲通道和多个阅读器?