|
| 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 EXPECTED_BODY_LENGTH = 12; |
| 7 | + |
| 8 | +const upgradeReceivedResolvers = Promise.withResolvers(); |
| 9 | + |
| 10 | +const server = http.createServer(); |
| 11 | +server.on('request', common.mustNotCall()); |
| 12 | +server.on('upgrade', function(req, socket, upgradeHead) { |
| 13 | + upgradeReceivedResolvers.resolve(); |
| 14 | + |
| 15 | + // Confirm the upgrade: |
| 16 | + socket.write('HTTP/1.1 101 Switching Protocols\r\n' + |
| 17 | + 'Upgrade: custom-protocol\r\n' + |
| 18 | + 'Connection: Upgrade\r\n' + |
| 19 | + '\r\n'); |
| 20 | + |
| 21 | + // Read and validate the request body: |
| 22 | + let reqBodyLength = 0; |
| 23 | + req.on('data', (chunk) => { reqBodyLength += chunk.length; }); |
| 24 | + req.on('end', common.mustCall(() => { |
| 25 | + assert.strictEqual(reqBodyLength, EXPECTED_BODY_LENGTH); |
| 26 | + |
| 27 | + assert.deepStrictEqual(req.trailers, { 'extra-data': 'abc' }); |
| 28 | + |
| 29 | + // Defer upgrade stream read slightly to make sure it doesn't start |
| 30 | + // streaming along with the request body, until we actually read it: |
| 31 | + setTimeout(common.mustCall(() => { |
| 32 | + let socketData = upgradeHead; |
| 33 | + |
| 34 | + socket.on('data', (d) => { |
| 35 | + socketData = Buffer.concat([socketData, d]); |
| 36 | + }); |
| 37 | + |
| 38 | + socket.on('end', common.mustCall(() => { |
| 39 | + assert.strictEqual(socketData.toString(), 'upgrade head\npost-upgrade message'); |
| 40 | + socket.end(); |
| 41 | + })); |
| 42 | + }), 10); |
| 43 | + })); |
| 44 | +}); |
| 45 | + |
| 46 | +await new Promise((resolve) => server.listen(0, () => resolve())); |
| 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 | + 'trailer: extra-data\r\n' + |
| 61 | + '\r\n' |
| 62 | +); |
| 63 | + |
| 64 | +// Make sure the server has processed the above & fired 'upgrade', before the body |
| 65 | +// data starts streaming: |
| 66 | +await upgradeReceivedResolvers.promise; |
| 67 | + |
| 68 | +// Write the body, and include a chunk extension and a trailer header to check |
| 69 | +// that upgrade handling doesn't skip or confuse anything with request parsing: |
| 70 | +conn.write( |
| 71 | + // 12 byte body sent immediately, with (ignored) chunk extension. |
| 72 | + 'C;chunk-hash=abc\r\nrequest body\r\n' + |
| 73 | + '0\r\n' + |
| 74 | + // Request trailer: |
| 75 | + 'extra-data: abc\r\n' + |
| 76 | + '\r\n' + |
| 77 | + 'upgrade head' |
| 78 | +); |
| 79 | + |
| 80 | +const response = await new Promise((resolve) => conn.once('data', resolve)); |
| 81 | +assert.ok(response.startsWith('HTTP/1.1 101 Switching Protocols\r\n')); |
| 82 | + |
| 83 | +// Send more data after connection is confirmed: |
| 84 | +conn.write('\npost-upgrade message'); |
| 85 | +conn.end(); |
| 86 | + |
| 87 | +await new Promise((resolve) => conn.on('end', resolve)); |
| 88 | + |
| 89 | +server.close(); |
0 commit comments