Compare commits

...

1 Commits

Author SHA1 Message Date
Meghan Denny
7cadcb55d2 node: fix test-http2-client-settings-before-connect.js 2025-09-03 00:27:27 -07:00
6 changed files with 193 additions and 40 deletions

View File

@@ -205,8 +205,8 @@ const FullSettingsPayload = packed struct(u288) {
result.put(globalObject, jsc.ZigString.static("maxHeaderListSize"), jsc.JSValue.jsNumber(this.maxHeaderListSize));
result.put(globalObject, jsc.ZigString.static("maxHeaderSize"), jsc.JSValue.jsNumber(this.maxHeaderListSize));
// TODO: we dont support this setting yet see https://nodejs.org/api/http2.html#settings-object
// we should also support customSettings
result.put(globalObject, jsc.ZigString.static("enableConnectProtocol"), .false);
// TODO: we should also support customSettings
return result;
}
@@ -331,7 +331,7 @@ pub fn jsAssertSettings(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallF
if (try options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (headerTableSize.isNumber()) {
const headerTableSizeValue = headerTableSize.toInt32();
const headerTableSizeValue = headerTableSize.toInt64();
if (headerTableSizeValue > MAX_HEADER_TABLE_SIZE or headerTableSizeValue < 0) {
return globalObject.throw("Expected headerTableSize to be a number between 0 and 2^32-1", .{});
}
@@ -348,7 +348,7 @@ pub fn jsAssertSettings(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallF
if (try options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (initialWindowSize.isNumber()) {
const initialWindowSizeValue = initialWindowSize.toInt32();
const initialWindowSizeValue = initialWindowSize.toInt64();
if (initialWindowSizeValue > MAX_HEADER_TABLE_SIZE or initialWindowSizeValue < 0) {
return globalObject.throw("Expected initialWindowSize to be a number between 0 and 2^32-1", .{});
}
@@ -359,7 +359,7 @@ pub fn jsAssertSettings(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallF
if (try options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (maxFrameSize.isNumber()) {
const maxFrameSizeValue = maxFrameSize.toInt32();
const maxFrameSizeValue = maxFrameSize.toInt64();
if (maxFrameSizeValue > MAX_FRAME_SIZE or maxFrameSizeValue < 16384) {
return globalObject.throw("Expected maxFrameSize to be a number between 16,384 and 2^24-1", .{});
}
@@ -370,7 +370,7 @@ pub fn jsAssertSettings(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallF
if (try options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (maxConcurrentStreams.isNumber()) {
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt32();
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt64();
if (maxConcurrentStreamsValue > MAX_HEADER_TABLE_SIZE or maxConcurrentStreamsValue < 0) {
return globalObject.throw("Expected maxConcurrentStreams to be a number between 0 and 2^32-1", .{});
}
@@ -381,7 +381,7 @@ pub fn jsAssertSettings(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallF
if (try options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (maxHeaderListSize.isNumber()) {
const maxHeaderListSizeValue = maxHeaderListSize.toInt32();
const maxHeaderListSizeValue = maxHeaderListSize.toInt64();
if (maxHeaderListSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderListSizeValue < 0) {
return globalObject.throw("Expected maxHeaderListSize to be a number between 0 and 2^32-1", .{});
}
@@ -392,7 +392,7 @@ pub fn jsAssertSettings(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallF
if (try options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (maxHeaderSize.isNumber()) {
const maxHeaderSizeValue = maxHeaderSize.toInt32();
const maxHeaderSizeValue = maxHeaderSize.toInt64();
if (maxHeaderSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderSizeValue < 0) {
return globalObject.throw("Expected maxHeaderSize to be a number between 0 and 2^32-1", .{});
}
@@ -2586,14 +2586,30 @@ pub const H2FrameParser = struct {
return globalObject.throw("Expected settings to be a object", .{});
}
if (try options.get(globalObject, "customSettings")) |customSettings| {
if (customSettings.isObject()) {
var piter = try bun.jsc.JSPropertyIterator(.{ .skip_empty_name = true, .include_value = true }).init(globalObject, customSettings.getObject().?);
var i: usize = 0;
while (try piter.next()) |_| : (i += 1) {
if (i >= 10) return globalObject.ERR(.HTTP2_TOO_MANY_CUSTOM_SETTINGS, "Number of custom settings exceeds MAX_ADDITIONAL_SETTINGS", .{}).throw();
const keyI = try piter.key.coerceToInt64(globalObject);
if (keyI < 0 or keyI > 0xffff)
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Invalid value for setting \"customSettings:id\": {d}", .{keyI}).throw();
const valueI = try piter.value.coerceToInt64(globalObject);
if (valueI < 0 or valueI > std.math.maxInt(u32))
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Invalid value for setting \"customSettings:value\": {d}", .{valueI}).throw();
}
}
}
if (try options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (headerTableSize.isNumber()) {
const headerTableSizeValue = headerTableSize.toInt32();
const headerTableSizeValue = headerTableSize.toInt64();
if (headerTableSizeValue > MAX_HEADER_TABLE_SIZE or headerTableSizeValue < 0) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected headerTableSize to be a number between 0 and 2^32-1", .{}).throw();
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Expected headerTableSize to be a number between 0 and 2^32-1", .{}).throw();
}
this.localSettings.headerTableSize = @intCast(headerTableSizeValue);
} else if (!headerTableSize.isEmptyOrUndefinedOrNull()) {
} else if (!headerTableSize.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected headerTableSize to be a number", .{}).throw();
}
}
@@ -2601,68 +2617,68 @@ pub const H2FrameParser = struct {
if (try options.get(globalObject, "enablePush")) |enablePush| {
if (enablePush.isBoolean()) {
this.localSettings.enablePush = if (enablePush.asBoolean()) 1 else 0;
} else if (!enablePush.isEmptyOrUndefinedOrNull()) {
} else if (!enablePush.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected enablePush to be a boolean", .{}).throw();
}
}
if (try options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (initialWindowSize.isNumber()) {
const initialWindowSizeValue = initialWindowSize.toInt32();
const initialWindowSizeValue = initialWindowSize.toInt64();
if (initialWindowSizeValue > MAX_WINDOW_SIZE or initialWindowSizeValue < 0) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected initialWindowSize to be a number between 0 and 2^32-1", .{}).throw();
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Expected initialWindowSize to be a number between 0 and 2^32-1", .{}).throw();
}
log("initialWindowSize: {d}", .{initialWindowSizeValue});
this.localSettings.initialWindowSize = @intCast(initialWindowSizeValue);
} else if (!initialWindowSize.isEmptyOrUndefinedOrNull()) {
} else if (!initialWindowSize.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected initialWindowSize to be a number", .{}).throw();
}
}
if (try options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (maxFrameSize.isNumber()) {
const maxFrameSizeValue = maxFrameSize.toInt32();
const maxFrameSizeValue = maxFrameSize.toInt64();
if (maxFrameSizeValue > MAX_FRAME_SIZE or maxFrameSizeValue < 16384) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxFrameSize to be a number between 16,384 and 2^24-1", .{}).throw();
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Expected maxFrameSize to be a number between 16,384 and 2^24-1", .{}).throw();
}
this.localSettings.maxFrameSize = @intCast(maxFrameSizeValue);
} else if (!maxFrameSize.isEmptyOrUndefinedOrNull()) {
} else if (!maxFrameSize.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxFrameSize to be a number", .{}).throw();
}
}
if (try options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (maxConcurrentStreams.isNumber()) {
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt32();
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt64();
if (maxConcurrentStreamsValue > MAX_HEADER_TABLE_SIZE or maxConcurrentStreamsValue < 0) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxConcurrentStreams to be a number between 0 and 2^32-1", .{}).throw();
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Expected maxConcurrentStreams to be a number between 0 and 2^32-1", .{}).throw();
}
this.localSettings.maxConcurrentStreams = @intCast(maxConcurrentStreamsValue);
} else if (!maxConcurrentStreams.isEmptyOrUndefinedOrNull()) {
} else if (!maxConcurrentStreams.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxConcurrentStreams to be a number", .{}).throw();
}
}
if (try options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (maxHeaderListSize.isNumber()) {
const maxHeaderListSizeValue = maxHeaderListSize.toInt32();
const maxHeaderListSizeValue = maxHeaderListSize.toInt64();
if (maxHeaderListSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderListSizeValue < 0) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxHeaderListSize to be a number between 0 and 2^32-1", .{}).throw();
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Expected maxHeaderListSize to be a number between 0 and 2^32-1", .{}).throw();
}
this.localSettings.maxHeaderListSize = @intCast(maxHeaderListSizeValue);
} else if (!maxHeaderListSize.isEmptyOrUndefinedOrNull()) {
} else if (!maxHeaderListSize.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxHeaderListSize to be a number", .{}).throw();
}
}
if (try options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (maxHeaderSize.isNumber()) {
const maxHeaderSizeValue = maxHeaderSize.toInt32();
const maxHeaderSizeValue = maxHeaderSize.toInt64();
if (maxHeaderSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderSizeValue < 0) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxHeaderSize to be a number between 0 and 2^32-1", .{}).throw();
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE_RangeError, "Expected maxHeaderSize to be a number between 0 and 2^32-1", .{}).throw();
}
this.localSettings.maxHeaderListSize = @intCast(maxHeaderSizeValue);
} else if (!maxHeaderSize.isEmptyOrUndefinedOrNull()) {
} else if (!maxHeaderSize.isEmptyOrUndefined()) {
return globalObject.ERR(.HTTP2_INVALID_SETTING_VALUE, "Expected maxHeaderSize to be a number", .{}).throw();
}
}

View File

@@ -128,7 +128,7 @@ static EncodedJSValue getOwnProxyObject(JSPropertyIterator* iter, JSObject* obje
return JSValue::encode(result);
}
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i)
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, JSValue* key, BunString* propertyName, size_t i)
{
auto& vm = iter->vm;
auto scope = DECLARE_THROW_SCOPE(vm);
@@ -149,11 +149,12 @@ extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIte
JSValue result = slot.getValue(globalObject, prop);
RETURN_IF_EXCEPTION(scope, {});
*key = jsString(vm, String(prop.impl()));
*propertyName = Bun::toString(prop.impl());
return JSValue::encode(result);
}
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValueNonObservable(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i)
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValueNonObservable(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, JSValue* key, BunString* propertyName, size_t i)
{
auto& vm = iter->vm;
auto scope = DECLARE_THROW_SCOPE(vm);
@@ -176,13 +177,16 @@ extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValueNonObservable(
JSValue result = slot.getPureResult();
RETURN_IF_EXCEPTION(scope, {});
*key = jsString(vm, String(prop.impl()));
*propertyName = Bun::toString(prop.impl());
return JSValue::encode(result);
}
extern "C" void Bun__JSPropertyIterator__getName(JSPropertyIterator* iter, BunString* propertyName, size_t i)
extern "C" void Bun__JSPropertyIterator__getName(JSPropertyIterator* iter, JSValue* key, BunString* propertyName, size_t i)
{
auto& vm = iter->vm;
const auto& prop = iter->properties->propertyNameVector()[i];
*key = jsString(vm, String(prop.impl()));
*propertyName = Bun::toString(prop.impl());
}

View File

@@ -17,6 +17,7 @@ pub fn JSPropertyIterator(comptime options: JSPropertyIteratorOptions) type {
globalObject: *jsc.JSGlobalObject,
object: *jsc.JSObject,
// current property being yielded
key: jsc.JSValue = .zero,
value: jsc.JSValue = .zero,
pub fn getLongestPropertyName(this: *@This()) usize {
@@ -78,13 +79,13 @@ pub fn JSPropertyIterator(comptime options: JSPropertyIteratorOptions) type {
var name = bun.String.dead;
if (comptime options.include_value) {
const FnToUse = if (options.observable) JSPropertyIteratorImpl.getNameAndValue else JSPropertyIteratorImpl.getNameAndValueNonObservable;
const current: jsc.JSValue = try FnToUse(this.impl.?, this.globalObject, this.object, &name, i);
const current: jsc.JSValue = try FnToUse(this.impl.?, this.globalObject, this.object, &this.key, &name, i);
if (current == .zero) continue;
current.ensureStillAlive();
this.value = current;
} else {
// Exception check is unnecessary here because it won't throw.
this.impl.?.getName(&name, i);
this.impl.?.getName(&this.key, &name, i);
}
if (name.tag == .Dead) {
@@ -118,20 +119,20 @@ const JSPropertyIteratorImpl = opaque {
pub const deinit = Bun__JSPropertyIterator__deinit;
pub fn getNameAndValue(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, propertyName: *bun.String, i: usize) bun.JSError!jsc.JSValue {
pub fn getNameAndValue(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, key: *jsc.JSValue, propertyName: *bun.String, i: usize) bun.JSError!jsc.JSValue {
var scope: bun.jsc.CatchScope = undefined;
scope.init(globalObject, @src());
defer scope.deinit();
const value = Bun__JSPropertyIterator__getNameAndValue(iter, globalObject, object, propertyName, i);
const value = Bun__JSPropertyIterator__getNameAndValue(iter, globalObject, object, key, propertyName, i);
try scope.returnIfException();
return value;
}
pub fn getNameAndValueNonObservable(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, propertyName: *bun.String, i: usize) bun.JSError!jsc.JSValue {
pub fn getNameAndValueNonObservable(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, key: *jsc.JSValue, propertyName: *bun.String, i: usize) bun.JSError!jsc.JSValue {
var scope: bun.jsc.CatchScope = undefined;
scope.init(globalObject, @src());
defer scope.deinit();
const value = Bun__JSPropertyIterator__getNameAndValueNonObservable(iter, globalObject, object, propertyName, i);
const value = Bun__JSPropertyIterator__getNameAndValueNonObservable(iter, globalObject, object, key, propertyName, i);
try scope.returnIfException();
return value;
}
@@ -142,9 +143,9 @@ const JSPropertyIteratorImpl = opaque {
/// may return null without an exception
extern "c" fn Bun__JSPropertyIterator__create(globalObject: *jsc.JSGlobalObject, encodedValue: jsc.JSValue, count: *usize, own_properties_only: bool, only_non_index_properties: bool) ?*JSPropertyIteratorImpl;
extern "c" fn Bun__JSPropertyIterator__getNameAndValue(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, propertyName: *bun.String, i: usize) jsc.JSValue;
extern "c" fn Bun__JSPropertyIterator__getNameAndValueNonObservable(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, propertyName: *bun.String, i: usize) jsc.JSValue;
extern "c" fn Bun__JSPropertyIterator__getName(iter: *JSPropertyIteratorImpl, propertyName: *bun.String, i: usize) void;
extern "c" fn Bun__JSPropertyIterator__getNameAndValue(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, key: *jsc.JSValue, propertyName: *bun.String, i: usize) jsc.JSValue;
extern "c" fn Bun__JSPropertyIterator__getNameAndValueNonObservable(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject, key: *jsc.JSValue, propertyName: *bun.String, i: usize) jsc.JSValue;
extern "c" fn Bun__JSPropertyIterator__getName(iter: *JSPropertyIteratorImpl, key: *jsc.JSValue, propertyName: *bun.String, i: usize) void;
extern "c" fn Bun__JSPropertyIterator__deinit(iter: *JSPropertyIteratorImpl) void;
extern "c" fn Bun__JSPropertyIterator__getLongestPropertyName(iter: *JSPropertyIteratorImpl, globalObject: *jsc.JSGlobalObject, object: *jsc.JSObject) usize;
};

View File

@@ -859,6 +859,12 @@ pub const JSValue = enum(i64) {
pub inline fn isNull(this: JSValue) bool {
return this == .null;
}
pub inline fn isEmptyOrUndefined(this: JSValue) bool {
return switch (this) {
.zero, .js_undefined => true,
else => false,
};
}
pub inline fn isEmptyOrUndefinedOrNull(this: JSValue) bool {
return switch (@intFromEnum(this)) {
0, 0xa, 0x2 => true,

View File

@@ -2974,8 +2974,11 @@ class ServerHttp2Session extends Http2Session {
settings(settings: Settings, callback) {
this.#pendingSettingsAck = true;
if (callback) {
validateFunction(callback, "callback");
}
this.#parser?.settings(settings);
if (typeof callback === "function") {
if (callback) {
const start = Date.now();
this.once("localSettings", () => {
callback(null, this.#localSettings, Date.now() - start);
@@ -3412,8 +3415,11 @@ class ClientHttp2Session extends Http2Session {
settings(settings: Settings, callback) {
this.#pendingSettingsAck = true;
if (callback) {
validateFunction(callback, "callback");
}
this.#parser?.settings(settings);
if (typeof callback === "function") {
if (callback) {
const start = Date.now();
this.once("localSettings", () => {
callback(null, this.#localSettings, Date.now() - start);

View File

@@ -0,0 +1,120 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// We use the lower-level API here
server.on('stream', common.mustCall((stream, headers, flags) => {
stream.respond();
stream.end('ok');
}));
server.on('session', common.mustCall((session) => {
session.on('remoteSettings', common.mustCall(2));
}));
server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
[
['headerTableSize', -1, RangeError],
['headerTableSize', 2 ** 32, RangeError],
['initialWindowSize', -1, RangeError],
['initialWindowSize', 2 ** 32, RangeError],
['maxFrameSize', 1, RangeError],
['maxFrameSize', 2 ** 24, RangeError],
['maxConcurrentStreams', -1, RangeError],
['maxConcurrentStreams', 2 ** 32, RangeError],
['maxHeaderListSize', -1, RangeError],
['maxHeaderListSize', 2 ** 32, RangeError],
['maxHeaderSize', -1, RangeError],
['maxHeaderSize', 2 ** 32, RangeError],
['enablePush', 'a', TypeError],
['enablePush', 1, TypeError],
['enablePush', 0, TypeError],
['enablePush', null, TypeError],
['enablePush', {}, TypeError],
].forEach(([name, value, errorType]) => {
assert.throws(
() => client.settings({ [name]: value }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: errorType.name
}
)
});
assert.throws(
() => client.settings({ customSettings: {
0x11: 5,
0x12: 5,
0x13: 5,
0x14: 5,
0x15: 5,
0x16: 5,
0x17: 5,
0x18: 5,
0x19: 5,
0x1A: 5, // more than 10
0x1B: 5
} }),
{
code: 'ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS',
name: 'Error'
}
);
assert.throws(
() => client.settings({ customSettings: {
0x10000: 5,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
assert.throws(
() => client.settings({ customSettings: {
0x55: 0x100000000,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
assert.throws(
() => client.settings({ customSettings: {
0x55: -1,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
[1, true, {}, []].forEach((invalidCallback) => {
assert.throws(
() => client.settings({}, invalidCallback),
{
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
}
)
});
client.settings({ maxFrameSize: 1234567, customSettings: { 0xbf: 12 } });
const req = client.request();
req.on('response', common.mustCall());
req.resume();
req.on('close', common.mustCall(() => {
server.close();
client.close();
}));
}));