#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);
});
})
};
Однако я заметил, что это решение требует больше памяти, чем решение, с которым у меня были проблемы. Вероятно, потому, что я создаю избыточный поток для чтения.