| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c1c4fe9 commit 9b043a9
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1671,6 +1671,11 @@ per connection (in the case of HTTP Keep-Alive connections). | |||
| 1671 | 1671 | <!-- YAML | |
| 1672 | 1672 | added: v0.1.94 | |
| 1673 | 1673 | changes: | |
| 1674 | + - version: REPLACEME | ||
| 1675 | + pr-url: https://github.com/nodejs/node/pull/59824 | ||
| 1676 | + description: Whether this event is fired can now be controlled by the | ||
| 1677 | + `shouldUpgradeCallback` and sockets will be destroyed | ||
| 1678 | + if upgraded while no event handler is listening. | ||
| 1674 | 1679 | - version: v10.0.0 | |
| 1675 | 1680 | pr-url: https://github.com/nodejs/node/pull/19981 | |
| 1676 | 1681 | description: Not listening to this event no longer causes the socket | |
@@ -1682,13 +1687,25 @@ changes: | |||
| 1682 | 1687 | * `socket` {stream.Duplex} Network socket between the server and client | |
| 1683 | 1688 | * `head` {Buffer} The first packet of the upgraded stream (may be empty) | |
| 1684 | 1689 | ||
| 1685 | - Emitted each time a client requests an HTTP upgrade. Listening to this event | ||
| 1686 | - is optional and clients cannot insist on a protocol change. | ||
| 1690 | + Emitted each time a client's HTTP upgrade request is accepted. By default | ||
| 1691 | + all HTTP upgrade requests are ignored (i.e. only regular `'request'` events | ||
| 1692 | + are emitted, sticking with the normal HTTP request/response flow) unless you | ||
| 1693 | + listen to this event, in which case they are all accepted (i.e. the `'upgrade'` | ||
| 1694 | + event is emitted instead, and future communication must handled directly | ||
| 1695 | + through the raw socket). You can control this more precisely by using the | ||
| 1696 | + server `shouldUpgradeCallback` option. | ||
| 1697 | + | ||
| 1698 | + Listening to this event is optional and clients cannot insist on a protocol | ||
| 1699 | + change. | ||
| 1687 | 1700 | ||
| 1688 | 1701 | After this event is emitted, the request's socket will not have a `'data'` | |
| 1689 | 1702 | event listener, meaning it will need to be bound in order to handle data | |
| 1690 | 1703 | sent to the server on that socket. | |
| 1691 | 1704 | ||
| 1705 | + If an upgrade is accepted by `shouldUpgradeCallback` but no event handler | ||
| 1706 | + is registered then the socket is destroyed, resulting in an immediate | ||
| 1707 | + connection closure for the client. | ||
| 1708 | + | ||
| 1692 | 1709 | This event is guaranteed to be passed an instance of the {net.Socket} class, | |
| 1693 | 1710 | a subclass of {stream.Duplex}, unless the user specifies a socket | |
| 1694 | 1711 | type other than {net.Socket}. | |
@@ -3535,6 +3552,9 @@ Found'`. | |||
| 3535 | 3552 | <!-- YAML | |
| 3536 | 3553 | added: v0.1.13 | |
| 3537 | 3554 | changes: | |
| 3555 | + - version: REPLACEME | ||
| 3556 | + pr-url: https://github.com/nodejs/node/pull/59824 | ||
| 3557 | + description: The `shouldUpgradeCallback` option is now supported. | ||
| 3538 | 3558 | - version: | |
| 3539 | 3559 | - v20.1.0 | |
| 3540 | 3560 | - v18.17.0 | |
@@ -3624,6 +3644,13 @@ changes: | |||
| 3624 | 3644 | * `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class | |
| 3625 | 3645 | to be used. Useful for extending the original `ServerResponse`. **Default:** | |
| 3626 | 3646 | `ServerResponse`. | |
| 3647 | + * `shouldUpgradeCallback(request)` {Function} A callback which receives an | ||
| 3648 | + incoming request and returns a boolean, to control which upgrade attempts | ||
| 3649 | + should be accepted. Accepted upgrades will fire an `'upgrade'` event (or | ||
| 3650 | + their sockets will be destroyed, if no listener is registered) while | ||
| 3651 | + rejected upgrades will fire a `'request'` event like any non-upgrade | ||
| 3652 | + request. This options defaults to | ||
| 3653 | + `() => server.listenerCount('upgrade') > 0`. | ||
| 3627 | 3654 | * `uniqueHeaders` {Array} A list of response headers that should be sent only | |
| 3628 | 3655 | once. If the header's value is an array, the items will be joined | |
| 3629 | 3656 | using `; `. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -90,6 +90,7 @@ const { | |||
| 90 | 90 | validateBoolean, | |
| 91 | 91 | validateLinkHeaderValue, | |
| 92 | 92 | validateObject, | |
| 93 | + validateFunction, | ||
| 93 | 94 | } = require('internal/validators'); | |
| 94 | 95 | const Buffer = require('buffer').Buffer; | |
| 95 | 96 | const { setInterval, clearInterval } = require('timers'); | |
@@ -520,6 +521,16 @@ function storeHTTPOptions(options) { | |||
| 520 | 521 | } else { | |
| 521 | 522 | this.rejectNonStandardBodyWrites = false; | |
| 522 | 523 | } | |
| 524 | + | ||
| 525 | + const shouldUpgradeCallback = options.shouldUpgradeCallback; | ||
| 526 | + if (shouldUpgradeCallback !== undefined) { | ||
| 527 | + validateFunction(shouldUpgradeCallback, 'options.shouldUpgradeCallback'); | ||
| 528 | + this.shouldUpgradeCallback = shouldUpgradeCallback; | ||
| 529 | + } else { | ||
| 530 | + this.shouldUpgradeCallback = function() { | ||
| 531 | + return this.listenerCount('upgrade') > 0; | ||
| 532 | + }; | ||
| 533 | + } | ||
| 523 | 534 | } | |
| 524 | 535 | ||
| 525 | 536 | function setupConnectionsTracking() { | |
@@ -955,15 +966,15 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) { | |||
| 955 | 966 | parser = null; | |
| 956 | 967 | ||
| 957 | 968 | const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; | |
| 958 | - if (eventName === 'upgrade' || server.listenerCount(eventName) > 0) { | ||
| 969 | + if (server.listenerCount(eventName) > 0) { | ||
| 959 | 970 | debug('SERVER have listener for %s', eventName); | |
| 960 | 971 | const bodyHead = d.slice(ret, d.length); | |
| 961 | 972 | ||
| 962 | 973 | socket.readableFlowing = null; | |
| 963 | 974 | ||
| 964 | 975 | server.emit(eventName, req, socket, bodyHead); | |
| 965 | 976 | } else { | |
| 966 | - // Got CONNECT method, but have no handler. | ||
| 977 | + // Got upgrade or CONNECT method, but have no handler. | ||
| 967 | 978 | socket.destroy(); | |
| 968 | 979 | } | |
| 969 | 980 | } else if (parser.incoming && parser.incoming.method === 'PRI') { | |
@@ -1062,7 +1073,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { | |||
| 1062 | 1073 | ||
| 1063 | 1074 | if (req.upgrade) { | |
| 1064 | 1075 | req.upgrade = req.method === 'CONNECT' || | |
| 1065 | - server.listenerCount('upgrade') > 0; | ||
| 1076 | + !!server.shouldUpgradeCallback(req); | ||
| 1066 | 1077 | if (req.upgrade) | |
| 1067 | 1078 | return 2; | |
| 1068 | 1079 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,167 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + | ||
| 6 | + const net = require('net'); | ||
| 7 | + const http = require('http'); | ||
| 8 | + | ||
| 9 | + function testUpgradeCallbackTrue() { | ||
| 10 | + const server = http.createServer({ | ||
| 11 | + shouldUpgradeCallback: common.mustCall((req) => { | ||
| 12 | + assert.strictEqual(req.url, '/websocket'); | ||
| 13 | + assert.strictEqual(req.headers.upgrade, 'websocket'); | ||
| 14 | + | ||
| 15 | + return true; | ||
| 16 | + }) | ||
| 17 | + }); | ||
| 18 | + | ||
| 19 | + server.on('upgrade', function(req, socket, upgradeHead) { | ||
| 20 | + assert.strictEqual(req.url, '/websocket'); | ||
| 21 | + assert.strictEqual(req.headers.upgrade, 'websocket'); | ||
| 22 | + assert.ok(socket instanceof net.Socket); | ||
| 23 | + assert.ok(upgradeHead instanceof Buffer); | ||
| 24 | + | ||
| 25 | + socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + | ||
| 26 | + 'Upgrade: WebSocket\r\n' + | ||
| 27 | + 'Connection: Upgrade\r\n' + | ||
| 28 | + '\r\n\r\n'); | ||
| 29 | + }); | ||
| 30 | + | ||
| 31 | + server.on('request', common.mustNotCall()); | ||
| 32 | + | ||
| 33 | + server.listen(0, common.mustCall(() => { | ||
| 34 | + const req = http.request({ | ||
| 35 | + port: server.address().port, | ||
| 36 | + path: '/websocket', | ||
| 37 | + headers: { | ||
| 38 | + 'Upgrade': 'websocket', | ||
| 39 | + 'Connection': 'Upgrade' | ||
| 40 | + } | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + req.on('upgrade', common.mustCall((res, socket, upgradeHead) => { | ||
| 44 | + assert.strictEqual(res.statusCode, 101); | ||
| 45 | + assert.ok(socket instanceof net.Socket); | ||
| 46 | + assert.ok(upgradeHead instanceof Buffer); | ||
| 47 | + socket.end(); | ||
| 48 | + server.close(); | ||
| 49 | + | ||
| 50 | + testUpgradeCallbackFalse(); | ||
| 51 | + })); | ||
| 52 | + | ||
| 53 | + req.on('response', common.mustNotCall()); | ||
| 54 | + req.end(); | ||
| 55 | + })); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + | ||
| 59 | + function testUpgradeCallbackFalse() { | ||
| 60 | + const server = http.createServer({ | ||
| 61 | + shouldUpgradeCallback: common.mustCall(() => { | ||
| 62 | + return false; | ||
| 63 | + }) | ||
| 64 | + }); | ||
| 65 | + | ||
| 66 | + server.on('upgrade', common.mustNotCall()); | ||
| 67 | + | ||
| 68 | + server.on('request', common.mustCall((req, res) => { | ||
| 69 | + res.writeHead(200, { 'Content-Type': 'text/plain' }); | ||
| 70 | + res.write('received but not upgraded'); | ||
| 71 | + res.end(); | ||
| 72 | + })); | ||
| 73 | + | ||
| 74 | + server.listen(0, common.mustCall(() => { | ||
| 75 | + const req = http.request({ | ||
| 76 | + port: server.address().port, | ||
| 77 | + path: '/websocket', | ||
| 78 | + headers: { | ||
| 79 | + 'Upgrade': 'websocket', | ||
| 80 | + 'Connection': 'Upgrade' | ||
| 81 | + } | ||
| 82 | + }); | ||
| 83 | + | ||
| 84 | + req.on('upgrade', common.mustNotCall()); | ||
| 85 | + | ||
| 86 | + req.on('response', common.mustCall((res) => { | ||
| 87 | + assert.strictEqual(res.statusCode, 200); | ||
| 88 | + let data = ''; | ||
| 89 | + res.on('data', (chunk) => { data += chunk; }); | ||
| 90 | + res.on('end', common.mustCall(() => { | ||
| 91 | + assert.strictEqual(data, 'received but not upgraded'); | ||
| 92 | + server.close(); | ||
| 93 | + | ||
| 94 | + testUpgradeCallbackTrueWithoutHandler(); | ||
| 95 | + })); | ||
| 96 | + })); | ||
| 97 | + req.end(); | ||
| 98 | + })); | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + | ||
| 102 | + function testUpgradeCallbackTrueWithoutHandler() { | ||
| 103 | + const server = http.createServer({ | ||
| 104 | + shouldUpgradeCallback: common.mustCall(() => { | ||
| 105 | + return true; | ||
| 106 | + }) | ||
| 107 | + }); | ||
| 108 | + | ||
| 109 | + // N.b: no 'upgrade' handler | ||
| 110 | + server.on('request', common.mustNotCall()); | ||
| 111 | + | ||
| 112 | + server.listen(0, common.mustCall(() => { | ||
| 113 | + const req = http.request({ | ||
| 114 | + port: server.address().port, | ||
| 115 | + path: '/websocket', | ||
| 116 | + headers: { | ||
| 117 | + 'Upgrade': 'websocket', | ||
| 118 | + 'Connection': 'Upgrade' | ||
| 119 | + } | ||
| 120 | + }); | ||
| 121 | + | ||
| 122 | + req.on('upgrade', common.mustNotCall()); | ||
| 123 | + req.on('response', common.mustNotCall()); | ||
| 124 | + | ||
| 125 | + req.on('error', common.mustCall((e) => { | ||
| 126 | + assert.strictEqual(e.code, 'ECONNRESET'); | ||
| 127 | + server.close(); | ||
| 128 | + | ||
| 129 | + testUpgradeCallbackError(); | ||
| 130 | + })); | ||
| 131 | + req.end(); | ||
| 132 | + })); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + | ||
| 136 | + function testUpgradeCallbackError() { | ||
| 137 | + const server = http.createServer({ | ||
| 138 | + shouldUpgradeCallback: common.mustCall(() => { | ||
| 139 | + throw new Error('should upgrade callback failed'); | ||
| 140 | + }) | ||
| 141 | + }); | ||
| 142 | + | ||
| 143 | + server.on('upgrade', common.mustNotCall()); | ||
| 144 | + server.on('request', common.mustNotCall()); | ||
| 145 | + | ||
| 146 | + server.listen(0, common.mustCall(() => { | ||
| 147 | + const req = http.request({ | ||
| 148 | + port: server.address().port, | ||
| 149 | + path: '/websocket', | ||
| 150 | + headers: { | ||
| 151 | + 'Upgrade': 'websocket', | ||
| 152 | + 'Connection': 'Upgrade' | ||
| 153 | + } | ||
| 154 | + }); | ||
| 155 | + | ||
| 156 | + req.on('upgrade', common.mustNotCall()); | ||
| 157 | + req.on('response', common.mustNotCall()); | ||
| 158 | + | ||
| 159 | + process.on('uncaughtException', common.mustCall(() => { | ||
| 160 | + process.exit(0); | ||
| 161 | + })); | ||
| 162 | + | ||
| 163 | + req.end(); | ||
| 164 | + })); | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + testUpgradeCallbackTrue(); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments