Skip to content

Commit 83d8ad3

Browse files
trivikrMylesBorins
authored andcommitted
test: http2 stream.respond() error checks
Backport-PR-URL: #19579 Backport-PR-URL: #20456 PR-URL: #18861 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent f59eab0 commit 83d8ad3

File tree

2 files changed

+165
-78
lines changed

2 files changed

+165
-78
lines changed

test/parallel/test-http2-respond-errors.js

+66-78
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,81 @@ const common = require('../common');
55
if (!common.hasCrypto)
66
common.skip('missing crypto');
77
const http2 = require('http2');
8-
const {
9-
constants,
10-
Http2Stream,
11-
nghttp2ErrorString
12-
} = process.binding('http2');
8+
const { Http2Stream } = process.binding('http2');
9+
10+
const types = {
11+
boolean: true,
12+
function: () => {},
13+
number: 1,
14+
object: {},
15+
array: [],
16+
null: null,
17+
symbol: Symbol('test')
18+
};
1319

14-
// tests error handling within respond
15-
// - every other NGHTTP2 error from binding (should emit stream error)
20+
const server = http2.createServer();
1621

17-
const specificTestKeys = [];
22+
Http2Stream.prototype.respond = () => 1;
23+
server.on('stream', common.mustCall((stream) => {
1824

19-
const specificTests = [];
25+
// Check for all possible TypeError triggers on options.getTrailers
26+
Object.entries(types).forEach(([type, value]) => {
27+
if (type === 'function') {
28+
return;
29+
}
2030

21-
const genericTests = Object.getOwnPropertyNames(constants)
22-
.filter((key) => (
23-
key.indexOf('NGHTTP2_ERR') === 0 && specificTestKeys.indexOf(key) < 0
24-
))
25-
.map((key) => ({
26-
ngError: constants[key],
27-
error: {
28-
code: 'ERR_HTTP2_ERROR',
31+
common.expectsError(
32+
() => stream.respond({
33+
'content-type': 'text/plain'
34+
}, {
35+
['getTrailers']: value
36+
}),
37+
{
38+
type: TypeError,
39+
code: 'ERR_INVALID_OPT_VALUE',
40+
message: `The value "${String(value)}" is invalid ` +
41+
'for option "getTrailers"'
42+
}
43+
);
44+
});
45+
46+
// Send headers
47+
stream.respond({
48+
'content-type': 'text/plain'
49+
}, {
50+
['getTrailers']: () => common.mustCall()
51+
});
52+
53+
// Should throw if headers already sent
54+
common.expectsError(
55+
() => stream.respond(),
56+
{
2957
type: Error,
30-
message: nghttp2ErrorString(constants[key])
31-
},
32-
type: 'stream'
33-
}));
34-
35-
36-
const tests = specificTests.concat(genericTests);
37-
38-
let currentError;
39-
40-
// mock submitResponse because we only care about testing error handling
41-
Http2Stream.prototype.respond = () => currentError.ngError;
42-
43-
const server = http2.createServer();
44-
server.on('stream', common.mustCall((stream, headers) => {
45-
const errorMustCall = common.expectsError(currentError.error);
46-
const errorMustNotCall = common.mustNotCall(
47-
`${currentError.error.code} should emit on ${currentError.type}`
58+
code: 'ERR_HTTP2_HEADERS_SENT',
59+
message: 'Response has already been initiated.'
60+
}
4861
);
4962

50-
if (currentError.type === 'stream') {
51-
stream.session.on('error', errorMustNotCall);
52-
stream.on('error', errorMustCall);
53-
stream.on('error', common.mustCall(() => {
54-
stream.destroy();
55-
}));
56-
} else {
57-
stream.session.once('error', errorMustCall);
58-
stream.on('error', errorMustNotCall);
59-
}
60-
61-
stream.respond();
62-
}, tests.length));
63-
64-
server.listen(0, common.mustCall(() => runTest(tests.shift())));
65-
66-
function runTest(test) {
67-
const port = server.address().port;
68-
const url = `http://localhost:${port}`;
69-
const headers = {
70-
':path': '/',
71-
':method': 'POST',
72-
':scheme': 'http',
73-
':authority': `localhost:${port}`
74-
};
75-
76-
const client = http2.connect(url);
77-
const req = client.request(headers);
78-
req.on('error', common.expectsError({
79-
code: 'ERR_HTTP2_STREAM_ERROR',
80-
type: Error,
81-
message: 'Stream closed with error code 2'
82-
}));
63+
// Should throw if stream already destroyed
64+
stream.destroy();
65+
common.expectsError(
66+
() => stream.respond(),
67+
{
68+
type: Error,
69+
code: 'ERR_HTTP2_INVALID_STREAM',
70+
message: 'The stream has been destroyed'
71+
}
72+
);
73+
}));
8374

84-
currentError = test;
85-
req.resume();
86-
req.end();
75+
server.listen(0, common.mustCall(() => {
76+
const client = http2.connect(`http://localhost:${server.address().port}`);
77+
const req = client.request();
8778

8879
req.on('end', common.mustCall(() => {
8980
client.close();
90-
91-
if (!tests.length) {
92-
server.close();
93-
} else {
94-
runTest(tests.shift());
95-
}
81+
server.close();
9682
}));
97-
}
83+
req.resume();
84+
req.end();
85+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
// Flags: --expose-internals
3+
4+
const common = require('../common');
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
const http2 = require('http2');
8+
const {
9+
constants,
10+
Http2Stream,
11+
nghttp2ErrorString
12+
} = process.binding('http2');
13+
const { NghttpError } = require('internal/http2/util');
14+
15+
// tests error handling within respond
16+
// - every other NGHTTP2 error from binding (should emit stream error)
17+
18+
const specificTestKeys = [];
19+
20+
const specificTests = [];
21+
22+
const genericTests = Object.getOwnPropertyNames(constants)
23+
.filter((key) => (
24+
key.indexOf('NGHTTP2_ERR') === 0 && specificTestKeys.indexOf(key) < 0
25+
))
26+
.map((key) => ({
27+
ngError: constants[key],
28+
error: {
29+
code: 'ERR_HTTP2_ERROR',
30+
type: NghttpError,
31+
name: 'Error [ERR_HTTP2_ERROR]',
32+
message: nghttp2ErrorString(constants[key])
33+
},
34+
type: 'stream'
35+
}));
36+
37+
38+
const tests = specificTests.concat(genericTests);
39+
40+
let currentError;
41+
42+
// mock submitResponse because we only care about testing error handling
43+
Http2Stream.prototype.respond = () => currentError.ngError;
44+
45+
const server = http2.createServer();
46+
server.on('stream', common.mustCall((stream, headers) => {
47+
const errorMustCall = common.expectsError(currentError.error);
48+
const errorMustNotCall = common.mustNotCall(
49+
`${currentError.error.code} should emit on ${currentError.type}`
50+
);
51+
52+
if (currentError.type === 'stream') {
53+
stream.session.on('error', errorMustNotCall);
54+
stream.on('error', errorMustCall);
55+
stream.on('error', common.mustCall(() => {
56+
stream.destroy();
57+
}));
58+
} else {
59+
stream.session.once('error', errorMustCall);
60+
stream.on('error', errorMustNotCall);
61+
}
62+
63+
stream.respond();
64+
}, tests.length));
65+
66+
server.listen(0, common.mustCall(() => runTest(tests.shift())));
67+
68+
function runTest(test) {
69+
const port = server.address().port;
70+
const url = `http://localhost:${port}`;
71+
const headers = {
72+
':path': '/',
73+
':method': 'POST',
74+
':scheme': 'http',
75+
':authority': `localhost:${port}`
76+
};
77+
78+
const client = http2.connect(url);
79+
const req = client.request(headers);
80+
req.on('error', common.expectsError({
81+
code: 'ERR_HTTP2_STREAM_ERROR',
82+
type: Error,
83+
message: 'Stream closed with error code 2'
84+
}));
85+
86+
currentError = test;
87+
req.resume();
88+
req.end();
89+
90+
req.on('end', common.mustCall(() => {
91+
client.close();
92+
93+
if (!tests.length) {
94+
server.close();
95+
} else {
96+
runTest(tests.shift());
97+
}
98+
}));
99+
}

0 commit comments

Comments
 (0)