http2: add tests and benchmarks

PR-URL: https://github.com/nodejs/node/pull/14239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2017-07-17 10:29:42 -07:00
parent e71e71b513
commit b1e055696f
95 changed files with 5327 additions and 2 deletions

View File

@ -97,6 +97,12 @@ directory, see [the guide on benchmarks](../doc/guides/writing-and-running-bench
Benchmarks for the <code>http</code> subsystem.
</td>
</tr>
<tr>
<td>http2</td>
<td>
Benchmarks for the <code>http2</code> subsystem.
</td>
</tr>
<tr>
<td>misc</td>
<td>

View File

@ -111,10 +111,63 @@ class TestDoubleBenchmarker {
}
}
/**
* HTTP/2 Benchmarker
*/
class H2LoadBenchmarker {
constructor() {
this.name = 'h2load';
this.executable = 'h2load';
const result = child_process.spawnSync(this.executable, ['-h']);
this.present = !(result.error && result.error.code === 'ENOENT');
}
create(options) {
const args = [];
if (typeof options.requests === 'number')
args.push('-n', options.requests);
if (typeof options.clients === 'number')
args.push('-c', options.clients);
if (typeof options.threads === 'number')
args.push('-t', options.threads);
if (typeof options.maxConcurrentStreams === 'number')
args.push('-m', options.maxConcurrentStreams);
if (typeof options.initialWindowSize === 'number')
args.push('-w', options.initialWindowSize);
if (typeof options.sessionInitialWindowSize === 'number')
args.push('-W', options.sessionInitialWindowSize);
if (typeof options.rate === 'number')
args.push('-r', options.rate);
if (typeof options.ratePeriod === 'number')
args.push(`--rate-period=${options.ratePeriod}`);
if (typeof options.duration === 'number')
args.push('-T', options.duration);
if (typeof options.timeout === 'number')
args.push('-N', options.timeout);
if (typeof options.headerTableSize === 'number')
args.push(`--header-table-size=${options.headerTableSize}`);
if (typeof options.encoderHeaderTableSize === 'number') {
args.push(
`--encoder-header-table-size=${options.encoderHeaderTableSize}`);
}
const scheme = options.scheme || 'http';
const host = options.host || '127.0.0.1';
args.push(`${scheme}://${host}:${options.port}${options.path}`);
const child = child_process.spawn(this.executable, args);
return child;
}
processResults(output) {
const rex = /(\d+(?:\.\d+)) req\/s/;
return rex.exec(output)[1];
}
}
const http_benchmarkers = [
new WrkBenchmarker(),
new AutocannonBenchmarker(),
new TestDoubleBenchmarker()
new TestDoubleBenchmarker(),
new H2LoadBenchmarker()
];
const benchmarkers = {};

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../common.js');
const PORT = common.PORT;
const path = require('path');
const fs = require('fs');
const file = path.join(path.resolve(__dirname, '../fixtures'), 'alice.html');
var bench = common.createBenchmark(main, {
requests: [100, 1000, 10000, 100000, 1000000],
streams: [100, 200, 1000],
clients: [1, 2]
}, { flags: ['--expose-http2', '--no-warnings'] });
function main(conf) {
fs.open(file, 'r', (err, fd) => {
if (err)
throw err;
const n = +conf.requests;
const m = +conf.streams;
const c = +conf.clients;
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFD(fd);
stream.on('error', (err) => {});
});
server.listen(PORT, () => {
bench.http({
path: '/',
requests: n,
maxConcurrentStreams: m,
clients: c,
threads: c
}, () => server.close());
});
});
}

38
benchmark/http2/simple.js Normal file
View File

@ -0,0 +1,38 @@
'use strict';
const common = require('../common.js');
const PORT = common.PORT;
const path = require('path');
const fs = require('fs');
const file = path.join(path.resolve(__dirname, '../fixtures'), 'alice.html');
var bench = common.createBenchmark(main, {
requests: [100, 1000, 10000, 100000],
streams: [100, 200, 1000],
clients: [1, 2]
}, { flags: ['--expose-http2', '--no-warnings'] });
function main(conf) {
const n = +conf.requests;
const m = +conf.streams;
const c = +conf.clients;
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', (stream) => {
const out = fs.createReadStream(file);
stream.respond();
out.pipe(stream);
stream.on('error', (err) => {});
});
server.listen(PORT, () => {
bench.http({
path: '/',
requests: n,
maxConcurrentStreams: m,
clients: c,
threads: c
}, () => { server.close(); });
});
}

View File

@ -816,3 +816,12 @@ exports.hijackStdout = hijackStdWritable.bind(null, 'stdout');
exports.hijackStderr = hijackStdWritable.bind(null, 'stderr');
exports.restoreStdout = restoreWritable.bind(null, 'stdout');
exports.restoreStderr = restoreWritable.bind(null, 'stderr');
let fd = 2;
exports.firstInvalidFD = function firstInvalidFD() {
// Get first known bad file descriptor.
try {
while (fs.fstatSync(++fd));
} catch (e) {}
return fd;
};

View File

@ -19,6 +19,11 @@ const providers = Object.assign({}, process.binding('async_wrap').Providers);
process.removeAllListeners('uncaughtException');
hooks.disable();
delete providers.NONE; // Should never be used.
// TODO(jasnell): Test for these
delete providers.HTTP2SESSION;
delete providers.HTTP2SESSIONSHUTDOWNWRAP;
const obj_keys = Object.keys(providers);
if (obj_keys.length > 0)
process._rawDebug(obj_keys);

0
test/parallel/test-dgram-bind-default-address.js Executable file → Normal file
View File

View File

@ -0,0 +1,229 @@
// Flags: --expose-http2
'use strict';
require('../common');
const assert = require('assert');
assert.doesNotThrow(() => process.binding('http2'));
const binding = process.binding('http2');
const http2 = require('http2');
assert(binding.Http2Session);
assert.strictEqual(typeof binding.Http2Session, 'function');
const settings = http2.getDefaultSettings();
assert.strictEqual(settings.headerTableSize, 4096);
assert.strictEqual(settings.enablePush, true);
assert.strictEqual(settings.initialWindowSize, 65535);
assert.strictEqual(settings.maxFrameSize, 16384);
assert.strictEqual(binding.nghttp2ErrorString(-517),
'GOAWAY has already been sent');
// assert constants are present
assert(binding.constants);
assert.strictEqual(typeof binding.constants, 'object');
const constants = binding.constants;
const expectedStatusCodes = {
HTTP_STATUS_CONTINUE: 100,
HTTP_STATUS_SWITCHING_PROTOCOLS: 101,
HTTP_STATUS_PROCESSING: 102,
HTTP_STATUS_OK: 200,
HTTP_STATUS_CREATED: 201,
HTTP_STATUS_ACCEPTED: 202,
HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203,
HTTP_STATUS_NO_CONTENT: 204,
HTTP_STATUS_RESET_CONTENT: 205,
HTTP_STATUS_PARTIAL_CONTENT: 206,
HTTP_STATUS_MULTI_STATUS: 207,
HTTP_STATUS_ALREADY_REPORTED: 208,
HTTP_STATUS_IM_USED: 226,
HTTP_STATUS_MULTIPLE_CHOICES: 300,
HTTP_STATUS_MOVED_PERMANENTLY: 301,
HTTP_STATUS_FOUND: 302,
HTTP_STATUS_SEE_OTHER: 303,
HTTP_STATUS_NOT_MODIFIED: 304,
HTTP_STATUS_USE_PROXY: 305,
HTTP_STATUS_TEMPORARY_REDIRECT: 307,
HTTP_STATUS_PERMANENT_REDIRECT: 308,
HTTP_STATUS_BAD_REQUEST: 400,
HTTP_STATUS_UNAUTHORIZED: 401,
HTTP_STATUS_PAYMENT_REQUIRED: 402,
HTTP_STATUS_FORBIDDEN: 403,
HTTP_STATUS_NOT_FOUND: 404,
HTTP_STATUS_METHOD_NOT_ALLOWED: 405,
HTTP_STATUS_NOT_ACCEPTABLE: 406,
HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407,
HTTP_STATUS_REQUEST_TIMEOUT: 408,
HTTP_STATUS_CONFLICT: 409,
HTTP_STATUS_GONE: 410,
HTTP_STATUS_LENGTH_REQUIRED: 411,
HTTP_STATUS_PRECONDITION_FAILED: 412,
HTTP_STATUS_PAYLOAD_TOO_LARGE: 413,
HTTP_STATUS_URI_TOO_LONG: 414,
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415,
HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416,
HTTP_STATUS_EXPECTATION_FAILED: 417,
HTTP_STATUS_TEAPOT: 418,
HTTP_STATUS_MISDIRECTED_REQUEST: 421,
HTTP_STATUS_UNPROCESSABLE_ENTITY: 422,
HTTP_STATUS_LOCKED: 423,
HTTP_STATUS_FAILED_DEPENDENCY: 424,
HTTP_STATUS_UNORDERED_COLLECTION: 425,
HTTP_STATUS_UPGRADE_REQUIRED: 426,
HTTP_STATUS_PRECONDITION_REQUIRED: 428,
HTTP_STATUS_TOO_MANY_REQUESTS: 429,
HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451,
HTTP_STATUS_INTERNAL_SERVER_ERROR: 500,
HTTP_STATUS_NOT_IMPLEMENTED: 501,
HTTP_STATUS_BAD_GATEWAY: 502,
HTTP_STATUS_SERVICE_UNAVAILABLE: 503,
HTTP_STATUS_GATEWAY_TIMEOUT: 504,
HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505,
HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506,
HTTP_STATUS_INSUFFICIENT_STORAGE: 507,
HTTP_STATUS_LOOP_DETECTED: 508,
HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509,
HTTP_STATUS_NOT_EXTENDED: 510,
HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511
};
const expectedHeaderNames = {
HTTP2_HEADER_STATUS: ':status',
HTTP2_HEADER_METHOD: ':method',
HTTP2_HEADER_AUTHORITY: ':authority',
HTTP2_HEADER_SCHEME: ':scheme',
HTTP2_HEADER_PATH: ':path',
HTTP2_HEADER_DATE: 'date',
HTTP2_HEADER_ACCEPT_CHARSET: 'accept-charset',
HTTP2_HEADER_ACCEPT_ENCODING: 'accept-encoding',
HTTP2_HEADER_ACCEPT_LANGUAGE: 'accept-language',
HTTP2_HEADER_ACCEPT_RANGES: 'accept-ranges',
HTTP2_HEADER_ACCEPT: 'accept',
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: 'access-control-allow-origin',
HTTP2_HEADER_AGE: 'age',
HTTP2_HEADER_ALLOW: 'allow',
HTTP2_HEADER_AUTHORIZATION: 'authorization',
HTTP2_HEADER_CACHE_CONTROL: 'cache-control',
HTTP2_HEADER_CONTENT_DISPOSITION: 'content-disposition',
HTTP2_HEADER_CONTENT_ENCODING: 'content-encoding',
HTTP2_HEADER_CONTENT_LANGUAGE: 'content-language',
HTTP2_HEADER_CONTENT_LENGTH: 'content-length',
HTTP2_HEADER_CONTENT_LOCATION: 'content-location',
HTTP2_HEADER_CONTENT_RANGE: 'content-range',
HTTP2_HEADER_CONTENT_TYPE: 'content-type',
HTTP2_HEADER_COOKIE: 'cookie',
HTTP2_HEADER_CONNECTION: 'connection',
HTTP2_HEADER_ETAG: 'etag',
HTTP2_HEADER_EXPECT: 'expect',
HTTP2_HEADER_EXPIRES: 'expires',
HTTP2_HEADER_FROM: 'from',
HTTP2_HEADER_HOST: 'host',
HTTP2_HEADER_IF_MATCH: 'if-match',
HTTP2_HEADER_IF_MODIFIED_SINCE: 'if-modified-since',
HTTP2_HEADER_IF_NONE_MATCH: 'if-none-match',
HTTP2_HEADER_IF_RANGE: 'if-range',
HTTP2_HEADER_IF_UNMODIFIED_SINCE: 'if-unmodified-since',
HTTP2_HEADER_LAST_MODIFIED: 'last-modified',
HTTP2_HEADER_LINK: 'link',
HTTP2_HEADER_LOCATION: 'location',
HTTP2_HEADER_MAX_FORWARDS: 'max-forwards',
HTTP2_HEADER_PREFER: 'prefer',
HTTP2_HEADER_PROXY_AUTHENTICATE: 'proxy-authenticate',
HTTP2_HEADER_PROXY_AUTHORIZATION: 'proxy-authorization',
HTTP2_HEADER_PROXY_CONNECTION: 'proxy-connection',
HTTP2_HEADER_RANGE: 'range',
HTTP2_HEADER_REFERER: 'referer',
HTTP2_HEADER_REFRESH: 'refresh',
HTTP2_HEADER_RETRY_AFTER: 'retry-after',
HTTP2_HEADER_SERVER: 'server',
HTTP2_HEADER_SET_COOKIE: 'set-cookie',
HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: 'strict-transport-security',
HTTP2_HEADER_TRANSFER_ENCODING: 'transfer-encoding',
HTTP2_HEADER_USER_AGENT: 'user-agent',
HTTP2_HEADER_VARY: 'vary',
HTTP2_HEADER_VIA: 'via',
HTTP2_HEADER_WWW_AUTHENTICATE: 'www-authenticate',
HTTP2_HEADER_KEEP_ALIVE: 'keep-alive',
HTTP2_HEADER_CONTENT_MD5: 'content-md5',
HTTP2_HEADER_TE: 'te',
HTTP2_HEADER_UPGRADE: 'upgrade',
HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings'
};
const expectedNGConstants = {
NGHTTP2_SESSION_SERVER: 0,
NGHTTP2_SESSION_CLIENT: 1,
NGHTTP2_STREAM_STATE_IDLE: 1,
NGHTTP2_STREAM_STATE_OPEN: 2,
NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3,
NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4,
NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5,
NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6,
NGHTTP2_STREAM_STATE_CLOSED: 7,
NGHTTP2_HCAT_REQUEST: 0,
NGHTTP2_HCAT_RESPONSE: 1,
NGHTTP2_HCAT_PUSH_RESPONSE: 2,
NGHTTP2_HCAT_HEADERS: 3,
NGHTTP2_NO_ERROR: 0,
NGHTTP2_PROTOCOL_ERROR: 1,
NGHTTP2_INTERNAL_ERROR: 2,
NGHTTP2_FLOW_CONTROL_ERROR: 3,
NGHTTP2_SETTINGS_TIMEOUT: 4,
NGHTTP2_STREAM_CLOSED: 8,
NGHTTP2_FRAME_SIZE_ERROR: 6,
NGHTTP2_REFUSED_STREAM: 7,
NGHTTP2_CANCEL: 8,
NGHTTP2_COMPRESSION_ERROR: 9,
NGHTTP2_CONNECT_ERROR: 10,
NGHTTP2_ENHANCE_YOUR_CALM: 11,
NGHTTP2_INADEQUATE_SECURITY: 12,
NGHTTP2_HTTP_1_1_REQUIRED: 13,
NGHTTP2_NV_FLAG_NONE: 0,
NGHTTP2_NV_FLAG_NO_INDEX: 1,
NGHTTP2_ERR_DEFERRED: -508,
NGHTTP2_ERR_NOMEM: -901,
NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE: -509,
NGHTTP2_ERR_INVALID_ARGUMENT: -501,
NGHTTP2_ERR_STREAM_CLOSED: -510,
NGHTTP2_ERR_FRAME_SIZE_ERROR: -522,
NGHTTP2_FLAG_NONE: 0,
NGHTTP2_FLAG_END_STREAM: 1,
NGHTTP2_FLAG_END_HEADERS: 4,
NGHTTP2_FLAG_ACK: 1,
NGHTTP2_FLAG_PADDED: 8,
NGHTTP2_FLAG_PRIORITY: 32,
NGHTTP2_DEFAULT_WEIGHT: 16,
NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1,
NGHTTP2_SETTINGS_ENABLE_PUSH: 2,
NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3,
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4,
NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5,
NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6
};
const defaultSettings = {
DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096,
DEFAULT_SETTINGS_ENABLE_PUSH: 1,
DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535,
DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384
};
for (const name of Object.keys(constants)) {
if (name.startsWith('HTTP_STATUS_')) {
assert.strictEqual(expectedStatusCodes[name], constants[name],
`Expected status code match for ${name}`);
} else if (name.startsWith('HTTP2_HEADER_')) {
assert.strictEqual(expectedHeaderNames[name], constants[name],
`Expected header name match for ${name}`);
} else if (name.startsWith('NGHTTP2_')) {
assert.strictEqual(expectedNGConstants[name], constants[name],
`Expected ng constant match for ${name}`);
} else if (name.startsWith('DEFAULT_SETTINGS_')) {
assert.strictEqual(defaultSettings[name], constants[name],
`Expected default setting match for ${name}`);
}
}

