Skip to content

Commit 50b4965

Browse files
trivikrMylesBorins
authored andcommitted
test: http2 client settings errors
Refs: #14985 PR-URL: #16096 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 327be77 commit 50b4965

File tree

2 files changed

+209
-104
lines changed

2 files changed

+209
-104
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
const {
4+
constants,
5+
Http2Session,
6+
nghttp2ErrorString
7+
} = process.binding('http2');
8+
const common = require('../common');
9+
if (!common.hasCrypto)
10+
common.skip('missing crypto');
11+
const http2 = require('http2');
12+
13+
// tests error handling within requestOnConnect
14+
// - NGHTTP2_ERR_NOMEM (should emit session error)
15+
// - every other NGHTTP2 error from binding (should emit session error)
16+
17+
const specificTestKeys = [
18+
'NGHTTP2_ERR_NOMEM'
19+
];
20+
21+
const specificTests = [
22+
{
23+
ngError: constants.NGHTTP2_ERR_NOMEM,
24+
error: {
25+
code: 'ERR_OUTOFMEMORY',
26+
type: Error,
27+
message: 'Out of memory'
28+
}
29+
}
30+
];
31+
32+
const genericTests = Object.getOwnPropertyNames(constants)
33+
.filter((key) => (
34+
key.indexOf('NGHTTP2_ERR') === 0 && specificTestKeys.indexOf(key) < 0
35+
))
36+
.map((key) => ({
37+
ngError: constants[key],
38+
error: {
39+
code: 'ERR_HTTP2_ERROR',
40+
type: Error,
41+
message: nghttp2ErrorString(constants[key])
42+
}
43+
}));
44+
45+
const tests = specificTests.concat(genericTests);
46+
47+
const server = http2.createServer(common.mustNotCall());
48+
server.on('sessionError', () => {}); // not being tested
49+
50+
server.listen(0, common.mustCall(() => runTest(tests.shift())));
51+
52+
function runTest(test) {
53+
// mock submitSettings because we only care about testing error handling
54+
Http2Session.prototype.submitSettings = () => test.ngError;
55+
56+
const errorMustCall = common.expectsError(test.error);
57+
const errorMustNotCall = common.mustNotCall(
58+
`${test.error.code} should emit on session`
59+
);
60+
61+
const url = `http://localhost:${server.address().port}`;
62+
63+
const client = http2.connect(url, {
64+
settings: {
65+
maxHeaderListSize: 1
66+
}
67+
});
68+
69+
const req = client.request();
70+
req.resume();
71+
req.end();
72+
73+
client.on('error', errorMustCall);
74+
req.on('error', errorMustNotCall);
75+
76+
req.on('end', common.mustCall(() => {
77+
client.destroy();
78+
if (!tests.length) {
79+
server.close();
80+
} else {
81+
runTest(tests.shift());
82+
}
83+
}));
84+
}

test/parallel/test-http2-session-settings.js

Lines changed: 125 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -9,110 +9,131 @@ const h2 = require('http2');
99

1010
const server = h2.createServer();
1111

