Skip to content

Commit be61c60

Browse files
committed
Update after review
Primarily fixing error handling and improving the tests
1 parent 0a97dda commit be61c60

File tree

6 files changed

+291
-197
lines changed

6 files changed

+291
-197
lines changed

doc/api/http.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1713,7 +1713,7 @@ change.
17131713
In the uncommon case that the incoming request has a body, this body will be
17141714
parsed as normal separate to the upgrade stream, and the raw socket data will
17151715
only begin after it has completed. To ensure that reading from the socket isn't
1716-
blocked bv waiting for the request body to be read, any reads on the socket
1716+
blocked by waiting for the request body to be read, any reads on the socket
17171717
will start the request body flowing automatically. If you want to read the
17181718
request body, ensure that you do so (i.e. you attach `'data'` listeners)
17191719
before starting to read from the upgraded socket.

lib/_http_server.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,6 @@ class UpgradeStream extends Duplex {
971971

972972
// Proxy error, end & closure events immediately.
973973
socket.on('error', (err) => this.destroy(err));
974-
this.on('error', (err) => socket.destroy(err));
975974

976975
socket.on('close', () => this.destroy());
977976
this.on('close', () => socket.destroy());
@@ -1052,6 +1051,11 @@ class UpgradeStream extends Duplex {
10521051
_write(chunk, encoding, callback) {
10531052
this[kSocket].write(chunk, encoding, callback);
10541053
}
1054+
1055+
_destroy(err, callback) {
1056+
this[kSocket].destroy(err);
1057+
callback(err);
1058+
}
10551059
}
10561060

10571061
function onParserExecuteCommon(server, socket, parser, state, ret, d) {
@@ -1095,7 +1099,7 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) {
10951099
return;
10961100
}
10971101

1098-
d ||= Buffer.from([]);
1102+
d ||= Buffer.alloc(0);
10991103

11001104
upgradeStream = new UpgradeStream(socket, req);
11011105
socket[kUpgradeStream] = upgradeStream;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as common from '../common/index.mjs';
2+
import * as assert from 'assert';
3+
import * as http from 'http';
4+
import * as net from 'net';
5+
6+
const upgradeReceivedResolvers = Promise.withResolvers();
7+
8+
const server = http.createServer();
9+
server.on('request', common.mustNotCall());
10+
server.on('upgrade', function(req, socket, upgradeHead) {
11+
upgradeReceivedResolvers.resolve();
12+
13+
// As soon as the body starts arriving, simulate an error
14+
req.on('data', () => {
15+
req.socket.destroy(new Error('simulated body error'));
16+
});
17+
});
18+
19+
await new Promise((resolve) => server.listen(0, () => resolve()));
20+
21+
const conn = net.createConnection(server.address().port);
22+
conn.setEncoding('utf8');
23+
24+
await new Promise((resolve) => conn.on('connect', resolve));
25+
26+
// Write request headers, leave the body pending:
27+
conn.write(
28+
'POST / HTTP/1.1\r\n' +
29+
'host: localhost\r\n' +
30+
'Upgrade: custom-protocol\r\n' +
31+
'Connection: Upgrade\r\n' +
32+
'transfer-encoding: chunked\r\n' +
33+
'\r\n'
34+
);
35+
36+
// Make sure the server has processed the above & fired 'upgrade' before the body
37+
// data starts streaming:
38+
await upgradeReceivedResolvers.promise;
39+
40+
conn.write('5\r\nhello\r\n');
41+
42+
process.on('uncaughtException', common.mustCall((err) => {
43+
assert.strictEqual(err.message, 'simulated body error');
44+
conn.destroy();
45+
server.close();
46+
}));

test/parallel/test-http-upgrade-server-with-body.mjs

Lines changed: 54 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -3,207 +3,67 @@ import * as assert from 'assert';
33
import * as http from 'http';
44
import * as net from 'net';
55

6-
async function createServer({ expectedBodyLength }) {
7-
const server = http.createServer();
8-
9-
server.on('request', common.mustNotCall());
10-
11-
server.on('upgrade', function(req, socket, upgradeHead) {
12-
// Confirm the upgrade:
13-
socket.write('HTTP/1.1 101 Switching Protocols\r\n' +
14-
'Upgrade: custom-protocol\r\n' +
15-
'Connection: Upgrade\r\n' +
16-
'\r\n');
17-
18-
// Read and validate the request body, if and only if expectedBodyLength is set:
19-
if (expectedBodyLength !== undefined) {
20-
let reqBody = '';
21-
req.on('data', (str) => { reqBody += str; });
22-
req.on('end', common.mustCall(() => {
23-
assert.strictEqual(reqBody.length, expectedBodyLength);
6+
const EXPECTED_BODY_LENGTH = 12;
7+
8+
const server = http.createServer();
9+
server.on('request', common.mustNotCall());
10+
server.on('upgrade', function(req, socket, upgradeHead) {
11+
// Confirm the upgrade:
12+
socket.write('HTTP/1.1 101 Switching Protocols\r\n' +
13+
'Upgrade: custom-protocol\r\n' +
14+
'Connection: Upgrade\r\n' +
15+
'\r\n');
16+
17+
// Read and validate the request body:
18+
let reqBodyLength = 0;
19+
req.on('data', (chunk) => { reqBodyLength += chunk.length; });
20+
req.on('end', common.mustCall(() => {
21+
assert.strictEqual(reqBodyLength, EXPECTED_BODY_LENGTH);
22+
23+
// Defer upgrade stream read slightly to make sure it doesn't start
24+
// streaming along with the request body, until we actually read it:
25+
setTimeout(common.mustCall(() => {
26+
let socketData = upgradeHead;
27+
28+
socket.on('data', common.mustCall((d) => {
29+
socketData = Buffer.concat([socketData, d]);
2430
}));
25-
}
26-
27-
let socketData = upgradeHead;
28-
29-
// Collect the raw protocol data and check it:
30-
socket.on('data', common.mustCall((d) => {
31-
socketData = Buffer.concat([socketData, d]);
32-
}));
33-
34-
socket.on('end', common.mustCall(() => {
35-
assert.strictEqual(socketData.toString(), 'upgrade head\npost-upgrade message');
36-
socket.end();
37-
}));
38-
});
39-
40-
await new Promise((resolve) => server.listen(0, () => resolve()));
41-
42-
return server;
43-
}
44-
45-
async function testUpgradeWithBody() {
46-
const server = await createServer({ expectedBodyLength: 12 });
47-
48-
const conn = net.createConnection(server.address().port);
49-
conn.setEncoding('utf8');
50-
51-
await new Promise((resolve) => conn.on('connect', resolve));
52-
53-
// Write request headers, body & upgrade head all together:
54-
conn.write(
55-
'POST / HTTP/1.1\r\n' +
56-
'host: localhost\r\n' +
57-
'Upgrade: custom-protocol\r\n' +
58-
'Connection: Upgrade\r\n' +
59-
'transfer-encoding: chunked\r\n' +
60-
'\r\n' +
61-
'C\r\nrequest body\r\n' + // 12 byte body sent immediately
62-
'0\r\n\r\n' +
63-
'upgrade head'
64-
);
65-
66-
const response = await new Promise((resolve) => conn.once('data', resolve));
67-
assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n'));
68-
69-
// Send more data after connection is confirmed:
70-
conn.write('\npost-upgrade message');
71-
conn.end();
72-
73-
await new Promise((resolve) => conn.on('end', resolve));
74-
75-
server.close();
76-
}
77-
78-
async function testUpgradeWithLargeBody() {
79-
const server = await createServer({ expectedBodyLength: 100_000 });
80-
81-
const conn = net.createConnection(server.address().port);
82-
conn.setEncoding('utf8');
83-
84-
await new Promise((resolve) => conn.on('connect', resolve));
85-
86-
// Write request headers, leave the body pending:
87-
conn.write(
88-
'POST / HTTP/1.1\r\n' +
89-
'host: localhost\r\n' +
90-
'Upgrade: custom-protocol\r\n' +
91-
'Connection: Upgrade\r\n' +
92-
'transfer-encoding: chunked\r\n' +
93-
'\r\n'
94-
);
95-
96-
await new Promise((resolve) => setTimeout(resolve, 10));
9731

98-
// Write the large body and part of the initial upgrade data.
99-
for (let i = 0; i < 20_000; i++) {
100-
conn.write('5\r\nhello\r\n'); // 10 byte chunk = 5 byte body
101-
}
102-
103-
// End body and then send upgrade head
104-
await new Promise((resolve) => setTimeout(resolve, 10));
105-
106-
conn.write(
107-
'0\r\n\r\n' +
108-
'upgrade head'
109-
);
110-
111-
const response = await new Promise((resolve) => conn.once('data', resolve));
112-
assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n'));
113-
114-
// Send more data after connection is confirmed:
115-
conn.write('\npost-upgrade message');
116-
conn.end();
117-
118-
await new Promise((resolve) => conn.on('end', resolve));
119-
120-
server.close();
121-
}
122-
123-
async function testUpgradeWithUnreadBody() {
124-
const server = await createServer({
125-
expectedBodyLength: undefined // Don't read the request body at all
126-
});
127-
128-
const conn = net.createConnection(server.address().port);
129-
conn.setEncoding('utf8');
130-
131-
await new Promise((resolve) => conn.on('connect', resolve));
132-
133-
// Write request headers, body & upgrade head all together:
134-
conn.write(
135-
'POST / HTTP/1.1\r\n' +
136-
'host: localhost\r\n' +
137-
'Upgrade: custom-protocol\r\n' +
138-
'Connection: Upgrade\r\n' +
139-
'transfer-encoding: chunked\r\n' +
140-
'\r\n' +
141-
'C\r\nrequest body\r\n' + // 12 byte body sent immediately
142-
'0\r\n\r\n' +
143-
'upgrade head'
144-
);
145-
146-
const response = await new Promise((resolve) => conn.once('data', resolve));
147-
assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n'));
148-
149-
// Send more data after connection is confirmed:
150-
conn.write('\npost-upgrade message');
151-
conn.end();
152-
153-
await new Promise((resolve) => conn.on('end', resolve));
154-
155-
server.close();
156-
}
157-
158-
async function testUpgradeWithUnreadLargeBody() {
159-
const server = await createServer({
160-
expectedBodyLength: undefined // Don't read the request body at all
161-
});
162-
163-
const conn = net.createConnection(server.address().port);
164-
conn.setEncoding('utf8');
165-
166-
await new Promise((resolve) => conn.on('connect', resolve));
167-
168-
// Write request headers, leave the body pending:
169-
conn.write(
170-
'POST / HTTP/1.1\r\n' +
171-
'host: localhost\r\n' +
172-
'Upgrade: custom-protocol\r\n' +
173-
'Connection: Upgrade\r\n' +
174-
'transfer-encoding: chunked\r\n' +
175-
'\r\n'
176-
);
177-
178-
await new Promise((resolve) => setTimeout(resolve, 10));
179-
180-
// Write the large body and part of the initial upgrade data.
181-
for (let i = 0; i < 20_000; i++) {
182-
conn.write('5\r\nhello\r\n'); // 10 byte chunk = 5 byte body
183-
}
32+
socket.on('end', common.mustCall(() => {
33+
assert.strictEqual(socketData.toString(), 'upgrade head\npost-upgrade message');
34+
socket.end();
35+
}));
36+
}), 10);
37+
}));
38+
});
18439

185-
// End body and then send upgrade head
186-
await new Promise((resolve) => setTimeout(resolve, 10));
40+
await new Promise((resolve) => server.listen(0, () => resolve()));
18741

188-
conn.write(
189-
'0\r\n\r\n' +
190-
'upgrade head'
191-
);
42+
const conn = net.createConnection(server.address().port);
43+
conn.setEncoding('utf8');
19244

193-
const response = await new Promise((resolve) => conn.once('data', resolve));
194-
assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n'));
45+
await new Promise((resolve) => conn.on('connect', resolve));
19546

196-
// Send more data after connection is confirmed:
197-
conn.write('\npost-upgrade message');
198-
conn.end();
47+
// Write request headers, body & upgrade head all together:
48+
conn.write(
49+
'POST / HTTP/1.1\r\n' +
50+
'host: localhost\r\n' +
51+
'Upgrade: custom-protocol\r\n' +
52+
'Connection: Upgrade\r\n' +
53+
'transfer-encoding: chunked\r\n' +
54+
'\r\n' +
55+
'C\r\nrequest body\r\n' + // 12 byte body sent immediately
56+
'0\r\n\r\n' +
57+
'upgrade head'
58+
);
19959

200-
await new Promise((resolve) => conn.on('end', resolve));
60+
const response = await new Promise((resolve) => conn.once('data', resolve));
61+
assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n'));
20162

202-
server.close();
63+
// Send more data after connection is confirmed:
64+
conn.write('\npost-upgrade message');
65+
conn.end();
20366

204-
}
67+
await new Promise((resolve) => conn.on('end', resolve));
20568

206-
await testUpgradeWithBody();
207-
await testUpgradeWithLargeBody();
208-
await testUpgradeWithUnreadBody();
209-
await testUpgradeWithUnreadLargeBody();
69+
server.close();

0 commit comments

Comments
 (0)