View File

@ -0,0 +1,90 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', common.mustCall((stream, headers, flags) => {
const port = server.address().port;
if (headers[':path'] === '/') {
stream.pushStream({
':scheme': 'http',
':path': '/foobar',
':authority': `localhost:${port}`,
}, (push, headers) => {
push.respond({
'content-type': 'text/html',
':status': 200,
'x-push-data': 'pushed by server',
});
push.write('pushed by server ');
// Sending in next immediate ensures that a second data frame
// will be sent to the client, which will cause the 'data' event
// to fire multiple times.
setImmediate(() => {
push.end('data');
});
stream.end('st');
});
}
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.write('te');
}));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const headers = { ':path': '/' };
const client = http2.connect(`http://localhost:${port}`);
const req = client.request(headers);
let expected = 2;
function maybeClose() {
if (--expected === 0) {
server.close();
client.destroy();
}
}
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['content-type'], 'text/html');
}));
client.on('stream', common.mustCall((stream, headers, flags) => {
assert.strictEqual(headers[':scheme'], 'http');
assert.strictEqual(headers[':path'], '/foobar');
assert.strictEqual(headers[':authority'], `localhost:${port}`);
stream.on('push', common.mustCall((headers, flags) => {
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['content-type'], 'text/html');
assert.strictEqual(headers['x-push-data'], 'pushed by server');
}));
stream.setEncoding('utf8');
let pushData = '';
stream.on('data', common.mustCall((d) => {
pushData += d;
}, 2));
stream.on('end', common.mustCall(() => {
assert.strictEqual(pushData, 'pushed by server data');
maybeClose();
}));
}));
let data = '';
req.setEncoding('utf8');
req.on('data', common.mustCall((d) => data += d));
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'test');
maybeClose();
}));
req.end();
}));

View File

@ -0,0 +1,28 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustNotCall());
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
client.destroy();
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
}));
req.end();
}));

View File

@ -0,0 +1,28 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustNotCall());
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
client.destroy();
assert.throws(() => client.request({ ':path': '/' }),
common.expectsError({
code: 'ERR_HTTP2_INVALID_SESSION',
message: /^The session has been destroyed$/
}));
server.close();
}));

View File

@ -0,0 +1,54 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
server.listen(0);
server.on('listening', common.mustCall(function() {
const port = this.address().port;
const destroyCallbacks = [
(client) => client.destroy(),
(client) => client.socket.destroy()
];
let remaining = destroyCallbacks.length;
destroyCallbacks.forEach((destroyCallback) => {
const client = h2.connect(`http://localhost:${port}`);
client.on('connect', common.mustCall(() => {
const socket = client.socket;
assert(client.socket, 'client session has associated socket');
assert(!client.destroyed,
'client has not been destroyed before destroy is called');
assert(!socket.destroyed,
'socket has not been destroyed before destroy is called');
// Ensure that 'close' event is emitted
client.on('close', common.mustCall());
destroyCallback(client);
assert(!client.socket, 'client.socket undefined after destroy is called');
// Must must be closed
client.on('close', common.mustCall(() => {
assert(client.destroyed);
}));
// socket will close on process.nextTick
socket.on('close', common.mustCall(() => {
assert(socket.destroyed);
}));
if (--remaining === 0) {
server.close();
}
}));
});
}));

View File

@ -0,0 +1,37 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
client.priority(req, {});
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,34 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
client.rstStream(req, 0);
assert.strictEqual(req.rstCode, 0);
// make sure that destroy is called
req._destroy = common.mustCall(req._destroy.bind(req));
req.on('streamClosed', common.mustCall((code) => {
assert.strictEqual(req.destroyed, true);
assert.strictEqual(code, 0);
server.close();
client.destroy();
}));
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall());
req.end();
}));

View File

@ -0,0 +1,49 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const checkWeight = (actual, expect) => {
const server = http2.createServer();
server.on('stream', common.mustCall((stream, headers, flags) => {
assert.strictEqual(stream.state.weight, expect);
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('test');
}));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const client = http2.connect(`http://localhost:${port}`);
const headers = { ':path': '/' };
const req = client.request(headers, { weight: actual });
req.on('data', common.mustCall(() => {}));
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));
};
// when client weight is lower than 1, weight is 1
checkWeight(-1, 1);
checkWeight(0, 1);
// 1 - 256 is correct weight
checkWeight(1, 1);
checkWeight(16, 16);
checkWeight(256, 256);
// when client weight is higher than 256, weight is 256
checkWeight(257, 256);
checkWeight(512, 256);
// when client weight is undefined, weight is default 16
checkWeight(undefined, 16);

View File

@ -0,0 +1,63 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
assert.throws(() => client.settings({headerTableSize: -1}),
RangeError);
assert.throws(() => client.settings({headerTableSize: 2 ** 32}),
RangeError);
assert.throws(() => client.settings({initialWindowSize: -1}),
RangeError);
assert.throws(() => client.settings({initialWindowSize: 2 ** 32}),
RangeError);
assert.throws(() => client.settings({maxFrameSize: 1}),
RangeError);
assert.throws(() => client.settings({maxFrameSize: 2 ** 24}),
RangeError);
assert.throws(() => client.settings({maxConcurrentStreams: -1}),
RangeError);
assert.throws(() => client.settings({maxConcurrentStreams: 2 ** 31}),
RangeError);
assert.throws(() => client.settings({maxHeaderListSize: -1}),
RangeError);
assert.throws(() => client.settings({maxHeaderListSize: 2 ** 32}),
RangeError);
['a', 1, 0, null, {}].forEach((i) => {
assert.throws(() => client.settings({enablePush: i}), TypeError);
});
client.settings({ maxFrameSize: 1234567 });
const req = client.request({ ':path': '/' });
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,23 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustNotCall());
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
client.shutdown({graceful: true}, common.mustCall(() => {
server.close();
client.destroy();
}));
}));

View File

@ -0,0 +1,46 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const body =
'<html><head></head><body><h1>this is some data</h2></body></html>';
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream) {
// The stream aborted event must have been triggered
stream.on('aborted', common.mustCall());
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.write(body);
}
server.listen(0);
server.on('listening', common.mustCall(function() {
const client = h2.connect(`http://localhost:${this.address().port}`);
const req = client.request({ ':path': '/' });
req.on('response', common.mustCall(() => {
// send a premature socket close
client.socket.destroy();
}));
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
server.close();
}));
// On the client, the close event must call
client.on('close', common.mustCall());
req.end();
}));

View File

@ -0,0 +1,63 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const NGHTTP2_INTERNAL_ERROR = h2.constants.NGHTTP2_INTERNAL_ERROR;
const server = h2.createServer();
// Do not mustCall the server side callbacks, they may or may not be called
// depending on the OS. The determination is based largely on operating
// system specific timings
server.on('stream', (stream) => {
// Do not wrap in a must call or use common.expectsError (which now uses
// must call). The error may or may not be reported depending on operating
// system specific timings.
stream.on('error', (err) => {
if (err) {
assert.strictEqual(err.code, 'ERR_HTTP2_STREAM_ERROR');
assert.strictEqual(err.message, 'Stream closed with error code 2');
}
});
stream.respond({});
stream.end();
});
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
const err = new Error('test');
req.destroy(err);
req.on('error', common.mustCall((err) => {
const fn = err.code === 'ERR_HTTP2_STREAM_ERROR' ?
common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 2'
}) :
common.expectsError({
type: Error,
message: 'test'
});
fn(err);
}, 2));
req.on('streamClosed', common.mustCall((code) => {
assert.strictEqual(req.rstCode, NGHTTP2_INTERNAL_ERROR);
assert.strictEqual(code, NGHTTP2_INTERNAL_ERROR);
server.close();
client.destroy();
}));
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall());
}));

View File

@ -0,0 +1,37 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', common.mustNotCall());
const count = 32;
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let remaining = count;
function maybeClose() {
if (--remaining === 0) {
server.close();
client.destroy();
}
}
// nghttp2 will catch the bad header value for us.
function doTest(i) {
const req = client.request({ ':path': `bad${String.fromCharCode(i)}path` });
req.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 1'
}));
req.on('streamClosed', common.mustCall(maybeClose));
}
for (let i = 0; i <= count; i += 1)
doTest(i);
}));

View File

@ -0,0 +1,44 @@
// Flags: --expose-http2
'use strict';
// Verifies that uploading data from a client works
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const fs = require('fs');
const path = require('path');
const loc = path.join(common.fixturesDir, 'person.jpg');
let fileData;
assert(fs.existsSync(loc));
fs.readFile(loc, common.mustCall((err, data) => {
assert.ifError(err);
fileData = data;
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
let data = Buffer.alloc(0);
stream.on('data', (chunk) => data = Buffer.concat([data, chunk]));
stream.on('end', common.mustCall(() => {
assert.deepStrictEqual(data, fileData);
}));
stream.respond();
stream.end();
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':method': 'POST' });
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
fs.createReadStream(loc).pipe(req);
}));
}));

View File

