Skip to content

Commit d572c59

Browse files
committed
Merge branch 'test-stuck-reads'
2 parents 78d0141 + 2b08027 commit d572c59

File tree

6 files changed

+194
-9
lines changed

6 files changed

+194
-9
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

test/mocha/integration/echo.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ fetch = async (...args) => {
3636

3737
const fcgi = require('../../../index.js');
3838

39+
function timeoutSignal(ms) {
40+
const ctrl = new AbortController();
41+
setTimeout(() => ctrl.abort(), ms);
42+
return ctrl.signal;
43+
}
44+
3945
describe('echo server', function setup() {
4046
let httpURL;
4147

@@ -112,7 +118,7 @@ describe('echo server', function setup() {
112118
});
113119

114120
it('should answer with the request', async () => {
115-
const res = await fetch(httpURL);
121+
const res = await fetch(httpURL, { signal: timeoutSignal(1000) });
116122

117123
expect(res.status).to.be.equal(200);
118124
expect(res.headers.get('Content-Type')).to.be.equal("application/json; charset=utf-8");
@@ -134,7 +140,8 @@ describe('echo server', function setup() {
134140

135141
const res = await fetch(new URL(reqPath, httpURL), {
136142
method: 'POST',
137-
body: reqBody
143+
body: reqBody,
144+
signal: timeoutSignal(1000)
138145
});
139146

140147
expect(res.status).to.be.equal(200);
@@ -155,7 +162,7 @@ describe('echo server', function setup() {
155162
reqURL.searchParams.set('a', 'b');
156163
reqURL.searchParams.set('ca', 'd');
157164

158-
const res = await fetch(reqURL);
165+
const res = await fetch(reqURL, { signal: timeoutSignal(1000) });
159166

160167
expect(res.status).to.be.equal(200);
161168
expect(res.headers.get('Content-Type')).to.be.equal("application/json; charset=utf-8");
@@ -170,7 +177,8 @@ describe('echo server', function setup() {
170177
const authHdr = `Basic ${Buffer.from("ArthurDent:I think I'm a sofa...").toString('base64')}`;
171178

172179
const res = await fetch(httpURL, {
173-
headers: { 'Authorization': authHdr }
180+
headers: { 'Authorization': authHdr },
181+
signal: timeoutSignal(1000)
174182
});
175183

176184
expect(res.status).to.be.equal(200);
@@ -192,7 +200,8 @@ describe('echo server', function setup() {
192200
'x_test_hdr': hdr2, // passes hyphens in CGI params
193201
'Content-Type': "text/plain"
194202
},
195-
body: reqBody
203+
body: reqBody,
204+
signal: timeoutSignal(1000)
196205
});
197206

198207
expect(res.status).to.be.equal(200);

test/mocha/integration/multiwrite.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ fetch = async (...args) => {
3636

3737
const fcgi = require('../../../index.js');
3838

39+
function timeoutSignal(ms) {
40+
const ctrl = new AbortController();
41+
setTimeout(() => ctrl.abort(), ms);
42+
return ctrl.signal;
43+
}
44+
3945
describe('multiwrite server', function setup() {
4046
let httpURL;
4147

@@ -103,7 +109,7 @@ describe('multiwrite server', function setup() {
103109
});
104110

105111
it('should answer with the expected body', async () => {
106-
const res = await fetch(httpURL);
112+
const res = await fetch(httpURL, { signal: timeoutSignal(1000) });
107113
expect(res.status).to.be.equal(200);
108114

109115
const body = await res.text();

test/mocha/integration/multiwrite_no_content_length.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ fetch = async (...args) => {
3636

3737
const fcgi = require('../../../index.js');
3838

39+
function timeoutSignal(ms) {
40+
const ctrl = new AbortController();
41+
setTimeout(() => ctrl.abort(), ms);
42+
return ctrl.signal;
43+
}
44+
3945
describe('multiwrite server (no content-length)', function setup() {
4046
let httpURL;
4147

@@ -102,7 +108,7 @@ describe('multiwrite server (no content-length)', function setup() {
102108
});
103109

104110
it('should answer with the expected body', async () => {
105-
const res = await fetch(httpURL);
111+
const res = await fetch(httpURL, { signal: timeoutSignal(1000) });
106112
expect(res.status).to.be.equal(200);
107113

108114
const body = await res.text();
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)