Skip to content

Commit 2b08027

Browse files
committed
Reproduce backpressure issue from #24
1 parent 436fcf8 commit 2b08027

File tree

3 files changed

+166
-2
lines changed

3 files changed

+166
-2
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#npm
1+
# npm
22
node_modules
33

44
# Code coverage
@@ -7,3 +7,6 @@ coverage
77

88
# Log files
99
*.log
10+
11+
# vscode
12+
.vscode

.npmignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@ node_modules
1010

1111
# Code coverage
1212
.nyc_output
13-
coverage
13+
coverage
14+
15+
# vscode
16+
.vscode
17+
18+
# github actions
19+
.github
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/**
2+
* Copyright (c) 2023 Fabio Massaioli, Robert Groh and other contributors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the 'Software'), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
'use strict';
23+
/*global describe, it, before, after */
24+
/*jshint node: true */
25+
/*jshint expr: true*/
26+
27+
const fcgiHandler = require('fcgi-handler'),
28+
http = require('http'),
29+
stream = require('stream'),
30+
expect = require('chai').expect;
31+
32+
const fcgi = require('../../../index.js');
33+
34+
function timeoutSignal(ms) {
35+
const ctrl = new AbortController();
36+
setTimeout(() => ctrl.abort(), ms);
37+
return ctrl.signal;
38+
}
39+
40+
describe('stdin backpressuring server', function setup() {
41+
let httpURL;
42+
43+
stream.setDefaultHighWaterMark(false, 3);
44+
45+
before(function startFcgiServer(done) {
46+
function sendError(res, err) {
47+
res.writeHead(500, {
48+
'Content-Type': 'text/plain; charset=utf-8',
49+
'Content-Length': err.stack.length + 1
50+
});
51+
res.end(err.stack + '\n');
52+
}
53+
54+
const fcgiServer = fcgi.createServer(async function delayedRead(req, res) {
55+
await new Promise(r => setTimeout(r, 400));
56+
57+
let reqBody = "";
58+
59+
req.on('data', (data) => { reqBody += data.toString(); });
60+
61+
req.on('end', () => {
62+
try {
63+
const length = Buffer.byteLength(reqBody, "utf8");
64+
65+
res.writeHead(200, {
66+
'Content-Type': 'text/plain; charset=utf-8',
67+
'Content-Length': length
68+
});
69+
res.end(reqBody);
70+
} catch (err) {
71+
sendError(res, err);
72+
}
73+
});
74+
75+
req.on('error', sendError.bind(null, res));
76+
})
77+
78+
fcgiServer.listen(0, '127.0.0.1', function startHttpServer(err) {
79+
if (err) {
80+
done(err);
81+
return;
82+
}
83+
84+
const fcgiAddr = fcgiServer.address();
85+
console.log(`fcgi server listening at ${fcgiAddr.address}:${fcgiAddr.port}`);
86+
87+
const httpServer = http.createServer((req, res) => {
88+
fcgiHandler.connect({
89+
__proto__: fcgiAddr,
90+
noDelay: true
91+
}, (err, fcgiProcess) => {
92+
if (err)
93+
sendError(res, err);
94+
else
95+
fcgiProcess.handle(req, res, {});
96+
});
97+
});
98+
99+
httpServer.listen(0, '127.0.0.1', (err) => {
100+
if (err) {
101+
done(err);
102+
return;
103+
}
104+
105+
const httpAddr = httpServer.address();
106+
console.log(`http server listening at ${httpAddr.address}:${httpAddr.port}`);
107+
108+
httpURL = new URL(`http://${httpAddr.address}:${httpAddr.port}`);
109+
110+
done();
111+
});
112+
});
113+
});
114+
115+
it('should answer with the request body', async () => {
116+
const reqBody1 = "Hello ";
117+
const reqBody2 = "World!";
118+
119+
const res = await new Promise((resolve, reject) => {
120+
try {
121+
const req = http.request(httpURL, {
122+
method: 'POST',
123+
headers: {
124+
'Content-Type': "text/plain; charset=utf8"
125+
},
126+
noDelay: true,
127+
signal: timeoutSignal(1000)
128+
}, resolve);
129+
130+
req.on('error', reject);
131+
132+
req.write(reqBody1);
133+
134+
setTimeout(() => {
135+
try {
136+
req.write(reqBody2);
137+
req.end();
138+
} catch (err) {
139+
reject(err);
140+
}
141+
}, 800);
142+
} catch (err) {
143+
reject(err);
144+
}
145+
});
146+
147+
expect(res.statusCode).to.be.equal(200);
148+
149+
let body = "";
150+
for await (const chunk of res)
151+
body += chunk;
152+
153+
expect(body).to.be.equal(reqBody1 + reqBody2);
154+
});
155+
});

0 commit comments

Comments
 (0)