@ -0,0 +1,53 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_METHOD,
HTTP2_METHOD_POST
} = h2.constants;
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
let data = '';
stream.setEncoding('utf8');
stream.on('data', (chunk) => data += chunk);
stream.on('end', common.mustCall(() => {
assert.strictEqual(data, 'some data more data');
}));
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({
[HTTP2_HEADER_PATH]: '/',
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST });
req.write('some data ');
req.write('more data');
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,70 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerRequest should have header helpers
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
const expected = {
':path': '/foobar',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`,
'foo-bar': 'abc123'
};
assert.strictEqual(request.method, expected[':method']);
assert.strictEqual(request.scheme, expected[':scheme']);
assert.strictEqual(request.path, expected[':path']);
assert.strictEqual(request.url, expected[':path']);
assert.strictEqual(request.authority, expected[':authority']);
const headers = request.headers;
for (const [name, value] of Object.entries(expected)) {
assert.strictEqual(headers[name], value);
}
const rawHeaders = request.rawHeaders;
for (const [name, value] of Object.entries(expected)) {
const position = rawHeaders.indexOf(name);
assert.notStrictEqual(position, -1);
assert.strictEqual(rawHeaders[position + 1], value);
}
request.url = '/one';
assert.strictEqual(request.url, '/one');
assert.strictEqual(request.path, '/one');
request.path = '/two';
assert.strictEqual(request.url, '/two');
assert.strictEqual(request.path, '/two');
response.on('finish', common.mustCall(function() {
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/foobar',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`,
'foo-bar': 'abc123'
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,52 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const net = require('net');
// Http2ServerRequest should expose convenience properties
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
const expected = {
statusCode: null,
version: '2.0',
httpVersionMajor: 2,
httpVersionMinor: 0
};
assert.strictEqual(request.statusCode, expected.statusCode);
assert.strictEqual(request.httpVersion, expected.version);
assert.strictEqual(request.httpVersionMajor, expected.httpVersionMajor);
assert.strictEqual(request.httpVersionMinor, expected.httpVersionMinor);
assert.ok(request.socket instanceof net.Socket);
assert.ok(request.connection instanceof net.Socket);
assert.strictEqual(request.socket, request.connection);
response.on('finish', common.mustCall(function() {
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/foobar',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,79 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Push a request & response
const pushExpect = 'This is a server-initiated response';
const servExpect = 'This is a client-initiated response';
const server = h2.createServer((request, response) => {
assert.strictEqual(response.stream.id % 2, 1);
response.write(servExpect);
response.createPushResponse({
':path': '/pushed',
':method': 'GET'
}, common.mustCall((error, push) => {
assert.ifError(error);
assert.strictEqual(push.stream.id % 2, 0);
push.end(pushExpect);
response.end();
}));
});
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const client = h2.connect(`http://localhost:${port}`, common.mustCall(() => {
const headers = {
':path': '/',
':method': 'GET',
};
let remaining = 2;
function maybeClose() {
if (--remaining === 0) {
client.destroy();
server.close();
}
}
const req = client.request(headers);
client.on('stream', common.mustCall((pushStream, headers) => {
assert.strictEqual(headers[':path'], '/pushed');
assert.strictEqual(headers[':method'], 'GET');
assert.strictEqual(headers[':scheme'], 'http');
assert.strictEqual(headers[':authority'], `localhost:${port}`);
let actual = '';
pushStream.on('push', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
assert(headers['date']);
}));
pushStream.setEncoding('utf8');
pushStream.on('data', (chunk) => actual += chunk);
pushStream.on('end', common.mustCall(() => {
assert.strictEqual(actual, pushExpect);
maybeClose();
}));
}));
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
assert(headers['date']);
}));
let actual = '';
req.setEncoding('utf8');
req.on('data', (chunk) => actual += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(actual, servExpect);
maybeClose();
}));
}));
}));

View File

@ -0,0 +1,77 @@
// Flags: --expose-http2
'use strict';
const { strictEqual } = require('assert');
const { mustCall, mustNotCall } = require('../common');
const {
createServer,
connect,
constants: {
HTTP2_HEADER_STATUS,
HTTP_STATUS_OK
}
} = require('http2');
{
// Http2ServerResponse.end callback is called only the first time,
// but may be invoked repeatedly without throwing errors.
const server = createServer(mustCall((request, response) => {
response.end(mustCall(() => {
server.close();
}));
response.end(mustNotCall());
}));
server.listen(0, mustCall(() => {
const { port } = server.address();
const url = `http://localhost:${port}`;
const client = connect(url, mustCall(() => {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('data', mustNotCall());
request.on('end', mustCall(() => client.destroy()));
request.end();
request.resume();
}));
}));
}
{
// Http2ServerResponse.end is not necessary on HEAD requests since the stream
// is already closed. Headers, however, can still be sent to the client.
const server = createServer(mustCall((request, response) => {
strictEqual(response.finished, true);
response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
response.flushHeaders();
response.end(mustNotCall());
}));
server.listen(0, mustCall(() => {
const { port } = server.address();
const url = `http://localhost:${port}`;
const client = connect(url, mustCall(() => {
const headers = {
':path': '/',
':method': 'HEAD',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('response', mustCall((headers, flags) => {
strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
strictEqual(flags, 5); // the end of stream flag is set
strictEqual(headers.foo, 'bar');
}));
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
client.destroy();
server.close();
}));
request.end();
request.resume();
}));
}));
}

View File

@ -0,0 +1,37 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerResponse.finished
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
response.on('finish', common.mustCall(function() {
server.close();
}));
assert.strictEqual(response.finished, false);
response.end();
assert.strictEqual(response.finished, true);
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,43 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerResponse.flushHeaders
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
response.flushHeaders();
response.flushHeaders(); // Idempotent
response.writeHead(400, {'foo-bar': 'abc123'}); // Ignored
response.on('finish', common.mustCall(function() {
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('response', common.mustCall(function(headers, flags) {
assert.strictEqual(headers['foo-bar'], undefined);
assert.strictEqual(headers[':status'], 200);
}, 1));
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,83 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerResponse should support checking and reading custom headers
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
const real = 'foo-bar';
const fake = 'bar-foo';
const denormalised = ` ${real.toUpperCase()}\n\t`;
const expectedValue = 'abc123';
response.setHeader(real, expectedValue);
assert.strictEqual(response.hasHeader(real), true);
assert.strictEqual(response.hasHeader(fake), false);
assert.strictEqual(response.hasHeader(denormalised), true);
assert.strictEqual(response.getHeader(real), expectedValue);
assert.strictEqual(response.getHeader(denormalised), expectedValue);
assert.strictEqual(response.getHeader(fake), undefined);
response.removeHeader(fake);
assert.strictEqual(response.hasHeader(fake), false);
response.setHeader(real, expectedValue);
assert.strictEqual(response.getHeader(real), expectedValue);
assert.strictEqual(response.hasHeader(real), true);
response.removeHeader(real);
assert.strictEqual(response.hasHeader(real), false);
response.setHeader(denormalised, expectedValue);
assert.strictEqual(response.getHeader(denormalised), expectedValue);
assert.strictEqual(response.hasHeader(denormalised), true);
response.removeHeader(denormalised);
assert.strictEqual(response.hasHeader(denormalised), false);
assert.throws(function() {
response.setHeader(':status', 'foobar');
}, Error);
assert.throws(function() {
response.setHeader(real, null);
}, TypeError);
assert.throws(function() {
response.setHeader(real, undefined);
}, TypeError);
response.setHeader(real, expectedValue);
const expectedHeaderNames = [real];
assert.deepStrictEqual(response.getHeaderNames(), expectedHeaderNames);
const expectedHeaders = {[real]: expectedValue};
assert.deepStrictEqual(response.getHeaders(), expectedHeaders);
response.getHeaders()[fake] = fake;
assert.strictEqual(response.hasHeader(fake), false);
response.on('finish', common.mustCall(function() {
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,76 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerResponse should have a statusCode property
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
const expectedDefaultStatusCode = 200;
const realStatusCodes = {
continue: 100,
ok: 200,
multipleChoices: 300,
badRequest: 400,
internalServerError: 500
};
const fakeStatusCodes = {
tooLow: 99,
tooHigh: 600
};
assert.strictEqual(response.statusCode, expectedDefaultStatusCode);
assert.doesNotThrow(function() {
response.statusCode = realStatusCodes.ok;
response.statusCode = realStatusCodes.multipleChoices;
response.statusCode = realStatusCodes.badRequest;
response.statusCode = realStatusCodes.internalServerError;
});
assert.throws(function() {
response.statusCode = realStatusCodes.continue;
}, common.expectsError({
code: 'ERR_HTTP2_INFO_STATUS_NOT_ALLOWED',
type: RangeError
}));
assert.throws(function() {
response.statusCode = fakeStatusCodes.tooLow;
}, common.expectsError({
code: 'ERR_HTTP2_STATUS_INVALID',
type: RangeError
}));
assert.throws(function() {
response.statusCode = fakeStatusCodes.tooHigh;
}, common.expectsError({
code: 'ERR_HTTP2_STATUS_INVALID',
type: RangeError
}));
response.on('finish', common.mustCall(function() {
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,52 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerResponse.writeHead should accept an optional status message
const unsupportedWarned = common.mustCall(1);
process.on('warning', ({name, message}) => {
const expectedMessage =
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)';
if (name === 'UnsupportedWarning' && message === expectedMessage)
unsupportedWarned();
});
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
const statusCode = 200;
const statusMessage = 'OK';
const headers = {'foo-bar': 'abc123'};
response.writeHead(statusCode, statusMessage, headers);
response.on('finish', common.mustCall(function() {
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('response', common.mustCall(function(headers) {
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['foo-bar'], 'abc123');
}, 1));
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,98 @@
// Flags: --expose-http2
'use strict';
const { throws } = require('assert');
const { mustCall, mustNotCall, expectsError } = require('../common');
const { createServer, connect } = require('http2');
// Http2ServerResponse.write does not imply there is a callback
const expectedError = expectsError({
code: 'ERR_HTTP2_STREAM_CLOSED',
message: 'The stream is already closed'
}, 2);
{
const server = createServer();
server.listen(0, mustCall(() => {
const port = server.address().port;
const url = `http://localhost:${port}`;
const client = connect(url, mustCall(() => {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.end();
request.resume();
}));
server.once('request', mustCall((request, response) => {
client.destroy();
response.stream.session.on('close', mustCall(() => {
response.on('error', mustNotCall());
throws(
() => { response.write('muahaha'); },
/The stream is already closed/
);
server.close();
}));
}));
}));
}
{
const server = createServer();
server.listen(0, mustCall(() => {
const port = server.address().port;
const url = `http://localhost:${port}`;
const client = connect(url, mustCall(() => {
const headers = {
':path': '/',
':method': 'get',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.end();
request.resume();
}));
server.once('request', mustCall((request, response) => {
client.destroy();
response.stream.session.on('close', mustCall(() => {
response.write('muahaha', mustCall(expectedError));
server.close();
}));
}));
}));
}
{
const server = createServer();
server.listen(0, mustCall(() => {
const port = server.address().port;
const url = `http://localhost:${port}`;
const client = connect(url, mustCall(() => {
const headers = {
':path': '/',
':method': 'get',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.end();
request.resume();
}));
server.once('request', mustCall((request, response) => {
response.stream.session.on('close', mustCall(() => {
response.write('muahaha', 'utf8', mustCall(expectedError));
server.close();
}));
client.destroy();
}));
}));
}

View File

@ -0,0 +1,44 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
// Http2ServerResponse.writeHead should override previous headers
const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
response.setHeader('foo-bar', 'def456');
response.writeHead(500);
response.writeHead(418, {'foo-bar': 'abc123'}); // Override
response.on('finish', common.mustCall(function() {
assert.doesNotThrow(() => { response.writeHead(300); });
server.close();
}));
response.end();
}));
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(function() {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
};
const request = client.request(headers);
request.on('response', common.mustCall(function(headers) {
assert.strictEqual(headers['foo-bar'], 'abc123');
assert.strictEqual(headers[':status'], 418);
}, 1));
request.on('end', common.mustCall(function() {
client.destroy();
}));
request.end();
request.resume();
}));
}));

View File

@ -0,0 +1,71 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const net = require('net');
const http2 = require('http2');
const { URL } = require('url');
const {
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
NGHTTP2_CONNECT_ERROR
} = http2.constants;
const server = net.createServer(common.mustCall((socket) => {
let data = '';
socket.setEncoding('utf8');
socket.on('data', (chunk) => data += chunk);
socket.on('end', common.mustCall(() => {
assert.strictEqual(data, 'hello');
}));
socket.on('close', common.mustCall());
socket.end('hello');
}));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const proxy = http2.createServer();
proxy.on('stream', common.mustCall((stream, headers) => {
if (headers[HTTP2_HEADER_METHOD] !== 'CONNECT') {
stream.rstWithRefused();
return;
}
const auth = new URL(`tcp://${headers[HTTP2_HEADER_AUTHORITY]}`);
assert.strictEqual(auth.hostname, 'localhost');
assert.strictEqual(+auth.port, port);
const socket = net.connect(auth.port, auth.hostname, () => {
stream.respond();
socket.pipe(stream);
stream.pipe(socket);
});
socket.on('close', common.mustCall());
socket.on('error', (error) => {
stream.rstStream(NGHTTP2_CONNECT_ERROR);
});
}));
proxy.listen(0, () => {
const client = http2.connect(`http://localhost:${proxy.address().port}`);
const req = client.request({
[HTTP2_HEADER_METHOD]: 'CONNECT',
[HTTP2_HEADER_AUTHORITY]: `localhost:${port}`,
});
req.on('response', common.mustCall());
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => data += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'hello');
client.destroy();
proxy.close();
server.close();
}));
req.end('hello');
});
}));

View File

@ -0,0 +1,29 @@
// Flags: --expose-http2
'use strict';
const { mustCall } = require('../common');
const { doesNotThrow } = require('assert');
const { createServer, connect } = require('http2');
const server = createServer();
server.listen(0, mustCall(() => {
const authority = `http://localhost:${server.address().port}`;
const options = {};
const listener = () => mustCall();
const clients = new Set();
doesNotThrow(() => clients.add(connect(authority)));
doesNotThrow(() => clients.add(connect(authority, options)));
doesNotThrow(() => clients.add(connect(authority, options, listener())));
doesNotThrow(() => clients.add(connect(authority, listener())));
for (const client of clients) {
client.once('connect', mustCall((headers) => {
client.destroy();
clients.delete(client);
if (clients.size === 0) {
server.close();
}
}));
}
}));

View File

