微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Express中的响应流在Azure App Service中不起作用

如何解决Express中的响应流在Azure App Service中不起作用

我正在尝试使用通过Azure App Service托管的NodeJS Express服务器将响应流式传输到客户端。但是,我注意到它不是真正的流式传输,而是尝试整体发送响应。当响应大小很大(> 50MB)时,客户端会收到Internal Server Error,但服务器不会抛出错误

此外,当我在Docker(节点映像:10.22.0-alpine3.9)中运行服务器时,我看到即使是巨大的响应,客户端也会以流的形式获得响应。 (这是我真正需要的行为)

我的web.config文件如下。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="iisnode" path="server.js" verb="*" modules="iisnode" responseBufferLimit="0"/>
    </handlers>
    <iisnode flushResponse="true" />
    ...
  </system.webServer>
</configuration>

这只是我程序的简单解释。

我有一个外部API,该API返回类似于以下内容的对象。

{
  "title":"Test Title","lastBuildDate":"1597981114347","items":[
    {
      id: 'item1',value: 'value1'
    },{
      id: 'item2',value: 'value2'
    },...
  [
}

我只想过滤items数组中的元素,然后将它们发送给客户端。客户应收到如下响应。

[
   {
     id: 'item1',value: 'value1'
   },{
     id: 'item2',value: 'value2'
   },...
[

有时该对象太大(> 50MB),因此,我将响应作为流发送,以避免在服务器中使用过多的缓冲内存。下面是我用于流式传输响应的代码

const https = require('https');
const { withParser } = require('stream-json/filters/Pick');
const { streamArray } = require('stream-json/streamers/StreamArray');
const { chain } = require('stream-chain');

exports.getStreamResponse = async function (req,res) {
  const options = {
    hostname,port,path,method: 'GET',};

  return new Promise((resolve,reject) => {
    https.request(options,(dataStream) => {
      const pipeline = chain([
        dataStream,withParser({ filter: 'items' }),streamArray()
      ]);
  
      res.write("[");
  
      let separator = '';
  
      pipeline.on('data',data => {
        res.write(separator + JSON.stringify(data.value));
        if (!separator) {
          separator = ',';
        }
      });
  
      pipeline.on('end',() => {
        res.write("]");
        res.end();
        resolve();
      });

      pipeline.on('error',(error) => {
        reject(error);
      });
    });
  })
};
            

我还注意到,如果我像下面那样编写代码,则总是会得到流响应。但是,响应的格式不正确。

https.request(options,(dataStream) => {
  dataStream.pipe(res);
});

解决方法

就像我在问题的后半部分描述的那样,将res(我对客户端的响应)直接管道传输到dataStream(我从外部API获得的数据流),而无需任何问题。

出于相同的行为,我创建了一个Readable流,该流等效于应发送给我的客户端的响应。然后,我将其通过管道传输到res,并且可以正常工作。

这是我的解决方法。

const https = require('https');
const { withParser } = require('stream-json/filters/Pick');
const { streamArray } = require('stream-json/streamers/StreamArray');
const { chain } = require('stream-chain');
const { Readable } = require('stream');

exports.getStreamResponse = async function (req,res) {
  const options = {
    hostname,port,path,method: 'GET',};

  return new Promise((resolve,reject) => {
    https.request(options,(dataStream) => {
      const pipeline = chain([
        dataStream,withParser({ filter: 'items' }),streamArray()
      ]);
  
      // create a readable stream to collect data from response 
      const readable = new Readable({
        // this empty method is to avoid 'ERR_METHOD_NOT_IMPLEMENTED'
        // error when read method is called while there is no data in the
        // readable stream
        read(size) { }
      });
  
      let separator = '';
  
      readable.pipe(res);
      readable.push("[");

      pipeline.on('data',data => {
        readable.push(separator + JSON.stringify(data.value));
        if (!separator) {
          separator = ',';
        }
      });

      pipeline.on('end',() => {
        readable.push("]");
        readable.push(null);
        resolve();
      });
            
      pipeline.on('error',reject);
    });
  })
};

但是,我注意到此解决方案比我遇到的解决方案需要更多的内存。可能是因为我正在创建一个多余的可读流。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。