Skip to content

Commit c3eeb28

Browse files
trivikrmcollina
authored andcommitted
test: http2 client settings errors
Refs: #14985 PR-URL: #16096 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent d64e94b commit c3eeb28

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
@@ -8,110 +8,131 @@ const h2 = require('http2');
88

99
const server = h2.createServer();
1010

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

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

0 commit comments

Comments
 (0)