@ -0,0 +1,62 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
const setCookie = [
'a=b',
'c=d; Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly'
];
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
assert(Array.isArray(headers.abc));
assert.strictEqual(headers.abc.length, 3);
assert.strictEqual(headers.abc[0], '1');
assert.strictEqual(headers.abc[1], '2');
assert.strictEqual(headers.abc[2], '3');
assert.strictEqual(typeof headers.cookie, 'string');
assert.strictEqual(headers.cookie, 'a=b; c=d; e=f');
stream.respond({
'content-type': 'text/html',
':status': 200,
'set-cookie': setCookie
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({
':path': '/',
abc: [1, 2, 3],
cookie: ['a=b', 'c=d', 'e=f'],
});
req.resume();
req.on('response', common.mustCall((headers) => {
assert(Array.isArray(headers['set-cookie']));
assert.deepStrictEqual(headers['set-cookie'], setCookie,
'set-cookie header does not match');
}));
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,88 @@
// Flags: --expose-http2
'use strict';
// Tests http2.connect()
const common = require('../common');
const fs = require('fs');
const h2 = require('http2');
const path = require('path');
const url = require('url');
const URL = url.URL;
{
const server = h2.createServer();
server.listen(0);
server.on('listening', common.mustCall(function() {
const port = this.address().port;
const items = [
[`http://localhost:${port}`],
[new URL(`http://localhost:${port}`)],
[url.parse(`http://localhost:${port}`)],
[{port: port}, {protocol: 'http:'}],
[{port: port, hostname: '127.0.0.1'}, {protocol: 'http:'}]
];
let count = items.length;
const maybeClose = common.mustCall((client) => {
client.destroy();
if (--count === 0) {
setImmediate(() => server.close());
}
}, items.length);
items.forEach((i) => {
const client =
h2.connect.apply(null, i)
.on('connect', common.mustCall(() => maybeClose(client)));
});
// Will fail because protocol does not match the server.
h2.connect({port: port, protocol: 'https:'})
.on('socketError', common.mustCall());
}));
}
{
const options = {
key: fs.readFileSync(path.join(common.fixturesDir, 'keys/agent3-key.pem')),
cert: fs.readFileSync(path.join(common.fixturesDir, 'keys/agent3-cert.pem'))
};
const server = h2.createSecureServer(options);
server.listen(0);
server.on('listening', common.mustCall(function() {
const port = this.address().port;
const opts = {rejectUnauthorized: false};
const items = [
[`https://localhost:${port}`, opts],
[new URL(`https://localhost:${port}`), opts],
[url.parse(`https://localhost:${port}`), opts],
[{port: port, protocol: 'https:'}, opts],
[{port: port, hostname: '127.0.0.1', protocol: 'https:'}, opts]
];
let count = items.length;
const maybeClose = common.mustCall((client) => {
client.destroy();
if (--count === 0) {
setImmediate(() => server.close());
}
}, items.length);
items.forEach((i) => {
const client =
h2.connect.apply(null, i)
.on('connect', common.mustCall(() => maybeClose(client)));
});
}));
}

View File

@ -0,0 +1,75 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const tls = require('tls');
const h2 = require('http2');
function loadKey(keyname) {
return fs.readFileSync(
path.join(common.fixturesDir, 'keys', keyname), 'binary');
}
function onStream(stream, headers) {
const socket = stream.session.socket;
assert(headers[':authority'].startsWith(socket.servername));
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end(JSON.stringify({
servername: socket.servername,
alpnProtocol: socket.alpnProtocol
}));
}
function verifySecureSession(key, cert, ca, opts) {
const server = h2.createSecureServer({cert, key});
server.on('stream', common.mustCall(onStream));
server.listen(0);
server.on('listening', common.mustCall(function() {
const headers = { ':path': '/' };
if (!opts) {
opts = {};
}
opts.secureContext = tls.createSecureContext({ca});
const client = h2.connect(`https://localhost:${this.address().port}`, opts, function() {
const req = client.request(headers);
req.on('response', common.mustCall(function(headers) {
assert.strictEqual(headers[':status'], 200, 'status code is set');
assert.strictEqual(headers['content-type'], 'text/html',
'content type is set');
assert(headers['date'], 'there is a date');
}));
let data = '';
req.setEncoding('utf8');
req.on('data', (d) => data += d);
req.on('end', common.mustCall(() => {
const jsonData = JSON.parse(data);
assert.strictEqual(jsonData.servername, opts.servername || 'localhost');
assert.strictEqual(jsonData.alpnProtocol, 'h2');
server.close();
client.socket.destroy();
}));
req.end();
});
}));
}
// The server can be connected as 'localhost'.
verifySecureSession(
loadKey('agent8-key.pem'),
loadKey('agent8-cert.pem'),
loadKey('fake-startcom-root-cert.pem'));
// Custom servername is specified.
verifySecureSession(
loadKey('agent1-key.pem'),
loadKey('agent1-cert.pem'),
loadKey('ca1-cert.pem'),
{servername: 'agent1'});

View File

@ -0,0 +1,61 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const body =
'<html><head></head><body><h1>this is some data</h2></body></html>';
const server = h2.createServer();
const count = 100;
// we use the lower-level API here
server.on('stream', common.mustCall(onStream, count));
function onStream(stream, headers, flags) {
assert.strictEqual(headers[':scheme'], 'http');
assert.ok(headers[':authority']);
assert.strictEqual(headers[':method'], 'GET');
assert.strictEqual(flags, 5);
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end(body);
}
server.listen(0);
let expected = count;
server.on('listening', common.mustCall(function() {
const client = h2.connect(`http://localhost:${this.address().port}`);
const headers = { ':path': '/' };
for (let n = 0; n < count; n++) {
const req = client.request(headers);
req.on('response', common.mustCall(function(headers) {
assert.strictEqual(headers[':status'], 200, 'status code is set');
assert.strictEqual(headers['content-type'], 'text/html',
'content type is set');
assert(headers['date'], 'there is a date');
}));
let data = '';
req.setEncoding('utf8');
req.on('data', (d) => data += d);
req.on('end', common.mustCall(() => {
assert.strictEqual(body, data);
if (--expected === 0) {
server.close();
client.destroy();
}
}));
req.end();
}
}));

View File

@ -0,0 +1,28 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
// Date header is defaulted
stream.respond();
stream.end();
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall((headers) => {
// The date header must be set to a non-invalid value
assert.notStrictEqual((new Date()).toString(), 'Invalid Date');
}));
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
}));

View File

@ -0,0 +1,48 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const options = {};
const server = http2.createServer(options);
// options are defaulted but the options are not modified
assert.deepStrictEqual(Object.keys(options), []);
server.on('stream', common.mustCall((stream) => {
const headers = {};
const options = {};
stream.respond(headers, options);
// The headers are defaulted but the original object is not modified
assert.deepStrictEqual(Object.keys(headers), []);
// Options are defaulted but the original object is not modified
assert.deepStrictEqual(Object.keys(options), []);
stream.end();
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const headers = {};
const options = {};
const req = client.request(headers, options);
// The headers are defaulted but the original object is not modified
assert.deepStrictEqual(Object.keys(headers), []);
// Options are defaulted but the original object is not modified
assert.deepStrictEqual(Object.keys(options), []);
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
}));

View File

@ -0,0 +1,131 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const check = Buffer.from([0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x05,
0x00, 0x00, 0x40, 0x00, 0x00, 0x04, 0x00, 0x00,
0xff, 0xff, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01]);
const val = http2.getPackedSettings(http2.getDefaultSettings());
assert.deepStrictEqual(val, check);
[
['headerTableSize', 0],
['headerTableSize', 2 ** 32 - 1],
['initialWindowSize', 0],
['initialWindowSize', 2 ** 32 - 1],
['maxFrameSize', 16384],
['maxFrameSize', 2 ** 24 - 1],
['maxConcurrentStreams', 0],
['maxConcurrentStreams', 2 ** 31 - 1],
['maxHeaderListSize', 0],
['maxHeaderListSize', 2 ** 32 - 1]
].forEach((i) => {
assert.doesNotThrow(() => http2.getPackedSettings({ [i[0]]: i[1] }));
});
assert.doesNotThrow(() => http2.getPackedSettings({ enablePush: true }));
assert.doesNotThrow(() => http2.getPackedSettings({ enablePush: false }));
[
['headerTableSize', -1],
['headerTableSize', 2 ** 32],
['initialWindowSize', -1],
['initialWindowSize', 2 ** 32],
['maxFrameSize', 16383],
['maxFrameSize', 2 ** 24],
['maxConcurrentStreams', -1],
['maxConcurrentStreams', 2 ** 31],
['maxHeaderListSize', -1],
['maxHeaderListSize', 2 ** 32]
].forEach((i) => {
assert.throws(() => {
http2.getPackedSettings({ [i[0]]: i[1] });
}, common.expectsError({
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
type: RangeError,
message: `Invalid value for setting "${i[0]}": ${i[1]}`
}));
});
[
1, null, '', Infinity, new Date(), {}, NaN, [false]
].forEach((i) => {
assert.throws(() => {
http2.getPackedSettings({ enablePush: i });
}, common.expectsError({
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
type: TypeError,
message: `Invalid value for setting "enablePush": ${i}`
}));
});
{
const check = Buffer.from([
0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x03, 0x00, 0x00,
0x00, 0xc8, 0x00, 0x05, 0x00, 0x00, 0x4e, 0x20, 0x00, 0x04,
0x00, 0x00, 0x00, 0x64, 0x00, 0x06, 0x00, 0x00, 0x00, 0x64,
0x00, 0x02, 0x00, 0x00, 0x00, 0x01]);
const packed = http2.getPackedSettings({
headerTableSize: 100,
initialWindowSize: 100,
maxFrameSize: 20000,
maxConcurrentStreams: 200,
maxHeaderListSize: 100,
enablePush: true,
foo: 'ignored'
});
assert.strictEqual(packed.length, 36);
assert.deepStrictEqual(packed, check);
}
{
const packed = Buffer.from([
0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x03, 0x00, 0x00,
0x00, 0xc8, 0x00, 0x05, 0x00, 0x00, 0x4e, 0x20, 0x00, 0x04,
0x00, 0x00, 0x00, 0x64, 0x00, 0x06, 0x00, 0x00, 0x00, 0x64,
0x00, 0x02, 0x00, 0x00, 0x00, 0x01]);
[1, true, '', [], {}, NaN].forEach((i) => {
assert.throws(() => {
http2.getUnpackedSettings(i);
}, common.expectsError({
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "buf" argument must be one of type Buffer or Uint8Array'
}));
});
assert.throws(() => {
http2.getUnpackedSettings(packed.slice(5));
}, common.expectsError({
code: 'ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH',
type: RangeError,
message: 'Packed settings length must be a multiple of six'
}));
const settings = http2.getUnpackedSettings(packed);
assert(settings);
assert.strictEqual(settings.headerTableSize, 100);
assert.strictEqual(settings.initialWindowSize, 100);
assert.strictEqual(settings.maxFrameSize, 20000);
assert.strictEqual(settings.maxConcurrentStreams, 200);
assert.strictEqual(settings.maxHeaderListSize, 100);
assert.strictEqual(settings.enablePush, true);
}
{
const packed = Buffer.from([0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF]);
assert.throws(() => {
http2.getUnpackedSettings(packed, {validate: true});
}, common.expectsError({
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
type: RangeError,
message: 'Invalid value for setting "maxConcurrentStreams": 4294967295'
}));
}

View File

@ -0,0 +1,38 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
const data = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
server.on('stream', common.mustCall((stream) => {
stream.session.shutdown({
errorCode: 1,
opaqueData: data
});
stream.end();
stream.on('error', common.mustCall(common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 7'
})));
}));
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
client.on('goaway', common.mustCall((code, lastStreamID, buf) => {
assert.deepStrictEqual(code, 1);
assert.deepStrictEqual(lastStreamID, 0);
assert.deepStrictEqual(data, buf);
server.close();
}));
const req = client.request({ ':path': '/' });
req.resume();
req.on('end', common.mustCall());
req.end();
});

View File

@ -0,0 +1,57 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const errCheck = common.expectsError({
type: Error,
message: 'write after end'
}, 2);
const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_STATUS,
HTTP2_METHOD_HEAD,
} = http2.constants;
const server = http2.createServer();
server.on('stream', (stream, headers) => {
assert.strictEqual(headers[HTTP2_HEADER_METHOD], HTTP2_METHOD_HEAD);
stream.respond({ [HTTP2_HEADER_STATUS]: 200 });
// Because this is a head request, the outbound stream is closed automatically
stream.on('error', common.mustCall(errCheck));
stream.write('data');
});
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request({
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_HEAD,
[HTTP2_HEADER_PATH]: '/'
});
// Because it is a HEAD request, the payload is meaningless. The
// option.endStream flag is set automatically making the stream
// non-writable.
req.on('error', common.mustCall(errCheck));
req.write('data');
req.on('response', common.mustCall((headers, flags) => {
assert.strictEqual(headers[HTTP2_HEADER_STATUS], 200);
assert.strictEqual(flags, 5); // the end of stream flag is set
}));
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
});

View File

