Skip to content

Commit ad3d899

Browse files
committed
http2: improve http2 coverage
Improve http2 coverage through refactoring and tests PR-URL: nodejs#15210 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent dcc41fd commit ad3d899

6 files changed

Lines changed: 196 additions & 68 deletions

File tree

lib/internal/http2/core.js

Lines changed: 41 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const {
3737
isPayloadMeaningless,
3838
mapToHeaders,
3939
NghttpError,
40+
sessionName,
4041
toHeaderObject,
4142
updateOptionsBuffer,
4243
updateSettingsBuffer
@@ -87,7 +88,6 @@ const {
8788
NGHTTP2_CANCEL,
8889
NGHTTP2_DEFAULT_WEIGHT,
8990
NGHTTP2_FLAG_END_STREAM,
90-
NGHTTP2_HCAT_HEADERS,
9191
NGHTTP2_HCAT_PUSH_RESPONSE,
9292
NGHTTP2_HCAT_RESPONSE,
9393
NGHTTP2_INTERNAL_ERROR,
@@ -128,17 +128,6 @@ const {
128128
HTTP_STATUS_SWITCHING_PROTOCOLS
129129
} = constants;
130130

131-
function sessionName(type) {
132-
switch (type) {
133-
case NGHTTP2_SESSION_CLIENT:
134-
return 'client';
135-
case NGHTTP2_SESSION_SERVER:
136-
return 'server';
137-
default:
138-
return '<invalid>';
139-
}
140-
}
141-
142131
// Top level to avoid creating a closure
143132
function emit() {
144133
this.emit.apply(this, arguments);
@@ -163,59 +152,43 @@ function onSessionHeaders(id, cat, flags, headers) {
163152
const obj = toHeaderObject(headers);
164153

165154
if (stream === undefined) {
166-
switch (owner[kType]) {
167-
case NGHTTP2_SESSION_SERVER:
168-
stream = new ServerHttp2Stream(owner, id,
169-
{ readable: !endOfStream },
170-
obj);
171-
if (obj[HTTP2_HEADER_METHOD] === HTTP2_METHOD_HEAD) {
172-
// For head requests, there must not be a body...
173-
// end the writable side immediately.
174-
stream.end();
175-
const state = stream[kState];
176-
state.headRequest = true;
177-
}
178-
break;
179-
case NGHTTP2_SESSION_CLIENT:
180-
stream = new ClientHttp2Stream(owner, id, { readable: !endOfStream });
181-
break;
182-
default:
183-
assert.fail(null, null,
184-
'Internal HTTP/2 Error. Invalid session type. Please ' +
185-
'report this as a bug in Node.js');
155+
// owner[kType] can be only one of two possible values
156+
if (owner[kType] === NGHTTP2_SESSION_SERVER) {
157+
stream = new ServerHttp2Stream(owner, id,
158+
{ readable: !endOfStream },
159+
obj);
160+
if (obj[HTTP2_HEADER_METHOD] === HTTP2_METHOD_HEAD) {
161+
// For head requests, there must not be a body...
162+
// end the writable side immediately.
163+
stream.end();
164+
const state = stream[kState];
165+
state.headRequest = true;
166+
}
167+
} else {
168+
stream = new ClientHttp2Stream(owner, id, { readable: !endOfStream });
186169
}
187170
streams.set(id, stream);
188171
process.nextTick(emit.bind(owner, 'stream', stream, obj, flags, headers));
189172
} else {
190173
let event;
191-
let status;
192-
switch (cat) {
193-
case NGHTTP2_HCAT_RESPONSE:
194-
status = obj[HTTP2_HEADER_STATUS];
195-
if (!endOfStream &&
196-
status !== undefined &&
197-
status >= 100 &&
198-
status < 200) {
199-
event = 'headers';
200-
} else {
201-
event = 'response';
202-
}
203-
break;
204-
case NGHTTP2_HCAT_PUSH_RESPONSE:
205-
event = 'push';
206-
break;
207-
case NGHTTP2_HCAT_HEADERS:
208-
status = obj[HTTP2_HEADER_STATUS];
209-
if (!endOfStream && status !== undefined && status >= 200) {
210-
event = 'response';
211-
} else {
212-
event = endOfStream ? 'trailers' : 'headers';
213-
}
214-
break;
215-
default:
216-
assert.fail(null, null,
217-
'Internal HTTP/2 Error. Invalid headers category. Please ' +
218-
'report this as a bug in Node.js');
174+
const status = obj[HTTP2_HEADER_STATUS];
175+
if (cat === NGHTTP2_HCAT_RESPONSE) {
176+
if (!endOfStream &&
177+
status !== undefined &&
178+
status >= 100 &&
179+
status < 200) {
180+
event = 'headers';
181+
} else {
182+
event = 'response';
183+
}
184+
} else if (cat === NGHTTP2_HCAT_PUSH_RESPONSE) {
185+
event = 'push';
186+
} else { // cat === NGHTTP2_HCAT_HEADERS:
187+
if (!endOfStream && status !== undefined && status >= 200) {
188+
event = 'response';
189+
} else {
190+
event = endOfStream ? 'trailers' : 'headers';
191+
}
219192
}
220193
debug(`[${sessionName(owner[kType])}] emitting stream '${event}' event`);
221194
process.nextTick(emit.bind(stream, event, obj, flags, headers));
@@ -242,8 +215,10 @@ function onSessionTrailers(id) {
242215
'Internal HTTP/2 Failure. Stream does not exist. Please ' +
243216
'report this as a bug in Node.js');
244217
const getTrailers = stream[kState].getTrailers;
245-
if (typeof getTrailers !== 'function')
246-
return [];
218+
// We should not have been able to get here at all if getTrailers
219+
// was not a function, and there's no code that could have changed
220+
// it between there and here.
221+
assert.strictEqual(typeof getTrailers, 'function');
247222
const trailers = Object.create(null);
248223
getTrailers.call(stream, trailers);
249224
const headersList = mapToHeaders(trailers, assertValidPseudoHeaderTrailer);
@@ -2480,9 +2455,10 @@ function connect(authority, options, listener) {
24802455
}
24812456

24822457
function createSecureServer(options, handler) {
2483-
if (typeof options === 'function') {
2484-
handler = options;
2485-
options = Object.create(null);
2458+
if (options == null || typeof options !== 'object') {
2459+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
2460+
'options',
2461+
'object');
24862462
}
24872463
debug('creating http2secureserver');
24882464
return new Http2SecureServer(options, handler);

lib/internal/http2/util.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const binding = process.binding('http2');
44
const errors = require('internal/errors');
55

66
const {
7+
NGHTTP2_SESSION_CLIENT,
8+
NGHTTP2_SESSION_SERVER,
9+
710
HTTP2_HEADER_STATUS,
811
HTTP2_HEADER_METHOD,
912
HTTP2_HEADER_AUTHORITY,
@@ -439,13 +442,12 @@ class NghttpError extends Error {
439442
}
440443
}
441444

442-
function assertIsObject(value, name, types) {
445+
function assertIsObject(value, name, types = 'object') {
443446
if (value !== undefined &&
444447
(value === null ||
445448
typeof value !== 'object' ||
446449
Array.isArray(value))) {
447-
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE',
448-
name, types || 'object');
450+
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE', name, types);
449451
Error.captureStackTrace(err, assertIsObject);
450452
throw err;
451453
}
@@ -515,6 +517,17 @@ function isPayloadMeaningless(method) {
515517
return kNoPayloadMethods.has(method);
516518
}
517519

520+
function sessionName(type) {
521+
switch (type) {
522+
case NGHTTP2_SESSION_CLIENT:
523+
return 'client';
524+
case NGHTTP2_SESSION_SERVER:
525+
return 'server';
526+
default:
527+
return '<invalid>';
528+
}
529+
}
530+
518531
module.exports = {
519532
assertIsObject,
520533
assertValidPseudoHeaderResponse,
@@ -527,6 +540,7 @@ module.exports = {
527540
isPayloadMeaningless,
528541
mapToHeaders,
529542
NghttpError,
543+
sessionName,
530544
toHeaderObject,
531545
updateOptionsBuffer,
532546
updateSettingsBuffer

src/node_http2.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,8 @@ void Http2Session::OnHeaders(Nghttp2Stream* stream,
842842
Local<Function> fn = env()->push_values_to_array_function();
843843
Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX * 2];
844844

845+
CHECK_LE(cat, NGHTTP2_HCAT_HEADERS);
846+
845847
// The headers are passed in above as a linked list of nghttp2_header_list
846848
// structs. The following converts that into a JS array with the structure:
847849
// [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Flags: --expose-http2
2+
'use strict';
3+
4+
const common = require('../common');
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const http2 = require('http2');
9+
10+
// Error if options are not passed to createSecureServer
11+
12+
common.expectsError(
13+
() => http2.createSecureServer(),
14+
{
15+
code: 'ERR_INVALID_ARG_TYPE',
16+
type: TypeError
17+
});
18+
19+
common.expectsError(
20+
() => http2.createSecureServer(() => {}),
21+
{
22+
code: 'ERR_INVALID_ARG_TYPE',
23+
type: TypeError
24+
});
25+
26+
common.expectsError(
27+
() => http2.createSecureServer(1),
28+
{
29+
code: 'ERR_INVALID_ARG_TYPE',
30+
type: TypeError
31+
});
32+
33+
common.expectsError(
34+
() => http2.createSecureServer('test'),
35+
{
36+
code: 'ERR_INVALID_ARG_TYPE',
37+
type: TypeError
38+
});
39+
40+
common.expectsError(
41+
() => http2.createSecureServer(null),
42+
{
43+
code: 'ERR_INVALID_ARG_TYPE',
44+
type: TypeError
45+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
const common = require('../common');
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
const assert = require('assert');
8+
9+
const {
10+
assertIsObject,
11+
assertWithinRange,
12+
sessionName
13+
} = require('internal/http2/util');
14+
15+
// Code coverage for sessionName utility function
16+
assert.strictEqual(sessionName(0), 'server');
17+
assert.strictEqual(sessionName(1), 'client');
18+
[2, '', 'test', {}, [], true].forEach((i) => {
19+
assert.strictEqual(sessionName(2), '<invalid>');
20+
});
21+
22+
// Code coverage for assertWithinRange function
23+
common.expectsError(
24+
() => assertWithinRange('test', -1),
25+
{
26+
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
27+
type: RangeError,
28+
message: 'Invalid value for setting "test": -1'
29+
});
30+
31+
assertWithinRange('test', 1);
32+
33+
common.expectsError(
34+
() => assertIsObject('foo', 'test'),
35+
{
36+
code: 'ERR_INVALID_ARG_TYPE',
37+
type: TypeError,
38+
message: 'The "test" argument must be of type object'
39+
});
40+
41+
common.expectsError(
42+
() => assertIsObject('foo', 'test', ['Date']),
43+
{
44+
code: 'ERR_INVALID_ARG_TYPE',
45+
type: TypeError,
46+
message: 'The "test" argument must be of type Date'
47+
});
48+
49+
assertIsObject({}, 'test');
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Flags: --expose-http2
2+
'use strict';
3+
4+
const common = require('../common');
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const http2 = require('http2');
9+
10+
// Verify that setTimeout callback verifications work correctly
11+
12+
{
13+
const server = http2.createServer();
14+
common.expectsError(
15+
() => server.setTimeout(10, 'test'),
16+
{
17+
code: 'ERR_INVALID_CALLBACK',
18+
type: TypeError
19+
});
20+
common.expectsError(
21+
() => server.setTimeout(10, 1),
22+
{
23+
code: 'ERR_INVALID_CALLBACK',
24+
type: TypeError
25+
});
26+
}
27+
28+
{
29+
const server = http2.createSecureServer({});
30+
common.expectsError(
31+
() => server.setTimeout(10, 'test'),
32+
{
33+
code: 'ERR_INVALID_CALLBACK',
34+
type: TypeError
35+
});
36+
common.expectsError(
37+
() => server.setTimeout(10, 1),
38+
{
39+
code: 'ERR_INVALID_CALLBACK',
40+
type: TypeError
41+
});
42+
}

0 commit comments

Comments
 (0)