问题
I am trying to stream responses to my client using a NodeJS Express server hosted using Azure App Service. However, I noticed that it is not really streaming but tries to send the response as a whole. When the response size is huge (>50MB), the client gets an Internal Server Error
, but the server does not throw an error.
Further, when I run the server inside a Docker (Node Image: 10.22.0-alpine3.9
), I see that the client gets the response as a stream even for huge responses. (This is the behavior I actually need)
My web.config
file is as follows.
<?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>
This is a small explanation as to what my program does.
I have an external API that returns an object similar to the following.
{
"title":"Test Title",
"lastBuildDate":"1597981114347",
"items":[
{
id: 'item1',
value: 'value1'
},
{
id: 'item2',
value: 'value2'
},
...
[
}
I want to filter only the elements in items
array and send those to the client. The client should get a response like below.
[
{
id: 'item1',
value: 'value1'
},
{
id: 'item2',
value: 'value2'
},
...
[
Sometimes this object is too large (>50MB) and because of that, I am sending the response as a stream to avoid using too much buffer memory in my server. Below here is the code I used to stream the response.
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);
});
});
})
};
I also noticed that if I write the code like below, I always get a stream response. However, the response is not in the correct format as needed.
https.request(options, (dataStream) => {
dataStream.pipe(res);
});
回答1:
Like I described in the latter part of my question, directly piping the res
(my response to the client) to dataStream
(the data stream I got from the external API) allowed to stream without any issues.
Extending the same behavior, I created a Readable
stream which is equivalent to the response I should send to my client. Then I piped it to res
and it worked.
Here is my solution.
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);
});
})
};
However, I noticed this solution requires more memory than the solution I had issues with. Probably because I am creating a readable stream that is redundant.
来源:https://stackoverflow.com/questions/63555610/response-streaming-in-express-does-not-work-in-azure-app-service