@ -0,0 +1,146 @@
// Flags: --expose-http2
'use strict';
const {
fixturesDir,
mustCall,
mustNotCall
} = require('../common');
const { strictEqual } = require('assert');
const { join } = require('path');
const { readFileSync } = require('fs');
const { createSecureContext } = require('tls');
const { createSecureServer, connect } = require('http2');
const { get } = require('https');
const { parse } = require('url');
const { connect: tls } = require('tls');
const countdown = (count, done) => () => --count === 0 && done();
function loadKey(keyname) {
return readFileSync(join(fixturesDir, 'keys', keyname));
}
const key = loadKey('agent8-key.pem');
const cert = loadKey('agent8-cert.pem');
const ca = loadKey('fake-startcom-root-cert.pem');
const clientOptions = { secureContext: createSecureContext({ ca }) };
function onRequest(request, response) {
const { socket: { alpnProtocol } } = request.httpVersion === '2.0' ?
request.stream.session : request;
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({
alpnProtocol,
httpVersion: request.httpVersion
}));
}
function onSession(session) {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'https',
':authority': `localhost:${this.server.address().port}`
};
const request = session.request(headers);
request.on('response', mustCall((headers) => {
strictEqual(headers[':status'], 200);
strictEqual(headers['content-type'], 'application/json');
}));
request.setEncoding('utf8');
let raw = '';
request.on('data', (chunk) => { raw += chunk; });
request.on('end', mustCall(() => {
const { alpnProtocol, httpVersion } = JSON.parse(raw);
strictEqual(alpnProtocol, 'h2');
strictEqual(httpVersion, '2.0');
session.destroy();
this.cleanup();
}));
request.end();
}
// HTTP/2 & HTTP/1.1 server
{
const server = createSecureServer(
{ cert, key, allowHTTP1: true },
mustCall(onRequest, 2)
);
server.listen(0);
server.on('listening', mustCall(() => {
const { port } = server.address();
const origin = `https://localhost:${port}`;
const cleanup = countdown(2, () => server.close());
// HTTP/2 client
connect(
origin,
clientOptions,
mustCall(onSession.bind({ cleanup, server }))
);
// HTTP/1.1 client
get(
Object.assign(parse(origin), clientOptions),
mustCall((response) => {
strictEqual(response.statusCode, 200);
strictEqual(response.statusMessage, 'OK');
strictEqual(response.headers['content-type'], 'application/json');
response.setEncoding('utf8');
let raw = '';
response.on('data', (chunk) => { raw += chunk; });
response.on('end', mustCall(() => {
const { alpnProtocol, httpVersion } = JSON.parse(raw);
strictEqual(alpnProtocol, false);
strictEqual(httpVersion, '1.1');
cleanup();
}));
})
);
}));
}
// HTTP/2-only server
{
const server = createSecureServer(
{ cert, key },
mustCall(onRequest)
);
server.on('unknownProtocol', mustCall((socket) => {
socket.destroy();
}, 2));
server.listen(0);
server.on('listening', mustCall(() => {
const { port } = server.address();
const origin = `https://localhost:${port}`;
const cleanup = countdown(3, () => server.close());
// HTTP/2 client
connect(
origin,
clientOptions,
mustCall(onSession.bind({ cleanup, server }))
);
// HTTP/1.1 client
get(Object.assign(parse(origin), clientOptions), mustNotCall())
.on('error', mustCall(cleanup));
// Incompatible ALPN TLS client
tls(Object.assign({ port, ALPNProtocols: ['fake'] }, clientOptions))
.on('error', mustCall(cleanup));
}));
}

View File

@ -0,0 +1,85 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
const status101regex =
/^HTTP status code 101 \(Switching Protocols\) is forbidden in HTTP\/2$/;
const afterRespondregex =
/^Cannot specify additional headers after response initiated$/;
function onStream(stream, headers, flags) {
assert.throws(() => stream.additionalHeaders({ ':status': 201 }),
common.expectsError({
code: 'ERR_HTTP2_INVALID_INFO_STATUS',
type: RangeError,
message: /^Invalid informational status code: 201$/
}));
assert.throws(() => stream.additionalHeaders({ ':status': 101 }),
common.expectsError({
code: 'ERR_HTTP2_STATUS_101',
type: Error,
message: status101regex
}));
// Can send more than one
stream.additionalHeaders({ ':status': 100 });
stream.additionalHeaders({ ':status': 100 });
stream.respond({
'content-type': 'text/html',
':status': 200
});
assert.throws(() => stream.additionalHeaders({ abc: 123 }),
common.expectsError({
code: 'ERR_HTTP2_HEADERS_AFTER_RESPOND',
type: Error,
message: afterRespondregex
}));
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/'});
// The additionalHeaders method does not exist on client stream
assert.strictEqual(req.additionalHeaders, undefined);
// Additional informational headers
req.on('headers', common.mustCall((headers) => {
assert.notStrictEqual(headers, undefined);
assert.strictEqual(headers[':status'], 100);
}, 2));
// Response headers
req.on('response', common.mustCall((headers) => {
assert.notStrictEqual(headers, undefined);
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['content-type'], 'text/html');
}));
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,67 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const {
HTTP2_HEADER_METHOD,
HTTP2_HEADER_STATUS,
HTTP2_HEADER_PATH,
HTTP2_METHOD_POST
} = h2.constants;
// Only allow one stream to be open at a time
const server = h2.createServer({ settings: { maxConcurrentStreams: 1 }});
// The stream handler must be called only once
server.on('stream', common.mustCall((stream) => {
stream.respond({ [HTTP2_HEADER_STATUS]: 200 });
stream.end('hello world');
}));
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
let reqs = 2;
function onEnd() {
if (--reqs === 0) {
server.close();
client.destroy();
}
}
client.on('remoteSettings', common.mustCall((settings) => {
assert.strictEqual(settings.maxConcurrentStreams, 1);
}));
// This one should go through with no problems
const req1 = client.request({
[HTTP2_HEADER_PATH]: '/',
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST
});
req1.on('aborted', common.mustNotCall());
req1.on('response', common.mustCall());
req1.resume();
req1.on('end', onEnd);
req1.end();
// This one should be aborted
const req2 = client.request({
[HTTP2_HEADER_PATH]: '/',
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST
});
req2.on('aborted', common.mustCall());
req2.on('response', common.mustNotCall());
req2.resume();
req2.on('end', onEnd);
req2.on('error', common.mustCall(common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 7'
})));
}));

View File

@ -0,0 +1,48 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
const methods = [undefined, 'GET', 'POST', 'PATCH', 'FOO', 'A B C'];
let expected = methods.length;
// we use the lower-level API here
server.on('stream', common.mustCall(onStream, expected));
function onStream(stream, headers, flags) {
const method = headers[':method'];
assert.notStrictEqual(method, undefined);
assert(methods.includes(method), `method ${method} not included`);
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const headers = { ':path': '/' };
methods.forEach((method) => {
headers[':method'] = method;
const req = client.request(headers);
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
if (--expected === 0) {
server.close();
client.destroy();
}
}));
req.end();
});
}));

View File

@ -0,0 +1,61 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
[
':path',
':authority',
':method',
':scheme'
].forEach((i) => {
assert.throws(() => stream.respond({[i]: '/'}),
common.expectsError({
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER'
}));
});
stream.respond({
'content-type': 'text/html',
':status': 200
});
// This will cause an error to be emitted on the stream because
// using a pseudo-header in a trailer is forbidden.
stream.on('fetchTrailers', (trailers) => {
trailers[':status'] = 'bar';
});
stream.on('error', common.expectsError({
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER'
}));
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,58 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
stream.respond();
stream.end();
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let remaining = 3;
function maybeClose() {
if (--remaining === 0) {
server.close();
client.destroy();
}
}
// Request 1 will fail because there are two content-length header values
const req = client.request({
':method': 'POST',
'content-length': 1,
'Content-Length': 2
});
req.on('error', common.expectsError({
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
type: Error,
message: 'Header field "content-length" must have only a single value'
}));
req.on('error', common.mustCall(maybeClose));
req.end('a');
// Request 2 will succeed
const req2 = client.request({
':method': 'POST',
'content-length': 1
});
req2.resume();
req2.on('end', common.mustCall(maybeClose));
req2.end('a');
// Request 3 will fail because nghttp2 does not allow the content-length
// header to be set for non-payload bearing requests...
const req3 = client.request({ 'content-length': 1});
req3.resume();
req3.on('end', common.mustCall(maybeClose));
req3.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 1'
}));
}));

View File

@ -0,0 +1,60 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
const src = Object.create(null);
src.accept = [ 'abc', 'def' ];
src.Accept = 'ghijklmnop';
src['www-authenticate'] = 'foo';
src['WWW-Authenticate'] = 'bar';
src['WWW-AUTHENTICATE'] = 'baz';
src['proxy-authenticate'] = 'foo';
src['Proxy-Authenticate'] = 'bar';
src['PROXY-AUTHENTICATE'] = 'baz';
src['x-foo'] = 'foo';
src['X-Foo'] = 'bar';
src['X-FOO'] = 'baz';
src.constructor = 'foo';
src.Constructor = 'bar';
src.CONSTRUCTOR = 'baz';
// eslint-disable-next-line no-proto
src['__proto__'] = 'foo';
src['__PROTO__'] = 'bar';
src['__Proto__'] = 'baz';
function checkHeaders(headers) {
assert.deepStrictEqual(headers['accept'],
[ 'abc', 'def', 'ghijklmnop' ]);
assert.deepStrictEqual(headers['www-authenticate'],
[ 'foo', 'bar', 'baz' ]);
assert.deepStrictEqual(headers['proxy-authenticate'],
[ 'foo', 'bar', 'baz' ]);
assert.deepStrictEqual(headers['x-foo'], [ 'foo', 'bar', 'baz' ]);
assert.deepStrictEqual(headers['constructor'], [ 'foo', 'bar', 'baz' ]);
// eslint-disable-next-line no-proto
assert.deepStrictEqual(headers['__proto__'], [ 'foo', 'bar', 'baz' ]);
}
server.on('stream', common.mustCall((stream, headers) => {
assert.strictEqual(headers[':path'], '/');
assert.strictEqual(headers[':scheme'], 'http');
assert.strictEqual(headers[':method'], 'GET');
checkHeaders(headers);
stream.respond(src);
stream.end();
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request(src);
req.on('response', common.mustCall(checkHeaders));
req.on('streamClosed', common.mustCall(() => {
server.close();
client.destroy();
}));
}));

View File

@ -0,0 +1,59 @@
// Flags: --expose-http2
'use strict';
// Tests opening 100 concurrent simultaneous uploading streams over a single
// connection and makes sure that the data for each is appropriately echoed.
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
const count = 100;
server.on('stream', common.mustCall((stream) => {
stream.respond();
stream.pipe(stream);
}, count));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let remaining = count;
function maybeClose() {
if (--remaining === 0) {
server.close();
client.destroy();
}
}
function doRequest() {
const req = client.request({ ':method': 'POST '});
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => data += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'abcdefghij');
maybeClose();
}));
let n = 0;
function writeChunk() {
if (n < 10) {
req.write(String.fromCharCode(97 + n));
setTimeout(writeChunk, 10);
} else {
req.end();
}
n++;
}
writeChunk();
}
for (let n = 0; n < count; n++)
doRequest();
}));

View File

@ -0,0 +1,8 @@
// The --expose-http2 flag is not set
'use strict';
require('../common');
const assert = require('assert');
assert.throws(() => require('http2'),
/^Error: Cannot find module 'http2'$/);

View File

@ -0,0 +1,48 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustNotCall());
server.listen(0);
server.on('listening', common.mustCall(() => {
// Setting the maxSendHeaderBlockLength, then attempting to send a
// headers block that is too big should cause a 'meError' to
// be emitted, and will cause the stream to be shutdown.
const options = {
maxSendHeaderBlockLength: 10
};
const client = h2.connect(`http://localhost:${server.address().port}`,
options);
const req = client.request({ ':path': '/' });
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.on('frameError', common.mustCall((type, code) => {
assert.strictEqual(code, h2.constants.NGHTTP2_ERR_FRAME_SIZE_ERROR);
}));
req.on('error', common.mustCall(common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 7'
})));
req.end();
}));

View File

