Skip to content

Commit db9387a

Browse files
committed
Fix upgrade with request body streaming
1 parent 33ef11a commit db9387a

File tree

3 files changed

+63
-21
lines changed

3 files changed

+63
-21
lines changed

doc/api/http.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,11 @@ per connection (in the case of HTTP Keep-Alive connections).
16721672
<!-- YAML
16731673
added: v0.1.94
16741674
changes:
1675+
- version: REPLACEME
1676+
pr-url: https://github.com/nodejs/node/pull/00000
1677+
description: This event will not fire until the request body has been
1678+
received. Previously, it fired immediately and the request
1679+
body was incorrectly included in `head` instead.
16751680
- version: v24.9.0
16761681
pr-url: https://github.com/nodejs/node/pull/59824
16771682
description: Whether this event is fired can now be controlled by the
@@ -1703,6 +1708,11 @@ After this event is emitted, the request's socket will not have a `'data'`
17031708
event listener, meaning it will need to be bound in order to handle data
17041709
sent to the server on that socket.
17051710

1711+
In the (uncommon) case that the incoming request has a body, this event will
1712+
not be emitted until the body has been fully received. This differs from normal
1713+
`'request'` event handling, but ensures that all data received on the socket
1714+
is part of the upgraded protocol, not the initial request.
1715+
17061716
If an upgrade is accepted by `shouldUpgradeCallback` but no event handler
17071717
is registered then the socket is destroyed, resulting in an immediate
17081718
connection closure for the client.

lib/_http_server.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,12 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) {
952952
debug('parse error', ret);
953953
socketOnError.call(socket, ret);
954954
} else if (parser.incoming?.upgrade) {
955+
if (!parser.incoming.complete) {
956+
// The request is still incomplete - this means it's an upgrade with a request
957+
// body that hasn't yet been received. We have to wait for the body.
958+
return;
959+
}
960+
955961
// Upgrade or CONNECT
956962
const req = parser.incoming;
957963
debug('SERVER upgrade or connect', req.method);

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

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,63 @@ const server = http.createServer();
1010
server.on('request', common.mustNotCall());
1111

1212
server.on('upgrade', function(req, socket, upgradeHead) {
13+
// Read the body:
1314
let reqBody = '';
1415
req.on('data', common.mustCall((str) => { reqBody += str; }));
1516
req.on('end', common.mustCall(() => {
1617
assert.strictEqual(reqBody, 'request body');
17-
socket.end();
1818
}));
1919

2020
assert.strictEqual(upgradeHead.toString(), 'upgrade head');
21+
22+
// Confirm the upgrade:
23+
socket.write('HTTP/1.1 101 Switching Protocols\r\n' +
24+
'Upgrade: custom-protocol\r\n' +
25+
'Connection: Upgrade\r\n' +
26+
'\r\n');
27+
28+
// Check we see the upgraded protocol data only (not the
29+
// request body):
30+
socket.on('data', common.mustCall((d) => {
31+
assert.strictEqual(d.toString(), 'post-upgrade message');
32+
socket.end();
33+
}));
2134
});
2235

23-
server.listen(0, function() {
36+
server.listen(0, async function() {
2437
const conn = net.createConnection(this.address().port);
2538
conn.setEncoding('utf8');
2639

27-
conn.on('connect', function() {
28-
conn.write(
29-
'POST / HTTP/1.1\r\n' +
30-
'Upgrade: WebSocket\r\n' +
31-
'Connection: Upgrade\r\n' +
32-
'Content-Length: 12\r\n' +
33-
'\r\n' +
34-
'request body' +
35-
'upgrade head'
36-
);
37-
});
38-
39-
conn.on('end', function() {
40-
conn.end();
41-
});
42-
43-
conn.on('close', function() {
44-
server.close();
45-
});
40+
await new Promise((resolve) => conn.on('connect', resolve));
41+
42+
// Write request headers, leave the body pending:
43+
conn.write(
44+
'POST / HTTP/1.1\r\n' +
45+
'host: localhost\r\n' +
46+
'Upgrade: custom-protocol\r\n' +
47+
'Connection: Upgrade\r\n' +
48+
'transfer-encoding: chunked\r\n' +
49+
'\r\n'
50+
);
51+
52+
await new Promise((resolve) => setTimeout(resolve, 10));
53+
54+
// Write the body and part of the initial upgrade data. Most clients
55+
// shouldn't send this until after 101 response, but we want to make
56+
// sure it works even if weird cases.
57+
conn.write(
58+
'C\r\nrequest body\r\n' +
59+
'0\r\n\r\n' +
60+
'upgrade head'
61+
);
62+
63+
const response = await new Promise((resolve) => conn.once('data', resolve));
64+
assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n'));
65+
66+
conn.write('post-upgrade message');
67+
68+
await new Promise((resolve) => conn.on('end', resolve));
69+
70+
conn.end();
71+
server.close();
4672
});

0 commit comments

Comments
 (0)