Skip to content

Commit 6b08d00

Browse files
martenrichterrichardlau
authored andcommitted
http2: receive customsettings
This commit gives node.js the ability to also receive custom settings, in addition to sending, them which was implemented before. The custom settings received are limited to setting ids, that were specified before, when creating the session eithers through the server or the client. PR-URL: #51323 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Marco Ippolito <[email protected]>
1 parent 50ec55c commit 6b08d00

9 files changed

+383
-31
lines changed

doc/api/http2.md

+18-3
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,11 @@ changes:
24982498
**Default:** `100`.
24992499
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
25002500
remote peer upon connection.
2501+
* `remoteCustomSettings` {Array} The array of integer values determines the
2502+
settings types, which are included in the `CustomSettings`-property of
2503+
the received remoteSettings. Please see the `CustomSettings`-property of
2504+
the `Http2Settings` object for more information,
2505+
on the allowed setting types.
25012506
* `Http1IncomingMessage` {http.IncomingMessage} Specifies the
25022507
`IncomingMessage` class to used for HTTP/1 fallback. Useful for extending
25032508
the original `http.IncomingMessage`. **Default:** `http.IncomingMessage`.
@@ -2652,6 +2657,10 @@ changes:
26522657
**Default:** `100`.
26532658
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
26542659
remote peer upon connection.
2660+
* `remoteCustomSettings` {Array} The array of integer values determines the
2661+
settings types, which are included in the `customSettings`-property of the
2662+
received remoteSettings. Please see the `customSettings`-property of the
2663+
`Http2Settings` object for more information, on the allowed setting types.
26552664
* ...: Any [`tls.createServer()`][] options can be provided. For
26562665
servers, the identity options (`pfx` or `key`/`cert`) are usually required.
26572666
* `origins` {string\[]} An array of origin strings to send within an `ORIGIN`
@@ -2780,6 +2789,10 @@ changes:
27802789
`'https:'`
27812790
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
27822791
remote peer upon connection.
2792+
* `remoteCustomSettings` {Array} The array of integer values determines the
2793+
settings types, which are included in the `CustomSettings`-property of the
2794+
received remoteSettings. Please see the `CustomSettings`-property of the
2795+
`Http2Settings` object for more information, on the allowed setting types.
27832796
* `createConnection` {Function} An optional callback that receives the `URL`
27842797
instance passed to `connect` and the `options` object, and returns any
27852798
[`Duplex`][] stream that is to be used as the connection for this session.
@@ -3022,9 +3035,11 @@ properties.
30223035
it should be greater than 6, although it is not an error.
30233036
The values need to be unsigned integers in the range from 0 to 2^32-1.
30243037
Currently, a maximum of up 10 custom settings is supported.
3025-
It is only supported for sending SETTINGS.
3026-
Custom settings are not supported for the functions retrieving remote and
3027-
local settings as nghttp2 does not pass unknown HTTP/2 settings to Node.js.
3038+
It is only supported for sending SETTINGS, or for receiving settings values
3039+
specified in the `remoteCustomSettings` options of the server or client
3040+
object. Do not mix the `customSettings`-mechanism for a settings id with
3041+
interfaces for the natively handled settings, in case a setting becomes
3042+
natively supported in a future node version.
30283043

30293044
All additional properties on the settings object are ignored.
30303045

lib/internal/http2/core.js