@ -0,0 +1,73 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall((stream) => {
stream.respond({ ':status': 200 });
// The first pushStream will complete as normal
stream.pushStream({
':scheme': 'http',
':path': '/foobar',
':authority': `localhost:${server.address().port}`,
}, common.mustCall((pushedStream) => {
pushedStream.respond({ ':status': 200 });
pushedStream.end();
pushedStream.on('aborted', common.mustNotCall());
}));
// The second pushStream will be aborted because the client
// will reject it due to the maxReservedRemoteStreams option
// being set to only 1
stream.pushStream({
':scheme': 'http',
':path': '/foobar',
':authority': `localhost:${server.address().port}`,
}, common.mustCall((pushedStream) => {
pushedStream.respond({ ':status': 200 });
pushedStream.on('aborted', common.mustCall());
pushedStream.on('error', common.mustCall(common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 8'
})));
}));
stream.end('hello world');
}));
server.listen(0);
server.on('listening', common.mustCall(() => {
const options = {
maxReservedRemoteStreams: 1
};
const client = h2.connect(`http://localhost:${server.address().port}`,
options);
const req = client.request({ ':path': '/' });
// Because maxReservedRemoteStream is 1, the stream event
// must only be emitted once, even tho the server sends
// two push streams.
client.on('stream', common.mustCall((stream) => {
stream.resume();
stream.on('end', common.mustCall());
}));
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,50 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const { PADDING_STRATEGY_CALLBACK } = h2.constants;
function selectPadding(frameLen, max) {
assert.strictEqual(typeof frameLen, 'number');
assert.strictEqual(typeof max, 'number');
assert(max >= frameLen);
return max;
}
// selectPadding will be called three times:
// 1. For the client request headers frame
// 2. For the server response headers frame
// 3. For the server response data frame
const options = {
paddingStrategy: PADDING_STRATEGY_CALLBACK,
selectPadding: common.mustCall(selectPadding, 3)
};
const server = h2.createServer(options);
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`,
options);
const req = client.request({ ':path': '/' });
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,60 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onPriority(stream, parent, weight, exclusive) {
assert.strictEqual(stream, 1);
assert.strictEqual(parent, 0);
assert.strictEqual(weight, 1);
assert.strictEqual(exclusive, false);
}
function onStream(stream, headers, flags) {
stream.priority({
parent: 0,
weight: 1,
exclusive: false
});
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('priority', common.mustCall(onPriority));
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/'});
client.on('connect', () => {
req.priority({
parent: 0,
weight: 1,
exclusive: false
});
});
req.on('priority', common.mustCall(onPriority));
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,41 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const assert = require('assert');
const path = require('path');
const {
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_STATUS
} = http2.constants;
const fname = path.resolve(common.fixturesDir, 'elipses.txt');
const server = http2.createServer();
server.on('stream', (stream) => {
assert.throws(() => {
stream.respondWithFile(fname, {
[HTTP2_HEADER_STATUS]: 204,
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
});
}, common.expectsError({
code: 'ERR_HTTP2_PAYLOAD_FORBIDDEN',
type: Error,
message: 'Responses with 204 status must not have a payload'
}));
stream.respond({});
stream.end();
});
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall());
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
client.destroy();
server.close();
}));
req.end();
});

View File

@ -0,0 +1,44 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const assert = require('assert');
const path = require('path');
const {
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_STATUS
} = http2.constants;
const fname = path.resolve(common.fixturesDir, 'elipses.txt');
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFile(fname, {
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
}, {
statCheck(stat, headers) {
// abort the send and return a 304 Not Modified instead
stream.respond({ [HTTP2_HEADER_STATUS]: 304 });
return false;
}
});
});
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[HTTP2_HEADER_STATUS], 304);
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE, undefined]);
}));
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
client.destroy();
server.close();
}));
req.end();
});

View File

@ -0,0 +1,23 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const path = require('path');
const fname = path.resolve(common.fixturesDir, 'elipses.txt');
const server = http2.createServer(common.mustCall((request, response) => {
response.stream.respondWithFile(fname);
}));
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall());
req.on('end', common.mustCall(() => {
client.destroy();
server.close();
}));
req.end();
req.resume();
});

View File

@ -0,0 +1,37 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const assert = require('assert');
const {
NGHTTP2_INTERNAL_ERROR
} = http2.constants;
const errorCheck = common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: `Stream closed with error code ${NGHTTP2_INTERNAL_ERROR}`
}, 2);
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFD(common.firstInvalidFD());
stream.on('error', common.mustCall(errorCheck));
});
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall());
req.on('error', common.mustCall(errorCheck));
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
assert.strictEqual(req.rstCode, NGHTTP2_INTERNAL_ERROR);
client.destroy();
server.close();
}));
req.end();
});

View File

@ -0,0 +1,46 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const {
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_CONTENT_LENGTH
} = http2.constants;
const fname = path.resolve(common.fixturesDir, 'elipses.txt');
const data = fs.readFileSync(fname);
const stat = fs.statSync(fname);
const fd = fs.openSync(fname, 'r');
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFD(fd, {
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain',
[HTTP2_HEADER_CONTENT_LENGTH]: stat.size,
});
});
server.on('close', common.mustCall(() => fs.closeSync(fd)));
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], data.length);
}));
req.setEncoding('utf8');
let check = '';
req.on('data', (chunk) => check += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8'));
client.destroy();
server.close();
}));
req.end();
});

View File

@ -0,0 +1,81 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const {
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_LAST_MODIFIED
} = http2.constants;
const fname = path.resolve(common.fixturesDir, 'elipses.txt');
const data = fs.readFileSync(fname);
const stat = fs.statSync(fname);
const fd = fs.openSync(fname, 'r');
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respond({});
stream.end();
stream.pushStream({
':path': '/file.txt',
':method': 'GET'
}, (stream) => {
stream.respondWithFD(fd, {
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain',
[HTTP2_HEADER_CONTENT_LENGTH]: stat.size,
[HTTP2_HEADER_LAST_MODIFIED]: stat.mtime.toUTCString()
});
});
stream.end();
});
server.on('close', common.mustCall(() => fs.closeSync(fd)));
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let expected = 2;
function maybeClose() {
if (--expected === 0) {
server.close();
client.destroy();
}
}
const req = client.request({});
req.on('response', common.mustCall());
client.on('stream', common.mustCall((stream, headers) => {
stream.on('push', common.mustCall((headers) => {
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], data.length);
assert.strictEqual(headers[HTTP2_HEADER_LAST_MODIFIED],
stat.mtime.toUTCString());
}));
stream.setEncoding('utf8');
let check = '';
stream.on('data', (chunk) => check += chunk);
stream.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8'));
maybeClose();
}));
}));
req.resume();
req.on('end', maybeClose);
req.end();
});

View File

@ -0,0 +1,51 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const {
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_LAST_MODIFIED
} = http2.constants;
const fname = path.resolve(common.fixturesDir, 'elipses.txt');
const data = fs.readFileSync(fname);
const stat = fs.statSync(fname);
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFile(fname, {
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
}, {
statCheck(stat, headers) {
headers[HTTP2_HEADER_LAST_MODIFIED] = stat.mtime.toUTCString();
headers[HTTP2_HEADER_CONTENT_LENGTH] = stat.size;
}
});
});
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], data.length);
assert.strictEqual(headers[HTTP2_HEADER_LAST_MODIFIED],
stat.mtime.toUTCString());
}));
req.setEncoding('utf8');
let check = '';
req.on('data', (chunk) => check += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8'));
client.destroy();
server.close();
}));
req.end();
});

View File

@ -0,0 +1,75 @@
// Flags: --expose-http2
'use strict';
// Response splitting is no longer an issue with HTTP/2. The underlying
// nghttp2 implementation automatically strips out the header values that
// contain invalid characters.
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const { URL } = require('url');
// Response splitting example, credit: Amit Klein, Safebreach
const str = '/welcome?lang=bar%c4%8d%c4%8aContent­Length:%200%c4%8d%c4%8a%c' +
'4%8d%c4%8aHTTP/1.1%20200%20OK%c4%8d%c4%8aContent­Length:%202' +
'0%c4%8d%c4%8aLast­Modified:%20Mon,%2027%20Oct%202003%2014:50:18' +
'%20GMT%c4%8d%c4%8aContent­Type:%20text/html%c4%8d%c4%8a%c4%8' +
'd%c4%8a%3chtml%3eGotcha!%3c/html%3e';
// Response splitting example, credit: Сковорода Никита Андреевич (@ChALkeR)
const x = 'fooഊSet-Cookie: foo=barഊഊ<script>alert("Hi!")</script>';
const y = 'foo⠊Set-Cookie: foo=bar';
let remaining = 3;
function makeUrl(headers) {
return `${headers[':scheme']}://${headers[':authority']}${headers[':path']}`;
}
const server = http2.createServer();
server.on('stream', common.mustCall((stream, headers) => {
const obj = Object.create(null);
switch (remaining--) {
case 3:
const url = new URL(makeUrl(headers));
obj[':status'] = 302;
obj.Location = `/foo?lang=${url.searchParams.get('lang')}`;
break;
case 2:
obj.foo = x;
break;
case 1:
obj.foo = y;
break;
}
stream.respond(obj);
stream.end();
}, 3));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
function maybeClose() {
if (remaining === 0) {
server.close();
client.destroy();
}
}
function doTest(path, key, expected) {
const req = client.request({ ':path': path });
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers.foo, undefined);
assert.strictEqual(headers.location, undefined);
}));
req.resume();
req.on('end', common.mustCall(maybeClose));
}
doTest(str, 'location', str);
doTest('/', 'foo', x);
doTest('/', 'foo', y);
}));

View File

@ -0,0 +1,82 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const fs = require('fs');
const path = require('path');
const tls = require('tls');
const ajs_data = fs.readFileSync(path.resolve(common.fixturesDir, 'a.js'),
'utf8');
const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_STATUS
} = http2.constants;
function loadKey(keyname) {
return fs.readFileSync(
path.join(common.fixturesDir, 'keys', keyname), 'binary');
}
const key = loadKey('agent8-key.pem');
const cert = loadKey('agent8-cert.pem');
const ca = loadKey('fake-startcom-root-cert.pem');
const server = http2.createSecureServer({key, cert});
server.on('stream', (stream, headers) => {
const name = headers[HTTP2_HEADER_PATH].slice(1);
const file = path.resolve(common.fixturesDir, name);
fs.stat(file, (err, stat) => {
if (err != null || stat.isDirectory()) {
stream.respond({ [HTTP2_HEADER_STATUS]: 404 });
stream.end();
} else {
stream.respond({ [HTTP2_HEADER_STATUS]: 200 });
const str = fs.createReadStream(file);
str.pipe(stream);
}
});
});
server.listen(8000, () => {
const secureContext = tls.createSecureContext({ca});
const client = http2.connect(`https://localhost:${server.address().port}`,
{ secureContext });
let remaining = 2;
function maybeClose() {
if (--remaining === 0) {
client.destroy();
server.close();
}
}
// Request for a file that does exist, response is 200
const req1 = client.request({ [HTTP2_HEADER_PATH]: '/a.js' },
{ endStream: true });
req1.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[HTTP2_HEADER_STATUS], 200);
}));
let req1_data = '';
req1.setEncoding('utf8');
req1.on('data', (chunk) => req1_data += chunk);
req1.on('end', common.mustCall(() => {
assert.strictEqual(req1_data, ajs_data);
maybeClose();
}));
// Request for a file that does not exist, response is 404
const req2 = client.request({ [HTTP2_HEADER_PATH]: '/does_not_exist' },
{ endStream: true });
req2.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[HTTP2_HEADER_STATUS], 404);
}));
req2.on('data', common.mustNotCall());
req2.on('end', common.mustCall(() => maybeClose()));
});

View File

@ -0,0 +1,38 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.session.destroy();
assert.throws(() => stream.additionalHeaders({}),
common.expectsError({
code: 'ERR_HTTP2_INVALID_STREAM',
message: /^The stream has been destroyed$/
}));
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,38 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.session.destroy();
assert.throws(() => stream.pushStream({}, common.mustNotCall()),
common.expectsError({
code: 'ERR_HTTP2_INVALID_STREAM',
message: /^The stream has been destroyed$/
}));
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,38 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.session.destroy();
assert.throws(() => stream.respond({}),
common.expectsError({
code: 'ERR_HTTP2_INVALID_STREAM',
message: /^The stream has been destroyed$/
}));
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,38 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.session.destroy();
assert.throws(() => stream.write('data'),
common.expectsError({
code: 'ERR_HTTP2_INVALID_STREAM',
type: Error
}));
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,53 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
server.on('session', common.mustCall((session) => {
// Verify that the settings disabling push is received
session.on('remoteSettings', common.mustCall((settings) => {
assert.strictEqual(settings.enablePush, false);
}));
}));
server.on('stream', common.mustCall((stream) => {
// The client has disabled push streams, so pushAllowed must be false,
// and pushStream() must throw.
assert.strictEqual(stream.pushAllowed, false);
assert.throws(() => {
stream.pushStream({
':scheme': 'http',
':path': '/foobar',
':authority': `localhost:${server.address().port}`,
}, common.mustNotCall());
}, common.expectsError({
code: 'ERR_HTTP2_PUSH_DISABLED',
type: Error
}));
stream.respond({ ':status': 200 });
stream.end('test');
}));
server.listen(0, common.mustCall(() => {
const options = {settings: { enablePush: false }};
const client = http2.connect(`http://localhost:${server.address().port}`,
options);
const req = client.request({ ':path': '/' });
// Because push stream sre disabled, this must not be called.
client.on('stream', common.mustNotCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,58 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', common.mustCall((stream, headers) => {
const port = server.address().port;
if (headers[':path'] === '/') {
stream.pushStream({
':scheme': 'http',
':path': '/foobar',
':authority': `localhost:${port}`,
}, (push, headers) => {
push.respond({
'content-type': 'text/html',
':status': 200,
'x-push-data': 'pushed by server',
});
push.end('pushed by server data');
stream.end('test');
});
}
stream.respond({
'content-type': 'text/html',
':status': 200
});
}));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const headers = { ':path': '/' };
const client = http2.connect(`http://localhost:${port}`);
const req = client.request(headers);
client.on('stream', common.mustCall((stream, headers) => {
assert.strictEqual(headers[':scheme'], 'http');
assert.strictEqual(headers[':path'], '/foobar');
assert.strictEqual(headers[':authority'], `localhost:${port}`);
stream.on('push', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['content-type'], 'text/html');
assert.strictEqual(headers['x-push-data'], 'pushed by server');
}));
}));
let data = '';
req.on('data', common.mustCall((d) => data += d));
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'test');
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,45 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.rstStream();
assert.throws(() => {
stream.additionalHeaders({
':status': 123,
abc: 123
});
}, common.expectsError({
code: 'ERR_HTTP2_INVALID_STREAM',
message: /^The stream has been destroyed$/
}));
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.on('headers', common.mustNotCall());
req.on('streamClosed', common.mustCall((code) => {
assert.strictEqual(h2.constants.NGHTTP2_NO_ERROR, code);
server.close();
client.destroy();
}));
req.on('response', common.mustNotCall());
}));

View File

@ -0,0 +1,72 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const {
HTTP2_HEADER_METHOD,
HTTP2_HEADER_PATH,
HTTP2_METHOD_POST,
NGHTTP2_CANCEL,
NGHTTP2_NO_ERROR,
NGHTTP2_PROTOCOL_ERROR,
NGHTTP2_REFUSED_STREAM,
NGHTTP2_INTERNAL_ERROR
} = http2.constants;
const errCheck = common.expectsError({ code: 'ERR_HTTP2_STREAM_ERROR' }, 8);
function checkRstCode(rstMethod, expectRstCode) {
const server = http2.createServer();
server.on('stream', (stream, headers, flags) => {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.write('test');
if (rstMethod === 'rstStream')
stream[rstMethod](expectRstCode);
else
stream[rstMethod]();
if (expectRstCode > NGHTTP2_NO_ERROR) {
stream.on('error', common.mustCall(errCheck));
}
});
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const client = http2.connect(`http://localhost:${port}`);
const headers = {
[HTTP2_HEADER_PATH]: '/',
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST
};
const req = client.request(headers);
req.setEncoding('utf8');
req.on('streamClosed', common.mustCall((actualRstCode) => {
assert.strictEqual(
expectRstCode, actualRstCode, `${rstMethod} is not match rstCode`);
server.close();
client.destroy();
}));
req.on('data', common.mustCall());
req.on('aborted', common.mustCall());
req.on('end', common.mustCall());
if (expectRstCode > NGHTTP2_NO_ERROR) {
req.on('error', common.mustCall(errCheck));
}
}));
}
checkRstCode('rstStream', NGHTTP2_NO_ERROR);
checkRstCode('rstWithNoError', NGHTTP2_NO_ERROR);
checkRstCode('rstWithProtocolError', NGHTTP2_PROTOCOL_ERROR);
checkRstCode('rstWithCancel', NGHTTP2_CANCEL);
checkRstCode('rstWithRefuse', NGHTTP2_REFUSED_STREAM);
checkRstCode('rstWithInternalError', NGHTTP2_INTERNAL_ERROR);

