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:
parent
e71e71b513
commit
b1e055696f
@ -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>
|
||||
|
@ -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 = {};
|
||||
|
43
benchmark/http2/respond-with-fd.js
Normal file
43
benchmark/http2/respond-with-fd.js
Normal 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
38
benchmark/http2/simple.js
Normal 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(); });
|
||||
});
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
@ -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
0
test/parallel/test-dgram-bind-default-address.js
Executable file → Normal file
229
test/parallel/test-http2-binding.js
Normal file
229
test/parallel/test-http2-binding.js
Normal 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}`);
|
||||
}
|
||||
}
|
90
test/parallel/test-http2-client-data-end.js
Normal file
90
test/parallel/test-http2-client-data-end.js
Normal 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();
|
||||
}));
|
28
test/parallel/test-http2-client-destroy-before-connect.js
Normal file
28
test/parallel/test-http2-client-destroy-before-connect.js
Normal 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();
|
||||
|
||||
}));
|
28
test/parallel/test-http2-client-destroy-before-request.js
Normal file
28
test/parallel/test-http2-client-destroy-before-request.js
Normal 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();
|
||||
|
||||
}));
|
54
test/parallel/test-http2-client-destroy.js
Normal file
54
test/parallel/test-http2-client-destroy.js
Normal 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();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}));
|
37
test/parallel/test-http2-client-priority-before-connect.js
Normal file
37
test/parallel/test-http2-client-priority-before-connect.js
Normal 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();
|
||||
|
||||
}));
|
34
test/parallel/test-http2-client-rststream-before-connect.js
Normal file
34
test/parallel/test-http2-client-rststream-before-connect.js
Normal 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();
|
||||
}));
|
49
test/parallel/test-http2-client-set-priority.js
Normal file
49
test/parallel/test-http2-client-set-priority.js
Normal 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);
|
63
test/parallel/test-http2-client-settings-before-connect.js
Normal file
63
test/parallel/test-http2-client-settings-before-connect.js
Normal 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();
|
||||
|
||||
}));
|
23
test/parallel/test-http2-client-shutdown-before-connect.js
Normal file
23
test/parallel/test-http2-client-shutdown-before-connect.js
Normal 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();
|
||||
}));
|
||||
|
||||
}));
|
46
test/parallel/test-http2-client-socket-destroy.js
Normal file
46
test/parallel/test-http2-client-socket-destroy.js
Normal 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();
|
||||
|
||||
}));
|
@ -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());
|
||||
|
||||
}));
|
37
test/parallel/test-http2-client-unescaped-path.js
Normal file
37
test/parallel/test-http2-client-unescaped-path.js
Normal 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);
|
||||
}));
|
44
test/parallel/test-http2-client-upload.js
Normal file
44
test/parallel/test-http2-client-upload.js
Normal 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);
|
||||
}));
|
||||
}));
|
53
test/parallel/test-http2-client-write-before-connect.js
Normal file
53
test/parallel/test-http2-client-write-before-connect.js
Normal 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();
|
||||
|
||||
}));
|
70
test/parallel/test-http2-compat-serverrequest-headers.js
Normal file
70
test/parallel/test-http2-compat-serverrequest-headers.js
Normal 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();
|
||||
}));
|
||||
}));
|
52
test/parallel/test-http2-compat-serverrequest.js
Normal file
52
test/parallel/test-http2-compat-serverrequest.js
Normal 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();
|
||||
}));
|
||||
}));
|
@ -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();
|
||||
}));
|
||||
}));
|
||||
}));
|
77
test/parallel/test-http2-compat-serverresponse-end.js
Normal file
77
test/parallel/test-http2-compat-serverresponse-end.js
Normal 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();
|
||||
}));
|
||||
}));
|
||||
}
|
37
test/parallel/test-http2-compat-serverresponse-finished.js
Normal file
37
test/parallel/test-http2-compat-serverresponse-finished.js
Normal 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();
|
||||
}));
|
||||
}));
|
@ -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();
|
||||
}));
|
||||
}));
|
83
test/parallel/test-http2-compat-serverresponse-headers.js
Normal file
83
test/parallel/test-http2-compat-serverresponse-headers.js
Normal 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();
|
||||
}));
|
||||
}));
|
76
test/parallel/test-http2-compat-serverresponse-statuscode.js
Normal file
76
test/parallel/test-http2-compat-serverresponse-statuscode.js
Normal 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();
|
||||
}));
|
||||
}));
|
@ -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();
|
||||
}));
|
||||
}));
|
@ -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();
|
||||
}));
|
||||
}));
|
||||
}
|
44
test/parallel/test-http2-compat-serverresponse-writehead.js
Normal file
44
test/parallel/test-http2-compat-serverresponse-writehead.js
Normal 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();
|
||||
}));
|
||||
}));
|
71
test/parallel/test-http2-connect-method.js
Normal file
71
test/parallel/test-http2-connect-method.js
Normal 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');
|
||||
});
|
||||
}));
|
29
test/parallel/test-http2-connect.js
Normal file
29
test/parallel/test-http2-connect.js
Normal 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();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
62
test/parallel/test-http2-cookies.js
Normal file
62
test/parallel/test-http2-cookies.js
Normal 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();
|
||||
|
||||
}));
|
88
test/parallel/test-http2-create-client-connect.js
Normal file
88
test/parallel/test-http2-create-client-connect.js
Normal 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)));
|
||||
});
|
||||
}));
|
||||
}
|
75
test/parallel/test-http2-create-client-secure-session.js
Normal file
75
test/parallel/test-http2-create-client-secure-session.js
Normal 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'});
|
61
test/parallel/test-http2-create-client-session.js
Normal file
61
test/parallel/test-http2-create-client-session.js
Normal 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();
|
||||
}
|
||||
|
||||
}));
|
28
test/parallel/test-http2-date-header.js
Normal file
28
test/parallel/test-http2-date-header.js
Normal 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();
|
||||
}));
|
||||
}));
|
48
test/parallel/test-http2-dont-override.js
Normal file
48
test/parallel/test-http2-dont-override.js
Normal 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();
|
||||
}));
|
||||
}));
|
131
test/parallel/test-http2-getpackedsettings.js
Normal file
131
test/parallel/test-http2-getpackedsettings.js
Normal 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'
|
||||
}));
|
||||
}
|
38
test/parallel/test-http2-goaway-opaquedata.js
Normal file
38
test/parallel/test-http2-goaway-opaquedata.js
Normal 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();
|
||||
|
||||
});
|
57
test/parallel/test-http2-head-request.js
Normal file
57
test/parallel/test-http2-head-request.js
Normal 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();
|
||||
}));
|
||||
});
|
146
test/parallel/test-http2-https-fallback.js
Normal file
146
test/parallel/test-http2-https-fallback.js
Normal 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));
|
||||
}));
|
||||
}
|
85
test/parallel/test-http2-info-headers.js
Executable file
85
test/parallel/test-http2-info-headers.js
Executable 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();
|
||||
|
||||
}));
|
67
test/parallel/test-http2-max-concurrent-streams.js
Normal file
67
test/parallel/test-http2-max-concurrent-streams.js
Normal 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'
|
||||
})));
|
||||
|
||||
}));
|
48
test/parallel/test-http2-methods.js
Normal file
48
test/parallel/test-http2-methods.js
Normal 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();
|
||||
});
|
||||
}));
|
61
test/parallel/test-http2-misused-pseudoheaders.js
Normal file
61
test/parallel/test-http2-misused-pseudoheaders.js
Normal 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();
|
||||
|
||||
}));
|
58
test/parallel/test-http2-multi-content-length.js
Normal file
58
test/parallel/test-http2-multi-content-length.js
Normal 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'
|
||||
}));
|
||||
}));
|
60
test/parallel/test-http2-multiheaders.js
Normal file
60
test/parallel/test-http2-multiheaders.js
Normal 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();
|
||||
}));
|
||||
}));
|
59
test/parallel/test-http2-multiplex.js
Normal file
59
test/parallel/test-http2-multiplex.js
Normal 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();
|
||||
}));
|
8
test/parallel/test-http2-noflag.js
Normal file
8
test/parallel/test-http2-noflag.js
Normal 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'$/);
|
48
test/parallel/test-http2-options-max-headers-block-length.js
Normal file
48
test/parallel/test-http2-options-max-headers-block-length.js
Normal 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();
|
||||
|
||||
}));
|
73
test/parallel/test-http2-options-max-reserved-streams.js
Normal file
73
test/parallel/test-http2-options-max-reserved-streams.js
Normal 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();
|
||||
|
||||
}));
|
50
test/parallel/test-http2-padding-callback.js
Normal file
50
test/parallel/test-http2-padding-callback.js
Normal 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();
|
||||
}));
|
60
test/parallel/test-http2-priority-event.js
Normal file
60
test/parallel/test-http2-priority-event.js
Normal 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();
|
||||
|
||||
}));
|
41
test/parallel/test-http2-respond-file-204.js
Normal file
41
test/parallel/test-http2-respond-file-204.js
Normal 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();
|
||||
});
|
44
test/parallel/test-http2-respond-file-304.js
Normal file
44
test/parallel/test-http2-respond-file-304.js
Normal 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();
|
||||
});
|
23
test/parallel/test-http2-respond-file-compat.js
Normal file
23
test/parallel/test-http2-respond-file-compat.js
Normal 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();
|
||||
});
|
37
test/parallel/test-http2-respond-file-fd-invalid.js
Normal file
37
test/parallel/test-http2-respond-file-fd-invalid.js
Normal 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();
|
||||
});
|
46
test/parallel/test-http2-respond-file-fd.js
Normal file
46
test/parallel/test-http2-respond-file-fd.js
Normal 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();
|
||||
});
|
81
test/parallel/test-http2-respond-file-push.js
Normal file
81
test/parallel/test-http2-respond-file-push.js
Normal 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();
|
||||
});
|
51
test/parallel/test-http2-respond-file.js
Normal file
51
test/parallel/test-http2-respond-file.js
Normal 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();
|
||||
});
|
75
test/parallel/test-http2-response-splitting.js
Normal file
75
test/parallel/test-http2-response-splitting.js
Normal 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%8aContentLength:%200%c4%8d%c4%8a%c' +
|
||||
'4%8d%c4%8aHTTP/1.1%20200%20OK%c4%8d%c4%8aContentLength:%202' +
|
||||
'0%c4%8d%c4%8aLastModified:%20Mon,%2027%20Oct%202003%2014:50:18' +
|
||||
'%20GMT%c4%8d%c4%8aContentType:%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);
|
||||
|
||||
}));
|
82
test/parallel/test-http2-serve-file.js
Normal file
82
test/parallel/test-http2-serve-file.js
Normal 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()));
|
||||
|
||||
});
|
38
test/parallel/test-http2-server-destroy-before-additional.js
Normal file
38
test/parallel/test-http2-server-destroy-before-additional.js
Normal 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();
|
||||
|
||||
}));
|
38
test/parallel/test-http2-server-destroy-before-push.js
Normal file
38
test/parallel/test-http2-server-destroy-before-push.js
Normal 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();
|
||||
|
||||
}));
|
38
test/parallel/test-http2-server-destroy-before-respond.js
Normal file
38
test/parallel/test-http2-server-destroy-before-respond.js
Normal 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();
|
||||
|
||||
}));
|
38
test/parallel/test-http2-server-destroy-before-write.js
Normal file
38
test/parallel/test-http2-server-destroy-before-write.js
Normal 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();
|
||||
|
||||
}));
|
53
test/parallel/test-http2-server-push-disabled.js
Normal file
53
test/parallel/test-http2-server-push-disabled.js
Normal 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();
|
||||
}));
|
58
test/parallel/test-http2-server-push-stream.js
Normal file
58
test/parallel/test-http2-server-push-stream.js
Normal 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();
|
||||
}));
|
45
test/parallel/test-http2-server-rst-before-respond.js
Normal file
45
test/parallel/test-http2-server-rst-before-respond.js
Normal 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());
|
||||
|
||||
}));
|
72
test/parallel/test-http2-server-rst-stream.js
Normal file
72
test/parallel/test-http2-server-rst-stream.js
Normal 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);
|
36
test/parallel/test-http2-server-set-header.js
Normal file
36
test/parallel/test-http2-server-set-header.js
Normal 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());
|
32
test/parallel/test-http2-server-shutdown-before-respond.js
Normal file
32
test/parallel/test-http2-server-shutdown-before-respond.js
Normal 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();
|
||||
}));
|
57
test/parallel/test-http2-server-socket-destroy.js
Normal file
57
test/parallel/test-http2-server-socket-destroy.js
Normal 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());
|
||||
}));
|
78
test/parallel/test-http2-server-startup.js
Normal file
78
test/parallel/test-http2-server-startup.js
Normal 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());
|
||||
}));
|
||||
}
|
110
test/parallel/test-http2-session-settings.js
Normal file
110
test/parallel/test-http2-session-settings.js
Normal 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();
|
||||
|
||||
}));
|
97
test/parallel/test-http2-session-stream-state.js
Normal file
97
test/parallel/test-http2-session-stream-state.js
Normal 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();
|
||||
|
||||
}));
|
59
test/parallel/test-http2-single-headers.js
Normal file
59
test/parallel/test-http2-single-headers.js
Normal 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));
|
||||
});
|
||||
|
||||
}));
|
40
test/parallel/test-http2-status-code-invalid.js
Normal file
40
test/parallel/test-http2-status-code-invalid.js
Normal 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();
|
||||
}));
|
||||
}));
|
40
test/parallel/test-http2-status-code.js
Normal file
40
test/parallel/test-http2-status-code.js
Normal 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]);
|
||||
}));
|
32
test/parallel/test-http2-timeouts.js
Normal file
32
test/parallel/test-http2-timeouts.js
Normal 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();
|
||||
}));
|
||||
}));
|
||||
}));
|
60
test/parallel/test-http2-too-many-settings.js
Normal file
60
test/parallel/test-http2-too-many-settings.js
Normal 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();
|
||||
}));
|
||||
}));
|
44
test/parallel/test-http2-trailers.js
Normal file
44
test/parallel/test-http2-trailers.js
Normal 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();
|
||||
|
||||
}));
|
43
test/parallel/test-http2-util-asserts.js
Normal file
43
test/parallel/test-http2-util-asserts.js
Normal 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$/
|
||||
}));
|
248
test/parallel/test-http2-util-headers-list.js
Normal file
248
test/parallel/test-http2-util-headers-list.js
Normal 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));
|
102
test/parallel/test-http2-window-size.js
Normal file
102
test/parallel/test-http2-window-size.js
Normal 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(() => {}));
|
7
test/parallel/test-http2-withflag.js
Normal file
7
test/parallel/test-http2-withflag.js
Normal file
@ -0,0 +1,7 @@
|
||||
// Flags: --expose-http2
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
assert.doesNotThrow(() => require('http2'));
|
36
test/parallel/test-http2-write-callbacks.js
Normal file
36
test/parallel/test-http2-write-callbacks.js
Normal 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();
|
||||
}));
|
||||
}));
|
40
test/parallel/test-http2-write-empty-string.js
Normal file
40
test/parallel/test-http2-write-empty-string.js
Normal 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();
|
||||
}));
|
50
test/parallel/test-http2-zero-length-write.js
Normal file
50
test/parallel/test-http2-zero-length-write.js
Normal 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);
|
||||
}));
|
@ -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
0
test/parallel/test-tls-disable-renegotiation.js
Executable file → Normal file
Loading…
x
Reference in New Issue
Block a user