12-
server.on('stream', common.mustCall(onStream));
13-
14-
function assertSettings(settings) {
15-
assert.strictEqual(typeof settings, 'object');
16-
assert.strictEqual(typeof settings.headerTableSize, 'number');
17-
assert.strictEqual(typeof settings.enablePush, 'boolean');
18-
assert.strictEqual(typeof settings.initialWindowSize, 'number');
19-
assert.strictEqual(typeof settings.maxFrameSize, 'number');
20-
assert.strictEqual(typeof settings.maxConcurrentStreams, 'number');
21-
assert.strictEqual(typeof settings.maxHeaderListSize, 'number');
22-
}
23-
24-
function onStream(stream, headers, flags) {
25-
26-
const localSettings = stream.session.localSettings;
27-
const remoteSettings = stream.session.remoteSettings;
28-
assertSettings(localSettings);
29-
assertSettings(remoteSettings);
30-
31-
// Test that stored settings are returned when called for second time
32-
assert.strictEqual(stream.session.localSettings, localSettings);
33-
assert.strictEqual(stream.session.remoteSettings, remoteSettings);
34-
35-
stream.respond({
36-
'content-type': 'text/html',
37-
':status': 200
38-
});
39-
stream.end('hello world');
40-
}
41-
42-
server.listen(0);
43-
44-
server.on('listening', common.mustCall(() => {
45-
46-
const client = h2.connect(`http://localhost:${server.address().port}`, {
47-
settings: {
48-
enablePush: false,
49-
initialWindowSize: 123456
50-
}
51-
});
52-
53-
client.on('localSettings', common.mustCall((settings) => {
54-
assert(settings);
55-
assert.strictEqual(settings.enablePush, false);
56-
assert.strictEqual(settings.initialWindowSize, 123456);
57-
assert.strictEqual(settings.maxFrameSize, 16384);
58-
}, 2));
59-
client.on('remoteSettings', common.mustCall((settings) => {
60-
assert(settings);
61-
}));
62-
63-
const headers = { ':path': '/' };
64-
65-
const req = client.request(headers);
66-
67-
req.on('connect', common.mustCall(() => {
68-
// pendingSettingsAck will be true if a SETTINGS frame
69-
// has been sent but we are still waiting for an acknowledgement
70-
assert(client.pendingSettingsAck);
71-
}));
72-
73-
// State will only be valid after connect event is emitted
74-
req.on('ready', common.mustCall(() => {
75-
assert.doesNotThrow(() => {
76-
client.settings({
77-
maxHeaderListSize: 1
78-
});
12+
server.on(
13+
'stream',
14+
common.mustCall((stream) => {
15+
const assertSettings = (settings) => {
16+
assert.strictEqual(typeof settings, 'object');
17+
assert.strictEqual(typeof settings.headerTableSize, 'number');
18+
assert.strictEqual(typeof settings.enablePush, 'boolean');
19+
assert.strictEqual(typeof settings.initialWindowSize, 'number');
20+
assert.strictEqual(typeof settings.maxFrameSize, 'number');
21+
assert.strictEqual(typeof settings.maxConcurrentStreams, 'number');
22+
assert.strictEqual(typeof settings.maxHeaderListSize, 'number');
23+
};
24+
25+
const localSettings = stream.session.localSettings;
26+
const remoteSettings = stream.session.remoteSettings;
27+
assertSettings(localSettings);
28+
assertSettings(remoteSettings);
29+
30+
// Test that stored settings are returned when called for second time
31+
assert.strictEqual(stream.session.localSettings, localSettings);
32+
assert.strictEqual(stream.session.remoteSettings, remoteSettings);
33+
34+
stream.respond({
35+
'content-type': 'text/html',
36+
':status': 200
7937
});
80-
81-
// Verify valid error ranges
82-
[
83-
['headerTableSize', -1],
84-
['headerTableSize', 2 ** 32],
85-
['initialWindowSize', -1],
86-
['initialWindowSize', 2 ** 32],
87-
['maxFrameSize', 16383],
88-
['maxFrameSize', 2 ** 24],
89-
['maxHeaderListSize', -1],
90-
['maxHeaderListSize', 2 ** 32]
91-
].forEach((i) => {
92-
const settings = {};
93-
settings[i[0]] = i[1];
94-
assert.throws(() => client.settings(settings),
95-
common.expectsError({
96-
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
97-
type: RangeError
98-
}));
99-
});
100-
[1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
101-
assert.throws(() => client.settings({ enablePush: i }),
102-
common.expectsError({
103-
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
104-
type: TypeError
105-
}));
38+
stream.end('hello world');
39+
})
40+
);
41+
42+
server.listen(
43+
0,
44+
common.mustCall(() => {
45+
const client = h2.connect(`http://localhost:${server.address().port}`, {
46+
settings: {
47+
enablePush: false,
48+
initialWindowSize: 123456
49+
}
10650
});
10751

108-
}));
109-
110-
req.on('response', common.mustCall());
111-
req.resume();
112-
req.on('end', common.mustCall(() => {
113-
server.close();
114-
client.destroy();
115-
}));
116-
req.end();
117-
118-
}));
52+
client.on(
53+
'localSettings',
54+
common.mustCall((settings) => {
55+
assert(settings);
56+
assert.strictEqual(settings.enablePush, false);
57+
assert.strictEqual(settings.initialWindowSize, 123456);
58+
assert.strictEqual(settings.maxFrameSize, 16384);
59+
}, 2)
60+
);
61+
client.on(
62+
'remoteSettings',
63+
common.mustCall((settings) => {
64+
assert(settings);
65+
})
66+
);
67+
68+
const headers = { ':path': '/' };
69+
70+
const req = client.request(headers);
71+
72+
req.on(
73+
'connect',
74+
common.mustCall(() => {
75+
// pendingSettingsAck will be true if a SETTINGS frame
76+
// has been sent but we are still waiting for an acknowledgement
77+
assert(client.pendingSettingsAck);
78+
})
79+
);
80+
81+
// State will only be valid after connect event is emitted
82+
req.on(
83+
'ready',
84+
common.mustCall(() => {
85+
assert.doesNotThrow(() => {
86+
client.settings({
87+
maxHeaderListSize: 1
88+
});
89+
});
90+
91+
// Verify valid error ranges
92+
[
93+
['headerTableSize', -1],
94+
['headerTableSize', 2 ** 32],
95+
['initialWindowSize', -1],
96+
['initialWindowSize', 2 ** 32],
97+
['maxFrameSize', 16383],
98+
['maxFrameSize', 2 ** 24],
99+
['maxHeaderListSize', -1],
100+
['maxHeaderListSize', 2 ** 32]
101+
].forEach((i) => {
102+
const settings = {};
103+
settings[i[0]] = i[1];
104+
common.expectsError(
105+
() => client.settings(settings),
106+
{
107+
type: RangeError,
108+
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
109+
message: `Invalid value for setting "${i[0]}": ${i[1]}`
110+
}
111+
);
112+
});
113+
114+
// error checks for enablePush
115+
[1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
116+
common.expectsError(
117+
() => client.settings({ enablePush: i }),
118+
{
119+
type: TypeError,
120+
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
121+
message: `Invalid value for setting "enablePush": ${i}`
122+
}
123+
);
124+
});
125+
})
126+
);
127+
128+
req.on('response', common.mustCall());
129+
req.resume();
130+
req.on(
131+
'end',
132+
common.mustCall(() => {
133+
server.close();
134+
client.destroy();
135+
})
136+
);
137+
req.end();
138+
})
139+
);

0 commit comments

Comments
 (0)