View File

@ -0,0 +1,36 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const body =
'<html><head></head><body><h1>this is some data</h2></body></html>';
const server = http2.createServer((req, res) => {
res.setHeader('foobar', 'baz');
res.setHeader('X-POWERED-BY', 'node-test');
res.end(body);
});
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const headers = { ':path': '/' };
const req = client.request(headers);
req.setEncoding('utf8');
req.on('response', common.mustCall(function(headers) {
assert.strictEqual(headers['foobar'], 'baz');
assert.strictEqual(headers['x-powered-by'], 'node-test');
}));
let data = '';
req.on('data', (d) => data += d);
req.on('end', () => {
assert.strictEqual(body, data);
server.close();
client.destroy();
});
req.end();
}));
server.on('error', common.mustNotCall());

View File

@ -0,0 +1,32 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
const session = stream.session;
stream.session.shutdown({graceful: true}, common.mustCall(() => {
session.destroy();
}));
stream.respond({});
stream.end('data');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':path': '/' });
req.resume();
req.on('end', common.mustCall(() => server.close()));
req.end();
}));

View File

@ -0,0 +1,57 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const assert = require('assert');
const {
HTTP2_HEADER_METHOD,
HTTP2_HEADER_PATH,
HTTP2_METHOD_POST
} = h2.constants;
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream) {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.write('test');
const socket = stream.session.socket;
// When the socket is destroyed, the close events must be triggered
// on the socket, server and session.
socket.on('close', common.mustCall());
server.on('close', common.mustCall());
stream.session.on('close', common.mustCall(() => server.close()));
// Also, the aborted event must be triggered on the stream
stream.on('aborted', common.mustCall());
assert.notStrictEqual(stream.session, undefined);
socket.destroy();
assert.strictEqual(stream.session, undefined);
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request({
[HTTP2_HEADER_PATH]: '/',
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST });
req.on('aborted', common.mustCall());
req.on('end', common.mustCall());
req.on('response', common.mustCall());
req.on('data', common.mustCall());
client.on('close', common.mustCall());
}));

View File

@ -0,0 +1,78 @@
// Flags: --expose-http2
'use strict';
// Tests the basic operation of creating a plaintext or TLS
// HTTP2 server. The server does not do anything at this point
// other than start listening.
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const path = require('path');
const tls = require('tls');
const net = require('net');
const fs = require('fs');
const options = {
key: fs.readFileSync(
path.resolve(common.fixturesDir, 'keys/agent2-key.pem')),
cert: fs.readFileSync(
path.resolve(common.fixturesDir, 'keys/agent2-cert.pem'))
};
// There should not be any throws
assert.doesNotThrow(() => {
const serverTLS = http2.createSecureServer(options, () => {});
serverTLS.listen(0, common.mustCall(() => serverTLS.close()));
// There should not be an error event reported either
serverTLS.on('error', common.mustNotCall());
});
// There should not be any throws
assert.doesNotThrow(() => {
const server = http2.createServer(options, common.mustNotCall());
server.listen(0, common.mustCall(() => server.close()));
// There should not be an error event reported either
server.on('error', common.mustNotCall());
});
// Test the plaintext server socket timeout
{
let client;
const server = http2.createServer();
server.on('timeout', common.mustCall(() => {
server.close();
if (client)
client.end();
}));
server.setTimeout(common.platformTimeout(1000));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
client = net.connect(port, common.mustCall());
}));
}
// Test the secure server socket timeout
{
let client;
const server = http2.createSecureServer(options);
server.on('timeout', common.mustCall(() => {
server.close();
if (client)
client.end();
}));
server.setTimeout(common.platformTimeout(1000));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
client = tls.connect({
port: port,
rejectUnauthorized: false,
ALPNProtocols: ['h2']
}, common.mustCall());
}));
}

View File

@ -0,0 +1,110 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
server.on('stream', common.mustCall(onStream));
function assertSettings(settings) {
assert.strictEqual(typeof settings, 'object');
assert.strictEqual(typeof settings.headerTableSize, 'number');
assert.strictEqual(typeof settings.enablePush, 'boolean');
assert.strictEqual(typeof settings.initialWindowSize, 'number');
assert.strictEqual(typeof settings.maxFrameSize, 'number');
assert.strictEqual(typeof settings.maxConcurrentStreams, 'number');
assert.strictEqual(typeof settings.maxHeaderListSize, 'number');
}
function onStream(stream, headers, flags) {
assertSettings(stream.session.localSettings);
assertSettings(stream.session.remoteSettings);
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`, {
settings: {
enablePush: false,
initialWindowSize: 123456
}
});
client.on('localSettings', common.mustCall((settings) => {
assert(settings);
assert.strictEqual(settings.enablePush, false);
assert.strictEqual(settings.initialWindowSize, 123456);
assert.strictEqual(settings.maxFrameSize, 16384);
}, 2));
client.on('remoteSettings', common.mustCall((settings) => {
assert(settings);
}));
const headers = { ':path': '/' };
const req = client.request(headers);
req.on('connect', common.mustCall(() => {
// pendingSettingsAck will be true if a SETTINGS frame
// has been sent but we are still waiting for an acknowledgement
assert(client.pendingSettingsAck);
}));
// State will only be valid after connect event is emitted
req.on('ready', common.mustCall(() => {
assert.doesNotThrow(() => {
client.settings({
maxHeaderListSize: 1
});
});
// Verify valid error ranges
[
['headerTableSize', -1],
['headerTableSize', 2 ** 32],
['initialWindowSize', -1],
['initialWindowSize', 2 ** 32],
['maxFrameSize', 16383],
['maxFrameSize', 2 ** 24],
['maxHeaderListSize', -1],
['maxHeaderListSize', 2 ** 32]
].forEach((i) => {
const settings = {};
settings[i[0]] = i[1];
assert.throws(() => client.settings(settings),
common.expectsError({
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
type: RangeError
}));
});
[1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
assert.throws(() => client.settings({enablePush: i}),
common.expectsError({
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
type: TypeError
}));
});
}));
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,97 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
// Test Stream State.
{
const state = stream.state;
assert.strictEqual(typeof state, 'object');
assert.strictEqual(typeof state.state, 'number');
assert.strictEqual(typeof state.weight, 'number');
assert.strictEqual(typeof state.sumDependencyWeight, 'number');
assert.strictEqual(typeof state.localClose, 'number');
assert.strictEqual(typeof state.remoteClose, 'number');
assert.strictEqual(typeof state.localWindowSize, 'number');
}
// Test Session State.
{
const state = stream.session.state;
assert.strictEqual(typeof state, 'object');
assert.strictEqual(typeof state.effectiveLocalWindowSize, 'number');
assert.strictEqual(typeof state.effectiveRecvDataLength, 'number');
assert.strictEqual(typeof state.nextStreamID, 'number');
assert.strictEqual(typeof state.localWindowSize, 'number');
assert.strictEqual(typeof state.lastProcStreamID, 'number');
assert.strictEqual(typeof state.remoteWindowSize, 'number');
assert.strictEqual(typeof state.outboundQueueSize, 'number');
assert.strictEqual(typeof state.deflateDynamicTableSize, 'number');
assert.strictEqual(typeof state.inflateDynamicTableSize, 'number');
}
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('hello world');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const headers = { ':path': '/' };
const req = client.request(headers);
// State will only be valid after connect event is emitted
req.on('connect', common.mustCall(() => {
// Test Stream State.
{
const state = req.state;
assert.strictEqual(typeof state, 'object');
assert.strictEqual(typeof state.state, 'number');
assert.strictEqual(typeof state.weight, 'number');
assert.strictEqual(typeof state.sumDependencyWeight, 'number');
assert.strictEqual(typeof state.localClose, 'number');
assert.strictEqual(typeof state.remoteClose, 'number');
assert.strictEqual(typeof state.localWindowSize, 'number');
}
// Test Session State
{
const state = req.session.state;
assert.strictEqual(typeof state, 'object');
assert.strictEqual(typeof state.effectiveLocalWindowSize, 'number');
assert.strictEqual(typeof state.effectiveRecvDataLength, 'number');
assert.strictEqual(typeof state.nextStreamID, 'number');
assert.strictEqual(typeof state.localWindowSize, 'number');
assert.strictEqual(typeof state.lastProcStreamID, 'number');
assert.strictEqual(typeof state.remoteWindowSize, 'number');
assert.strictEqual(typeof state.outboundQueueSize, 'number');
assert.strictEqual(typeof state.deflateDynamicTableSize, 'number');
assert.strictEqual(typeof state.inflateDynamicTableSize, 'number');
}
}));
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,59 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const http2 = require('http2');
const server = http2.createServer();
// Each of these headers must appear only once
const singles = [
'content-type',
'user-agent',
'referer',
'authorization',
'proxy-authorization',
'if-modified-since',
'if-unmodified-since',
'from',
'location',
'max-forwards'
];
server.on('stream', common.mustNotCall());
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let remaining = singles.length * 2;
function maybeClose() {
if (--remaining === 0) {
server.close();
client.destroy();
}
}
singles.forEach((i) => {
const req = client.request({
[i]: 'abc',
[i.toUpperCase()]: 'xyz'
});
req.on('error', common.expectsError({
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
type: Error,
message: `Header field "${i}" must have only a single value`
}));
req.on('error', common.mustCall(maybeClose));
const req2 = client.request({
[i]: ['abc', 'xyz']
});
req2.on('error', common.expectsError({
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
type: Error,
message: `Header field "${i}" must have only a single value`
}));
req2.on('error', common.mustCall(maybeClose));
});
}));

View File

@ -0,0 +1,40 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
function expectsError(code) {
return common.expectsError({
code: 'ERR_HTTP2_STATUS_INVALID',
type: RangeError,
message: `Invalid status code: ${code}`
});
}
server.on('stream', common.mustCall((stream) => {
// Anything lower than 100 and greater than 599 is rejected
[ 99, 700, 1000 ].forEach((i) => {
assert.throws(() => stream.respond({ ':status': i }), expectsError(i));
});
stream.respond();
stream.end();
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
}));
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
}));

View File

@ -0,0 +1,40 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const codes = [ 200, 202, 300, 400, 404, 451, 500 ];
let test = 0;
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
const status = codes[test++];
stream.respond({ ':status': status }, { endStream: true });
}, 7));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let remaining = codes.length;
function maybeClose() {
if (--remaining === 0) {
client.destroy();
server.close();
}
}
function doTest(expected) {
const req = client.request();
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], expected);
}));
req.resume();
req.on('end', common.mustCall(maybeClose));
}
for (let n = 0; n < codes.length; n++)
doTest(codes[n]);
}));

View File

@ -0,0 +1,32 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const h2 = require('http2');
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall((stream) => {
stream.setTimeout(1, common.mustCall(() => {
stream.respond({':status': 200});
stream.end('hello world');
}));
}));
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
client.setTimeout(1, common.mustCall(() => {
const req = client.request({ ':path': '/' });
req.setTimeout(1, common.mustCall(() => {
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));
}));
}));

View File

@ -0,0 +1,60 @@
// Flags: --expose-http2
'use strict';
// Tests that attempting to send too many non-acknowledged
// settings frames will result in a throw.
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const maxPendingAck = 2;
const server = h2.createServer({ maxPendingAck: maxPendingAck + 1 });
let clients = 2;
function doTest(session) {
for (let n = 0; n < maxPendingAck; n++)
assert.doesNotThrow(() => session.settings({ enablePush: false }));
assert.throws(() => session.settings({ enablePush: false }),
common.expectsError({
code: 'ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
type: Error
}));
}
server.on('stream', common.mustNotCall());
server.once('session', common.mustCall((session) => doTest(session)));
server.listen(0);
const closeServer = common.mustCall(() => {
if (--clients === 0)
server.close();
}, clients);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`,
{ maxPendingAck: maxPendingAck + 1 });
let remaining = maxPendingAck + 1;
client.on('close', closeServer);
client.on('localSettings', common.mustCall(() => {
if (--remaining <= 0) {
client.destroy();
}
}, maxPendingAck + 1));
client.on('connect', common.mustCall(() => doTest(client)));
}));
// Setting maxPendingAck to 0, defaults it to 1
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`,
{ maxPendingAck: 0 });
client.on('close', closeServer);
client.on('localSettings', common.mustCall(() => {
client.destroy();
}));
}));

View File

@ -0,0 +1,44 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const h2 = require('http2');
const body =
'<html><head></head><body><h1>this is some data</h2></body></html>';
const trailerKey = 'test-trailer';
const trailerValue = 'testing';
const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.on('fetchTrailers', function(trailers) {
trailers[trailerKey] = trailerValue;
});
stream.end(body);
}
server.listen(0);
server.on('listening', common.mustCall(function() {
const client = h2.connect(`http://localhost:${this.address().port}`);
const req = client.request({':path': '/'});
req.on('data', common.mustCall());
req.on('trailers', common.mustCall((headers) => {
assert.strictEqual(headers[trailerKey], trailerValue);
req.end();
}));
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,43 @@
// Flags: --expose-internals --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const {
assertIsObject,
assertWithinRange,
} = require('internal/http2/util');
[
undefined,
{},
Object.create(null),
new Date(),
new (class Foo {})()
].forEach((i) => {
assert.doesNotThrow(() => assertIsObject(i, 'foo', 'object'));
});
[
1,
false,
'hello',
NaN,
Infinity,
[],
[{}]
].forEach((i) => {
assert.throws(() => assertIsObject(i, 'foo', 'object'),
common.expectsError({
code: 'ERR_INVALID_ARG_TYPE',
message: /^The "foo" argument must be of type object$/
}));
});
assert.doesNotThrow(() => assertWithinRange('foo', 1, 0, 2));
assert.throws(() => assertWithinRange('foo', 1, 2, 3),
common.expectsError({
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
message: /^Invalid value for setting "foo": 1$/
}));

