Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4430,6 +4430,42 @@
type of the readable half of the constructed readable-writable pair, use the
`readableType` option instead.

### DEP0202: `Http1IncomingMessage` and `Http1ServerResponse` options of HTTP/2 servers

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/61713

Check warning on line 4438 in doc/api/deprecations.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Documentation-only deprecation.
-->

Type: Documentation-only

The `Http1IncomingMessage` and `Http1ServerResponse` options of
[`http2.createServer()`][] and [`http2.createSecureServer()`][] are
deprecated. Use `http1Options.IncomingMessage` and
`http1Options.ServerResponse` instead.

```cjs
// Deprecated
const server = http2.createSecureServer({
allowHTTP1: true,
Http1IncomingMessage: MyIncomingMessage,
Http1ServerResponse: MyServerResponse,
});
```

```cjs
// Use this instead
const server = http2.createSecureServer({
allowHTTP1: true,
http1Options: {
IncomingMessage: MyIncomingMessage,
ServerResponse: MyServerResponse,
},
});
```

[DEP0142]: #dep0142-repl_builtinlibs
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
Expand Down Expand Up @@ -4509,6 +4545,8 @@
[`http.ServerResponse`]: http.md#class-httpserverresponse
[`http.get()`]: http.md#httpgetoptions-callback
[`http.request()`]: http.md#httprequestoptions-callback
[`http2.createSecureServer()`]: http2.md#http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: http2.md#http2createserveroptions-onrequesthandler
[`https.get()`]: https.md#httpsgetoptions-callback
[`https.request()`]: https.md#httpsrequestoptions-callback
[`message.connection`]: http.md#messageconnection
Expand Down
41 changes: 41 additions & 0 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2796,6 +2796,10 @@
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/61713

Check warning on line 2800 in doc/api/http2.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added `http1Options` option. The `Http1IncomingMessage`
and `Http1ServerResponse` options are now deprecated.
- version:
- v23.0.0
- v22.10.0
Expand Down Expand Up @@ -2914,9 +2918,27 @@
* `Http1IncomingMessage` {http.IncomingMessage} Specifies the
`IncomingMessage` class to used for HTTP/1 fallback. Useful for extending
the original `http.IncomingMessage`. **Default:** `http.IncomingMessage`.
**Deprecated.** Use `http1Options.IncomingMessage` instead. See
[DEP0202][].
* `Http1ServerResponse` {http.ServerResponse} Specifies the `ServerResponse`
class to used for HTTP/1 fallback. Useful for extending the original
`http.ServerResponse`. **Default:** `http.ServerResponse`.
**Deprecated.** Use `http1Options.ServerResponse` instead. See
[DEP0202][].
* `http1Options` {Object} An options object for configuring the HTTP/1
fallback when `allowHTTP1` is `true`. These options are passed to the
underlying HTTP/1 server. See [`http.createServer()`][] for available
options. Among others, the following are supported:
* `IncomingMessage` {http.IncomingMessage} Specifies the
`IncomingMessage` class to use for HTTP/1 fallback.
**Default:** `http.IncomingMessage`.
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse`
class to use for HTTP/1 fallback.
**Default:** `http.ServerResponse`.
* `keepAliveTimeout` {number} The number of milliseconds of inactivity
a server needs to wait for additional incoming data, after it has
finished writing the last response, before a socket will be destroyed.
**Default:** `5000`.
* `Http2ServerRequest` {http2.Http2ServerRequest} Specifies the
`Http2ServerRequest` class to use.
Useful for extending the original `Http2ServerRequest`.
Expand Down Expand Up @@ -2990,6 +3012,9 @@
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/61713

Check warning on line 3016 in doc/api/http2.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added `http1Options` option.
- version:
- v15.10.0
- v14.16.0
Expand Down Expand Up @@ -3108,6 +3133,20 @@
and trailing whitespace validation for HTTP/2 header field names and values
as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1).
**Default:** `true`.
* `http1Options` {Object} An options object for configuring the HTTP/1
fallback when `allowHTTP1` is `true`. These options are passed to the
underlying HTTP/1 server. See [`http.createServer()`][] for available
options. Among others, the following are supported:
* `IncomingMessage` {http.IncomingMessage} Specifies the
`IncomingMessage` class to use for HTTP/1 fallback.
**Default:** `http.IncomingMessage`.
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse`
class to use for HTTP/1 fallback.
**Default:** `http.ServerResponse`.
* `keepAliveTimeout` {number} The number of milliseconds of inactivity
a server needs to wait for additional incoming data, after it has
finished writing the last response, before a socket will be destroyed.
**Default:** `5000`.
* `onRequestHandler` {Function} See [Compatibility API][]
* Returns: {Http2SecureServer}

