Потоковая передача ответов в Express не работает в службе приложений Azure

#node.js #azure #express #azure-web-app-service #node-streams

#node.js #azure #экспресс #azure-web-app-service #потоки узлов

Вопрос:

Я пытаюсь передать ответы моему клиенту с помощью сервера NodeJS Express, размещенного с использованием службы приложений Azure. Однако я заметил, что на самом деле это не потоковая передача, а попытка отправить ответ целиком. Когда размер ответа огромен (> 50 МБ), клиент получает 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, который возвращает объект, подобный следующему.

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

Я хочу отфильтровать только элементы в items массиве и отправить их клиенту. Клиент должен получить ответ, подобный приведенному ниже.

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

Иногда этот объект слишком большой (> 50 МБ), и из-за этого я отправляю ответ в виде потока, чтобы избежать использования слишком большого объема буферной памяти на моем сервере. Ниже приведен код, который я использовал для потоковой передачи ответа.

 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);
      });
    });
  })
};
            
  

Я также заметил, что если я пишу код, подобный приведенному ниже, я всегда получаю ответ stream. Однако ответ представлен не в том формате, который необходим.

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

Ответ №1:

Как я описал в последней части моего вопроса, прямая передача 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);
    });
  })
};
  

Однако я заметил, что это решение требует больше памяти, чем решение, с которым у меня были проблемы. Вероятно, потому, что я создаю избыточный поток для чтения.