View File

@ -0,0 +1,248 @@
// Flags: --expose-internals --expose-http2
'use strict';
// Tests the internal utility function that is used to prepare headers
// to pass to the internal binding layer.
const common = require('../common');
const assert = require('assert');
const { mapToHeaders } = require('internal/http2/util');
const {
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_AGE,
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_HEADER_CONTENT_LANGUAGE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_CONTENT_LOCATION,
HTTP2_HEADER_CONTENT_MD5,
HTTP2_HEADER_CONTENT_RANGE,
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_DATE,
HTTP2_HEADER_ETAG,
HTTP2_HEADER_EXPIRES,
HTTP2_HEADER_FROM,
HTTP2_HEADER_IF_MATCH,
HTTP2_HEADER_IF_MODIFIED_SINCE,
HTTP2_HEADER_IF_NONE_MATCH,
HTTP2_HEADER_IF_RANGE,
HTTP2_HEADER_IF_UNMODIFIED_SINCE,
HTTP2_HEADER_LAST_MODIFIED,
HTTP2_HEADER_MAX_FORWARDS,
HTTP2_HEADER_PROXY_AUTHORIZATION,
HTTP2_HEADER_RANGE,
HTTP2_HEADER_REFERER,
HTTP2_HEADER_RETRY_AFTER,
HTTP2_HEADER_USER_AGENT,
HTTP2_HEADER_ACCEPT_CHARSET,
HTTP2_HEADER_ACCEPT_ENCODING,
HTTP2_HEADER_ACCEPT_LANGUAGE,
HTTP2_HEADER_ACCEPT_RANGES,
HTTP2_HEADER_ACCEPT,
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
HTTP2_HEADER_ALLOW,
HTTP2_HEADER_CACHE_CONTROL,
HTTP2_HEADER_CONTENT_DISPOSITION,
HTTP2_HEADER_COOKIE,
HTTP2_HEADER_EXPECT,
HTTP2_HEADER_LINK,
HTTP2_HEADER_PREFER,
HTTP2_HEADER_PROXY_AUTHENTICATE,
HTTP2_HEADER_REFRESH,
HTTP2_HEADER_SERVER,
HTTP2_HEADER_SET_COOKIE,
HTTP2_HEADER_STRICT_TRANSPORT_SECURITY,
HTTP2_HEADER_VARY,
HTTP2_HEADER_VIA,
HTTP2_HEADER_WWW_AUTHENTICATE,
HTTP2_HEADER_CONNECTION,
HTTP2_HEADER_UPGRADE,
HTTP2_HEADER_HTTP2_SETTINGS,
HTTP2_HEADER_TE,
HTTP2_HEADER_TRANSFER_ENCODING,
HTTP2_HEADER_HOST,
HTTP2_HEADER_KEEP_ALIVE,
HTTP2_HEADER_PROXY_CONNECTION
} = process.binding('http2').constants;
{
const headers = {
'abc': 1,
':status': 200,
':path': 'abc',
'xyz': [1, '2', { toString() { return '3'; } }, 4],
'foo': [],
'BAR': [1]
};
assert.deepStrictEqual(mapToHeaders(headers), [
[ ':path', 'abc' ],
[ ':status', '200' ],
[ 'abc', '1' ],
[ 'xyz', '1' ],
[ 'xyz', '2' ],
[ 'xyz', '3' ],
[ 'xyz', '4' ],
[ 'bar', '1' ]
]);
}
{
const headers = {
'abc': 1,
':path': 'abc',
':status': [200],
':authority': [],
'xyz': [1, 2, 3, 4]
};
assert.deepStrictEqual(mapToHeaders(headers), [
[ ':status', '200' ],
[ ':path', 'abc' ],
[ 'abc', '1' ],
[ 'xyz', '1' ],
[ 'xyz', '2' ],
[ 'xyz', '3' ],
[ 'xyz', '4' ]
]);
}
{
const headers = {
'abc': 1,
':path': 'abc',
'xyz': [1, 2, 3, 4],
'': 1,
':status': 200,
[Symbol('test')]: 1 // Symbol keys are ignored
};
assert.deepStrictEqual(mapToHeaders(headers), [
[ ':status', '200' ],
[ ':path', 'abc' ],
[ 'abc', '1' ],
[ 'xyz', '1' ],
[ 'xyz', '2' ],
[ 'xyz', '3' ],
[ 'xyz', '4' ]
]);
}
{
// Only own properties are used
const base = { 'abc': 1 };
const headers = Object.create(base);
headers[':path'] = 'abc';
headers.xyz = [1, 2, 3, 4];
headers.foo = [];
headers[':status'] = 200;
assert.deepStrictEqual(mapToHeaders(headers), [
[ ':status', '200' ],
[ ':path', 'abc' ],
[ 'xyz', '1' ],
[ 'xyz', '2' ],
[ 'xyz', '3' ],
[ 'xyz', '4' ]
]);
}
// The following are not allowed to have multiple values
[
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_AGE,
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_HEADER_CONTENT_LANGUAGE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_CONTENT_LOCATION,
HTTP2_HEADER_CONTENT_MD5,
HTTP2_HEADER_CONTENT_RANGE,
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_DATE,
HTTP2_HEADER_ETAG,
HTTP2_HEADER_EXPIRES,
HTTP2_HEADER_FROM,
HTTP2_HEADER_IF_MATCH,
HTTP2_HEADER_IF_MODIFIED_SINCE,
HTTP2_HEADER_IF_NONE_MATCH,
HTTP2_HEADER_IF_RANGE,
HTTP2_HEADER_IF_UNMODIFIED_SINCE,
HTTP2_HEADER_LAST_MODIFIED,
HTTP2_HEADER_MAX_FORWARDS,
HTTP2_HEADER_PROXY_AUTHORIZATION,
HTTP2_HEADER_RANGE,
HTTP2_HEADER_REFERER,
HTTP2_HEADER_RETRY_AFTER,
HTTP2_HEADER_USER_AGENT
].forEach((name) => {
const msg = `Header field "${name}" must have only a single value`;
common.expectsError({
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
message: msg
})(mapToHeaders({[name]: [1, 2, 3]}));
});
[
HTTP2_HEADER_ACCEPT_CHARSET,
HTTP2_HEADER_ACCEPT_ENCODING,
HTTP2_HEADER_ACCEPT_LANGUAGE,
HTTP2_HEADER_ACCEPT_RANGES,
HTTP2_HEADER_ACCEPT,
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
HTTP2_HEADER_ALLOW,
HTTP2_HEADER_CACHE_CONTROL,
HTTP2_HEADER_CONTENT_DISPOSITION,
HTTP2_HEADER_COOKIE,
HTTP2_HEADER_EXPECT,
HTTP2_HEADER_LINK,
HTTP2_HEADER_PREFER,
HTTP2_HEADER_PROXY_AUTHENTICATE,
HTTP2_HEADER_REFRESH,
HTTP2_HEADER_SERVER,
HTTP2_HEADER_SET_COOKIE,
HTTP2_HEADER_STRICT_TRANSPORT_SECURITY,
HTTP2_HEADER_VARY,
HTTP2_HEADER_VIA,
HTTP2_HEADER_WWW_AUTHENTICATE
].forEach((name) => {
assert(!(mapToHeaders({[name]: [1, 2, 3]}) instanceof Error), name);
});
const regex =
/^HTTP\/1 Connection specific headers are forbidden$/;
[
HTTP2_HEADER_CONNECTION,
HTTP2_HEADER_UPGRADE,
HTTP2_HEADER_HTTP2_SETTINGS,
HTTP2_HEADER_TE,
HTTP2_HEADER_TRANSFER_ENCODING,
HTTP2_HEADER_HOST,
HTTP2_HEADER_PROXY_CONNECTION,
HTTP2_HEADER_KEEP_ALIVE,
'Connection',
'Upgrade',
'HTTP2-Settings',
'TE',
'Transfer-Encoding',
'Proxy-Connection',
'Keep-Alive'
].forEach((name) => {
common.expectsError({
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
message: regex
})(mapToHeaders({[name]: 'abc'}));
});
assert(!(mapToHeaders({ te: 'trailers' }) instanceof Error));

View File

@ -0,0 +1,102 @@
// Flags: --expose-http2
'use strict';
// This test ensures that servers are able to send data independent of window
// size.
// TODO: This test makes large buffer allocations (128KiB) and should be tested
// on smaller / IoT platforms in case this poses problems for these targets.
const assert = require('assert');
const common = require('../common');
const h2 = require('http2');
// Given a list of buffers and an initial window size, have a server write
// each buffer to the HTTP2 Writable stream, and let the client verify that
// all of the bytes were sent correctly
function run(buffers, initialWindowSize) {
return new Promise((resolve, reject) => {
const expectedBuffer = Buffer.concat(buffers);
const server = h2.createServer();
server.on('stream', (stream) => {
let i = 0;
const writeToStream = () => {
const cont = () => {
i++;
if (i < buffers.length) {
setImmediate(writeToStream);
} else {
stream.end();
}
};
const drained = stream.write(buffers[i]);
if (drained) {
cont();
} else {
stream.once('drain', cont);
}
};
writeToStream();
});
server.listen(0);
server.on('listening', common.mustCall(function() {
const port = this.address().port;
const client =
h2.connect({
authority: 'localhost',
protocol: 'http:',
port
}, {
settings: {
initialWindowSize
}
}).on('connect', common.mustCall(() => {
const req = client.request({
':method': 'GET',
':path': '/'
});
const responses = [];
req.on('data', (data) => {
responses.push(data);
});
req.on('end', common.mustCall(() => {
const actualBuffer = Buffer.concat(responses);
assert.strictEqual(Buffer.compare(actualBuffer, expectedBuffer), 0);
// shut down
client.destroy();
server.close(() => {
resolve();
});
}));
req.end();
}));
}));
});
}
const bufferValueRange = [0, 1, 2, 3];
const buffersList = [
bufferValueRange.map((a) => Buffer.alloc(1 << 4, a)),
bufferValueRange.map((a) => Buffer.alloc((1 << 8) - 1, a)),
// Specifying too large of a value causes timeouts on some platforms
// bufferValueRange.map((a) => Buffer.alloc(1 << 17, a))
];
const initialWindowSizeList = [
1 << 4,
(1 << 8) - 1,
1 << 8,
1 << 17,
undefined // use default window size which is (1 << 16) - 1
];
// Call `run` on each element in the cartesian product of buffersList and
// initialWindowSizeList.
let p = Promise.resolve();
for (const buffers of buffersList) {
for (const initialWindowSize of initialWindowSizeList) {
p = p.then(() => run(buffers, initialWindowSize));
}
}
p.then(common.mustCall(() => {}));

View File

@ -0,0 +1,7 @@
// Flags: --expose-http2
'use strict';
require('../common');
const assert = require('assert');
assert.doesNotThrow(() => require('http2'));

View File

@ -0,0 +1,36 @@
// Flags: --expose-http2
'use strict';
// Verifies that write callbacks are called
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
stream.write('abc', common.mustCall(() => {
stream.end('xyz');
}));
let actual = '';
stream.setEncoding('utf8');
stream.on('data', (chunk) => actual += chunk);
stream.on('end', common.mustCall(() => assert.strictEqual(actual, 'abcxyz')));
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request({ ':method': 'POST' });
req.write('abc', common.mustCall(() => {
req.end('xyz');
}));
let actual = '';
req.setEncoding('utf8');
req.on('data', (chunk) => actual += chunk);
req.on('end', common.mustCall(() => assert.strictEqual(actual, 'abcxyz')));
req.on('streamClosed', common.mustCall(() => {
client.destroy();
server.close();
}));
}));

View File

@ -0,0 +1,40 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer(function(request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('1\n');
response.write('');
response.write('2\n');
response.write('');
response.end('3\n');
this.close();
});
server.listen(0, common.mustCall(function() {
const client = http2.connect(`http://localhost:${this.address().port}`);
const headers = { ':path': '/' };
const req = client.request(headers).setEncoding('ascii');
let res = '';
req.on('response', common.mustCall(function(headers) {
assert.strictEqual(200, headers[':status']);
}));
req.on('data', (chunk) => {
res += chunk;
});
req.on('end', common.mustCall(function() {
assert.strictEqual('1\n2\n3\n', res);
client.destroy();
}));
req.end();
}));

View File

@ -0,0 +1,50 @@
// Flags: --expose-http2
'use strict';
const common = require('../common');
const assert = require('assert');
const http2 = require('http2');
const { Readable } = require('stream');
function getSrc() {
const chunks = [ '', 'asdf', '', 'foo', '', 'bar', '' ];
return new Readable({
read() {
const chunk = chunks.shift();
if (chunk !== undefined)
this.push(chunk);
else
this.push(null);
}
});
}
const expect = 'asdffoobar';
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
let actual = '';
stream.respond();
stream.resume();
stream.setEncoding('utf8');
stream.on('data', (chunk) => actual += chunk);
stream.on('end', common.mustCall(() => {
getSrc().pipe(stream);
assert.strictEqual(actual, expect);
}));
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
let actual = '';
const req = client.request({ ':method': 'POST' });
req.on('response', common.mustCall());
req.on('data', (chunk) => actual += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(actual, expect);
server.close();
client.destroy();
}));
getSrc().pipe(req);
}));

View File

@ -3,7 +3,7 @@ const common = require('../common');
const assert = require('assert');
const expected_keys = ['ares', 'http_parser', 'modules', 'node',
'uv', 'v8', 'zlib'];
'uv', 'v8', 'zlib', 'nghttp2'];
if (common.hasCrypto) {
expected_keys.push('openssl');

0
test/parallel/test-tls-disable-renegotiation.js Executable file → Normal file
View File