Expand Down Expand Up @@ -4937,6 +4976,7 @@
[ALPN Protocol ID]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
[ALPN negotiation]: #alpn-negotiation
[Compatibility API]: #compatibility-api
[DEP0202]: deprecations.md#dep0202-http1incomingmessage-and-http1serverresponse-options-of-http2-servers
[HTTP/1]: http.md
[HTTP/2]: https://tools.ietf.org/html/rfc7540
[HTTP/2 Headers Object]: #headers-object
Expand All @@ -4963,6 +5003,7 @@
[`Http2Stream`]: #class-http2stream
[`ServerHttp2Stream`]: #class-serverhttp2stream
[`TypeError`]: errors.md#class-typeerror
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
Expand Down
26 changes: 26 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -3348,6 +3348,32 @@ This event is guaranteed to be emitted in the same order as the tests are
defined.
The corresponding execution ordered event is `'test:complete'`.

### Event: `'test:interrupted'`

<!-- YAML
added: REPLACEME
-->

* `data` {Object}
* `tests` {Array} An array of objects containing information about the
interrupted tests.
* `column` {number|undefined} The column number where the test is defined,
or `undefined` if the test was run through the REPL.
* `file` {string|undefined} The path of the test file,
`undefined` if test was run through the REPL.
* `line` {number|undefined} The line number where the test is defined, or
`undefined` if the test was run through the REPL.
* `name` {string} The test name.
* `nesting` {number} The nesting level of the test.

Emitted when the test runner is interrupted by a `SIGINT` signal (e.g., when
pressing <kbd>Ctrl</kbd>+<kbd>C</kbd>). The event contains information about
the tests that were running at the time of interruption.

When using process isolation (the default), the test name will be the file path
since the parent runner only knows about file-level tests. When using
`--test-isolation=none`, the actual test name is shown.

### Event: `'test:pass'`

* `data` {Object}
Expand Down
29 changes: 19 additions & 10 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ const { Duplex } = require('stream');
const tls = require('tls');
const { setImmediate, setTimeout, clearTimeout } = require('timers');

const { kIncomingMessage } = require('_http_common');
const { kServerResponse, Server: HttpServer, httpServerPreClose, setupConnectionsTracking } = require('_http_server');
const {
Server: HttpServer,
httpServerPreClose,
setupConnectionsTracking,
storeHTTPOptions,
} = require('_http_server');
const JSStreamSocket = require('internal/js_stream_socket');