+32
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ const {
99
FunctionPrototypeBind,
1010
FunctionPrototypeCall,
1111
MathMin,
12+
Number,
1213
ObjectAssign,
1314
ObjectKeys,
1415
ObjectDefineProperty,
16+
ObjectEntries,
1517
ObjectPrototypeHasOwnProperty,
1618
Promise,
1719
PromisePrototypeThen,
@@ -105,6 +107,7 @@ const {
105107
ERR_HTTP2_STREAM_CANCEL,
106108
ERR_HTTP2_STREAM_ERROR,
107109
ERR_HTTP2_STREAM_SELF_DEPENDENCY,
110+
ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS,
108111
ERR_HTTP2_TRAILERS_ALREADY_SENT,
109112
ERR_HTTP2_TRAILERS_NOT_READY,
110113
ERR_HTTP2_UNSUPPORTED_PROTOCOL,
@@ -140,6 +143,7 @@ const {
140143

141144
const {
142145
assertIsObject,
146+
assertIsArray,
143147
assertValidPseudoHeader,
144148
assertValidPseudoHeaderResponse,
145149
assertValidPseudoHeaderTrailer,
@@ -155,7 +159,9 @@ const {
155159
kRequest,
156160
kProxySocket,
157161
mapToHeaders,
162+
MAX_ADDITIONAL_SETTINGS,
158163
NghttpError,
164+
remoteCustomSettingsToBuffer,
159165
sessionName,
160166
toHeaderObject,
161167
updateOptionsBuffer,
@@ -947,6 +953,15 @@ function pingCallback(cb) {
947953
const validateSettings = hideStackFrames((settings) => {
948954
if (settings === undefined) return;
949955
assertIsObject.withoutStackTrace(settings.customSettings, 'customSettings', 'Number');
956+
if (settings.customSettings) {
957+
const entries = ObjectEntries(settings.customSettings);
958+
if (entries.length > MAX_ADDITIONAL_SETTINGS)
959+
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
960+
for (const { 0: key, 1: value } of entries) {
961+
assertWithinRange.withoutStackTrace('customSettings:id', Number(key), 0, 0xffff);
962+
assertWithinRange.withoutStackTrace('customSettings:value', Number(value), 0, kMaxInt);
963+
}
964+
}
950965

951966
assertWithinRange.withoutStackTrace('headerTableSize',
952967
settings.headerTableSize,
@@ -1031,6 +1046,9 @@ function setupHandle(socket, type, options) {
10311046
this[kState].flags |= SESSION_FLAGS_READY;
10321047

10331048
updateOptionsBuffer(options);
1049+
if (options.remoteCustomSettings) {
1050+
remoteCustomSettingsToBuffer(options.remoteCustomSettings);
1051+
}
10341052
const handle = new binding.Http2Session(type);
10351053
handle[kOwner] = this;
10361054

@@ -3103,6 +3121,13 @@ function initializeOptions(options) {
31033121
assertIsObject(options.settings, 'options.settings');
31043122
options.settings = { ...options.settings };
31053123

3124+
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
3125+
if (options.remoteCustomSettings) {
3126+
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
3127+
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
3128+
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
3129+
}
3130+
31063131
if (options.maxSessionInvalidFrames !== undefined)
31073132
validateUint32(options.maxSessionInvalidFrames, 'maxSessionInvalidFrames');
31083133

@@ -3277,6 +3302,13 @@ function connect(authority, options, listener) {
32773302
assertIsObject(options, 'options');
32783303
options = { ...options };
32793304

3305+
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
3306+
if (options.remoteCustomSettings) {
3307+
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
3308+
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
3309+
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
3310+
}
3311+
32803312
if (typeof authority === 'string')
32813313
authority = new URL(authority);
32823314

lib/internal/http2/util.js

+60-6
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,19 @@ function updateOptionsBuffer(options) {
278278
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
279279
}
280280

281+
function addCustomSettingsToObj() {
282+
const toRet = {};
283+
const num = settingsBuffer[IDX_SETTINGS_FLAGS + 1];
284+
for (let i = 0; i < num; i++) {
285+
toRet[settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1].toString()] =
286+
Number(settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2]);
287+
}
288+
return toRet;
289+
}
290+
281291
function getDefaultSettings() {
282292
settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
293+
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = 0; // Length of custom settings
283294
binding.refreshDefaultSettings();
284295
const holder = { __proto__: null };
285296

@@ -327,6 +338,8 @@ function getDefaultSettings() {
327338
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] === 1;
328339
}
329340

341+
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) holder.customSettings = addCustomSettingsToObj();
342+
330343
return holder;
331344
}
332345

@@ -338,7 +351,7 @@ function getSettings(session, remote) {
338351
else
339352
session.localSettings();
340353

341-
return {
354+
const toRet = {
342355
headerTableSize: settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE],
343356
enablePush: !!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH],
344357
initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
@@ -349,6 +362,8 @@ function getSettings(session, remote) {
349362
enableConnectProtocol:
350363
!!settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL],
351364
};
365+
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) toRet.customSettings = addCustomSettingsToObj();
366+
return toRet;
352367
}
353368

354369
function updateSettingsBuffer(settings) {
@@ -415,12 +430,22 @@ function updateSettingsBuffer(settings) {
415430
}
416431
}
417432
if (!set) { // not supported
418-
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
419-
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
433+
let i = 0;
434+
while (i < numCustomSettings) {
435+
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1] === nsetting) {
436+
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2] = val;
437+
break;
438+
}
439+
i++;
440+
}
441+
if (i === numCustomSettings) {
442+
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
443+
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
420444

421-
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
422-
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] = val;
423-
numCustomSettings++;
445+
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
446+
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] = val;
447+
numCustomSettings++;
448+
}
424449
}
425450
}
426451
}
@@ -475,6 +500,24 @@ function updateSettingsBuffer(settings) {
475500
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
476501
}
477502

503+
function remoteCustomSettingsToBuffer(remoteCustomSettings) {
504+
if (remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
505+
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
506+
let numCustomSettings = 0;
507+
for (let i = 0; i < remoteCustomSettings.length; i++) {
508+
const nsetting = remoteCustomSettings[i];
509+
if (typeof nsetting === 'number' && nsetting <= 0xffff &&
510+
nsetting >= 0) {
511+
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
512+
numCustomSettings++;
513+
} else
514+
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
515+
'Range Error', nsetting, 0, 0xffff);
516+
517+
}
518+
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = numCustomSettings;
519+
}
520+
478521
function getSessionState(session) {
479522
session.refreshState();
480523
return {
@@ -649,6 +692,14 @@ const assertIsObject = hideStackFrames((value, name, types) => {
649692
}
650693
});
651694

695+
const assertIsArray = hideStackFrames((value, name, types) => {
696+
if (value !== undefined &&
697+
(value === null ||
698+
!ArrayIsArray(value))) {
699+
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(name, types || 'Array', value);
700+
}
701+
});
702+
652703
const assertWithinRange = hideStackFrames(
653704
(name, value, min = 0, max = Infinity) => {
654705
if (value !== undefined &&
@@ -732,6 +783,7 @@ function getAuthority(headers) {
732783

733784
module.exports = {
734785
assertIsObject,
786+
assertIsArray,
735787
assertValidPseudoHeader,
736788
assertValidPseudoHeaderResponse,
737789
assertValidPseudoHeaderTrailer,
@@ -747,7 +799,9 @@ module.exports = {
747799
kProxySocket,
748800
kRequest,
749801
mapToHeaders,
802+
MAX_ADDITIONAL_SETTINGS,
750803
NghttpError,
804+
remoteCustomSettingsToBuffer,
751805
sessionName,
752806
toHeaderObject,
753807
updateOptionsBuffer,

0 commit comments

Comments
 (0)