const {
Expand Down Expand Up @@ -3257,8 +3261,6 @@ function connectionListener(socket) {
if (socket.alpnProtocol === false || socket.alpnProtocol === 'http/1.1') {
// Fallback to HTTP/1.1
if (options.allowHTTP1 === true) {
socket.server[kIncomingMessage] = options.Http1IncomingMessage;
socket.server[kServerResponse] = options.Http1ServerResponse;
return httpConnectionListener.call(this, socket);
}
// Let event handler deal with the socket
Expand Down Expand Up @@ -3340,9 +3342,18 @@ function initializeOptions(options) {
options.unknownProtocolTimeout = 10000;


// Used only with allowHTTP1
options.Http1IncomingMessage ||= http.IncomingMessage;
options.Http1ServerResponse ||= http.ServerResponse;
// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
options.http1Options = { ...options.http1Options };

// Backward compat: migrate deprecated top-level Http1 options (DEP0201)
if (options.Http1IncomingMessage !== undefined) {
options.http1Options.IncomingMessage ??= options.Http1IncomingMessage;
}
if (options.Http1ServerResponse !== undefined) {
options.http1Options.ServerResponse ??= options.Http1ServerResponse;
}

options.Http2ServerRequest ||= Http2ServerRequest;
options.Http2ServerResponse ||= Http2ServerResponse;
Expand Down Expand Up @@ -3390,9 +3401,7 @@ class Http2SecureServer extends TLSServer {
this.timeout = 0;
this.on('newListener', setupCompat);
if (options.allowHTTP1 === true) {
this.headersTimeout = 60_000; // Minimum between 60 seconds or requestTimeout
this.requestTimeout = 300_000; // 5 minutes
this.connectionsCheckingInterval = 30_000; // 30 seconds
storeHTTPOptions.call(this, { ...options, ...options.http1Options });
this.shouldUpgradeCallback = function() {
return this.listenerCount('upgrade') > 0;
};
Expand Down
9 changes: 4 additions & 5 deletions lib/internal/main/watch_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {
triggerUncaughtException,
exitCodes: { kNoFailure },
} = internalBinding('errors');
const { getOptionValue, getOptionsAsFlagsFromBinding } = require('internal/options');
const { getOptionValue } = require('internal/options');
const { FilesWatcher } = require('internal/watch_mode/files_watcher');
const { green, blue, red, white, clear } = require('internal/util/colors');
const { convertToValidSignal } = require('internal/util');
Expand All @@ -44,14 +44,13 @@ const kCommand = ArrayPrototypeSlice(process.argv, 1);
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));

const argsWithoutWatchOptions = [];
const argsFromBinding = getOptionsAsFlagsFromBinding();
for (let i = 0; i < argsFromBinding.length; i++) {
const arg = argsFromBinding[i];
for (let i = 0; i < process.execArgv.length; i++) {
const arg = process.execArgv[i];
if (StringPrototypeStartsWith(arg, '--watch=')) {
continue;
}
if (arg === '--watch') {
const nextArg = argsFromBinding[i + 1];
const nextArg = process.execArgv[i + 1];
if (nextArg && nextArg[0] !== '-') {
// If `--watch` doesn't include `=` and the next
// argument is not a flag then it is interpreted as
Expand Down
29 changes: 28 additions & 1 deletion lib/internal/test_runner/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {
ArrayPrototypeForEach,
ArrayPrototypePush,
FunctionPrototypeBind,
Promise,
PromiseResolve,
PromiseWithResolvers,
SafeMap,
Expand Down Expand Up @@ -32,7 +33,7 @@ const { PassThrough, compose } = require('stream');
const { reportReruns } = require('internal/test_runner/reporter/rerun');
const { queueMicrotask } = require('internal/process/task_queues');
const { TIMEOUT_MAX } = require('internal/timers');
const { clearInterval, setInterval } = require('timers');
const { clearInterval, setImmediate, setInterval } = require('timers');
const { bigint: hrtime } = process.hrtime;
const testResources = new SafeMap();
let globalRoot;
Expand Down Expand Up @@ -289,7 +290,33 @@ function setupProcessState(root, globalOptions) {
}
};

const findRunningTests = (test, running = []) => {
if (test.startTime !== null && !test.finished) {
for (let i = 0; i < test.subtests.length; i++) {
findRunningTests(test.subtests[i], running);
}
// Only add leaf tests (innermost running tests)
if (test.activeSubtests === 0 && test.name !== '<root>') {
ArrayPrototypePush(running, {
__proto__: null,
name: test.name,
nesting: test.nesting,
file: test.loc?.file,
line: test.loc?.line,
column: test.loc?.column,
});
}
}
return running;
};

const terminationHandler = async () => {
const runningTests = findRunningTests(root);
if (runningTests.length > 0) {
root.reporter.interrupted(runningTests);
// Allow the reporter stream to process the interrupted event
await new Promise((resolve) => setImmediate(resolve));
}
await exitHandler(true);
process.exit();
};
Expand Down
23 changes: 23 additions & 0 deletions lib/internal/test_runner/reporter/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,31 @@ class SpecReporter extends Transform {
break;
case 'test:watch:restarted':
return `\nRestarted at ${DatePrototypeToLocaleString(new Date())}\n`;
case 'test:interrupted':
return this.#formatInterruptedTests(data.tests);
}
}
#formatInterruptedTests(tests) {
if (tests.length === 0) {
return '';
}

const results = [
`\n${colors.yellow}Interrupted while running:${colors.white}\n`,
];

for (let i = 0; i < tests.length; i++) {
const test = tests[i];
let msg = `${indent(test.nesting)}${reporterUnicodeSymbolMap['warning:alert']}${test.name}`;
if (test.file) {
const relPath = relative(this.#cwd, test.file);
msg += ` ${colors.gray}(${relPath}:${test.line}:${test.column})${colors.white}`;
}
ArrayPrototypePush(results, msg);
}

return ArrayPrototypeJoin(results, '\n') + '\n';
}
_transform({ type, data }, encoding, callback) {
callback(null, this.#handleEvent({ __proto__: null, type, data }));
}
Expand Down
10 changes: 10 additions & 0 deletions lib/internal/test_runner/reporter/tap.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ async function * tapReporter(source) {
case 'test:coverage':
yield getCoverageReport(indent(data.nesting), data.summary, '# ', '', true);
break;
case 'test:interrupted':
for (let i = 0; i < data.tests.length; i++) {
const test = data.tests[i];
let msg = `Interrupted while running: ${test.name}`;
if (test.file) {
msg += ` at ${test.file}:${test.line}:${test.column}`;
}
yield `# ${tapEscape(msg)}\n`;
}
break;
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions lib/internal/test_runner/tests_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ class TestsStream extends Readable {
});
}

interrupted(tests) {
this[kEmitMessage]('test:interrupted', {
__proto__: null,
tests,
});
}

end() {
this.#tryPush(null);
}
Expand Down
11 changes: 9 additions & 2 deletions test/parallel/test-http2-https-fallback-http-server-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const ca = fixtures.readKey('fake-startcom-root-cert.pem');
function onRequest(request, response) {
const { socket: { alpnProtocol } } = request.httpVersion === '2.0' ?
request.stream.session : request;
// Verify that http1Options are applied when allowHTTP1 is true
if (request.httpVersion === '1.1') {
assert.strictEqual(request.socket.server.keepAliveTimeout, 10000);
}
response.status(200);
response.end(JSON.stringify({
alpnProtocol,
Expand All @@ -46,8 +50,11 @@ class MyServerResponse extends http.ServerResponse {
{
cert,
key, allowHTTP1: true,
Http1IncomingMessage: MyIncomingMessage,
Http1ServerResponse: MyServerResponse
http1Options: {
IncomingMessage: MyIncomingMessage,
ServerResponse: MyServerResponse,
keepAliveTimeout: 10000,
},
},
common.mustCall(onRequest, 1)
);
Expand Down
Loading
Loading