diff --git a/doc/api/errors.md b/doc/api/errors.md
index ac110a77445343..73fd1d52cbeedc 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -758,6 +758,21 @@ Status code was outside the regular status code range (100-999).
The `Trailer` header was set even though the transfer encoding does not support
that.
+
+### ERR_HTTP2_ALREADY_SHUTDOWN
+
+Occurs with multiple attempts to shutdown an HTTP/2 session.
+
+
+### ERR_HTTP2_ALTSVC_INVALID_ORIGIN
+
+HTTP/2 ALTSVC frames require a valid origin.
+
+
+### ERR_HTTP2_ALTSVC_LENGTH
+
+HTTP/2 ALTSVC frames are limited to a maximum of 16,382 payload bytes.
+
### ERR_HTTP2_CONNECT_AUTHORITY
@@ -781,6 +796,12 @@ forbidden.
A failure occurred sending an individual frame on the HTTP/2 session.
+
+### ERR_HTTP2_GOAWAY_SESSION
+
+New HTTP/2 Streams may not be opened after the `Http2Session` has received a
+`GOAWAY` frame from the connected peer.
+
### ERR_HTTP2_HEADER_REQUIRED
@@ -869,7 +890,7 @@ An operation was performed on a stream that had already been destroyed.
### ERR_HTTP2_MAX_PENDING_SETTINGS_ACK
Whenever an HTTP/2 `SETTINGS` frame is sent to a connected peer, the peer is
-required to send an acknowledgement that it has received and applied the new
+required to send an acknowledgment that it has received and applied the new
`SETTINGS`. By default, a maximum number of unacknowledged `SETTINGS` frames may
be sent at any given time. This error code is used when that limit has been
reached.
@@ -895,7 +916,7 @@ forbidden.
### ERR_HTTP2_PING_CANCEL
-An HTTP/2 ping was cancelled.
+An HTTP/2 ping was canceled.
### ERR_HTTP2_PING_LENGTH
@@ -920,6 +941,11 @@ client.
An attempt was made to use the `Http2Stream.prototype.responseWithFile()` API to
send something other than a regular file.
+
+### ERR_HTTP2_SESSION_ERROR
+
+The `Http2Session` closed with a non-zero error code.
+
### ERR_HTTP2_SOCKET_BOUND
@@ -937,10 +963,11 @@ Use of the `101` Informational status code is forbidden in HTTP/2.
An invalid HTTP status code has been specified. Status codes must be an integer
between `100` and `599` (inclusive).
-
-### ERR_HTTP2_STREAM_CLOSED
+
+### ERR_HTTP2_STREAM_CANCEL
-An action was performed on an HTTP/2 Stream that had already been closed.
+An `Http2Stream` was destroyed before any data was transmitted to the connected
+peer.
### ERR_HTTP2_STREAM_ERROR
@@ -1222,11 +1249,11 @@ A failure occurred resolving imports in an [ES6 module][].
### ERR_MULTIPLE_CALLBACK
-A callback was called more then once.
+A callback was called more than once.
*Note*: A callback is almost always meant to only be called once as the query
can either be fulfilled or rejected but not both at the same time. The latter
-would be possible by calling a callback more then once.
+would be possible by calling a callback more than once.
### ERR_NAPI_CONS_FUNCTION
diff --git a/doc/api/http2.md b/doc/api/http2.md
index 0d298abb8057b7..9ef3fe27413ab7 100644
--- a/doc/api/http2.md
+++ b/doc/api/http2.md
@@ -67,8 +67,8 @@ const fs = require('fs');
const client = http2.connect('https://localhost:8443', {
ca: fs.readFileSync('localhost-cert.pem')
});
-client.on('socketError', (err) => console.error(err));
client.on('error', (err) => console.error(err));
+client.on('socketError', (err) => console.error(err));
const req = client.request({ ':path': '/' });
@@ -83,7 +83,7 @@ let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => {
console.log(`\n${data}`);
- client.destroy();
+ client.close();
});
req.end();
```
@@ -127,7 +127,7 @@ solely on the API of the `Http2Session`.
added: v8.4.0
-->
-The `'close'` event is emitted once the `Http2Session` has been terminated.
+The `'close'` event is emitted once the `Http2Session` has been destroyed.
#### Event: 'connect'
-The `'localSettings'` event is emitted when an acknowledgement SETTINGS frame
+The `'localSettings'` event is emitted when an acknowledgment SETTINGS frame
has been received. When invoked, the handler function will receive a copy of
the local settings.
@@ -234,19 +234,13 @@ of the stream.
```js
const http2 = require('http2');
-const {
- HTTP2_HEADER_METHOD,
- HTTP2_HEADER_PATH,
- HTTP2_HEADER_STATUS,
- HTTP2_HEADER_CONTENT_TYPE
-} = http2.constants;
session.on('stream', (stream, headers, flags) => {
- const method = headers[HTTP2_HEADER_METHOD];
- const path = headers[HTTP2_HEADER_PATH];
+ const method = headers[':method'];
+ const path = headers[':path'];
// ...
stream.respond({
- [HTTP2_HEADER_STATUS]: 200,
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ ':status': 200,
+ 'content-type': 'text/plain'
});
stream.write('hello ');
stream.end('world');
@@ -275,19 +269,6 @@ server.on('stream', (stream, headers) => {
server.listen(80);
```
-#### Event: 'socketError'
-
-
-The `'socketError'` event is emitted when an `'error'` is emitted on the
-`Socket` instance bound to the `Http2Session`. If this event is not handled,
-the `'error'` event will be re-emitted on the `Socket`.
-
-For `ServerHttp2Session` instances, a `'socketError'` event listener is always
-registered that will, by default, forward the event on to the owning
-`Http2Server` instance if no additional handlers are registered.
-
#### Event: 'timeout'
+
+* Value: {string|undefined}
+
+Value will be `undefined` if the `Http2Session` is not yet connected to a
+socket, `h2c` if the `Http2Session` is not connected to a `TLSSocket`, or
+will return the value of the connected `TLSSocket`'s own `alpnProtocol`
+property.
+
+#### http2session.close([callback])
+
+
+* `callback` {Function}
+
+Gracefully closes the `Http2Session`, allowing any existing streams to
+complete on their own and preventing new `Http2Stream` instances from being
+created. Once closed, `http2session.destroy()` *might* be called if there
+are no open `Http2Stream` instances.
+
+If specified, the `callback` function is registered as a handler for the
+`'close'` event.
+
+#### http2session.closed
+
+
+* Value: {boolean}
+
+Will be `true` if this `Http2Session` instance has been closed, otherwise
+`false`.
+
+#### http2session.destroy([error,][code])
+* `error` {Error} An `Error` object if the `Http2Session` is being destroyed
+ due to an error.
+* `code` {number} The HTTP/2 error code to send in the final `GOAWAY` frame.
+ If unspecified, and `error` is not undefined, the default is `INTERNAL_ERROR`,
+ otherwise defaults to `NO_ERROR`.
* Returns: {undefined}
Immediately terminates the `Http2Session` and the associated `net.Socket` or
`tls.TLSSocket`.
+Once destroyed, the `Http2Session` will emit the `'close'` event. If `error`
+is not undefined, an `'error'` event will be emitted immediately after the
+`'close'` event.
+
+If there are any remaining open `Http2Streams` associated with the
+`Http2Session`, those will also be destroyed.
+
#### http2session.destroyed
+
+* Value: {boolean|undefined}
+
+Value is `undefined` if the `Http2Session` session socket has not yet been
+connected, `true` if the `Http2Session` is connected with a `TLSSocket`,
+and `false` if the `Http2Session` is connected to any other kind of socket
+or stream.
+
+#### http2session.goaway([code, [lastStreamID, [opaqueData]]])
+
+
+* `code` {number} An HTTP/2 error code
+* `lastStreamID` {number} The numeric ID of the last processed `Http2Stream`
+* `opaqueData` {Buffer|TypedArray|DataView} A `TypedArray` or `DataView`
+ instance containing additional data to be carried within the GOAWAY frame.
+
+Transmits a `GOAWAY` frame to the connected peer *without* shutting down the
+`Http2Session`.
+
#### http2session.localSettings
+
+* Value: {string[]|undefined}
+
+If the `Http2Session` is connected to a `TLSSocket`, the `originSet` property
+will return an Array of origins for which the `Http2Session` may be
+considered authoritative.
+
#### http2session.pendingSettingsAck
-* Value: {[Settings Object][]}
+Calls [`ref()`][`net.Socket.prototype.ref`] on this `Http2Session`
+instance's underlying [`net.Socket`].
-A prototype-less object describing the current remote settings of this
-`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
-
-#### http2session.request(headers[, options])
+#### http2session.remoteSettings
-* `headers` {[Headers Object][]}
-* `options` {Object}
- * `endStream` {boolean} `true` if the `Http2Stream` *writable* side should
- be closed initially, such as when sending a `GET` request that should not
- expect a payload body.
- * `exclusive` {boolean} When `true` and `parent` identifies a parent Stream,
- the created stream is made the sole direct dependency of the parent, with
- all other existing dependents made a dependent of the newly created stream.
- **Default:** `false`
- * `parent` {number} Specifies the numeric identifier of a stream the newly
- created stream is dependent on.
- * `weight` {number} Specifies the relative dependency of a stream in relation
- to other streams with the same `parent`. The value is a number between `1`
- and `256` (inclusive).
- * `getTrailers` {Function} Callback function invoked to collect trailer
- headers.
-
-* Returns: {ClientHttp2Stream}
-
-For HTTP/2 Client `Http2Session` instances only, the `http2session.request()`
-creates and returns an `Http2Stream` instance that can be used to send an
-HTTP/2 request to the connected server.
-
-This method is only available if `http2session.type` is equal to
-`http2.constants.NGHTTP2_SESSION_CLIENT`.
-
-```js
-const http2 = require('http2');
-const clientSession = http2.connect('https://localhost:1234');
-const {
- HTTP2_HEADER_PATH,
- HTTP2_HEADER_STATUS
-} = http2.constants;
-
-const req = clientSession.request({ [HTTP2_HEADER_PATH]: '/' });
-req.on('response', (headers) => {
- console.log(headers[HTTP2_HEADER_STATUS]);
- req.on('data', (chunk) => { /** .. **/ });
- req.on('end', () => { /** .. **/ });
-});
-```
-
-When set, the `options.getTrailers()` function is called immediately after
-queuing the last chunk of payload data to be sent. The callback is passed a
-single object (with a `null` prototype) that the listener may used to specify
-the trailing header fields to send to the peer.
+* Value: {[Settings Object][]}
-*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
-"pseudo-header" fields (e.g. `':method'`, `':path'`, etc). An `'error'` event
-will be emitted if the `getTrailers` callback attempts to set such header
-fields.
+A prototype-less object describing the current remote settings of this
+`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
#### http2session.setTimeout(msecs, callback)
* `options` {Object}
- * `graceful` {boolean} `true` to attempt a polite shutdown of the
- `Http2Session`.
* `errorCode` {number} The HTTP/2 [error code][] to return. Note that this is
*not* the same thing as an HTTP Response Status Code. **Default:** `0x00`
(No Error).
* `lastStreamID` {number} The Stream ID of the last successfully processed
- `Http2Stream` on this `Http2Session`.
+ `Http2Stream` on this `Http2Session`. If unspecified, will default to the
+ ID of the most recently received stream.
* `opaqueData` {Buffer|Uint8Array} A `Buffer` or `Uint8Array` instance
containing arbitrary additional data to send to the peer upon disconnection.
This is used, typically, to provide additional data for debugging failures,
@@ -483,23 +499,20 @@ added: v8.4.0
has been completed.
* Returns: {undefined}
-Attempts to shutdown this `Http2Session` using HTTP/2 defined procedures.
+Attempts to shut down this `Http2Session` using HTTP/2 defined procedures.
If specified, the given `callback` function will be invoked once the shutdown
process has completed.
-Note that calling `http2session.shutdown()` does *not* destroy the session or
-tear down the `Socket` connection. It merely prompts both sessions to begin
-preparing to cease activity.
-
-During a "graceful" shutdown, the session will first send a `GOAWAY` frame to
-the connected peer identifying the last processed stream as 232-1.
+If the `Http2Session` instance is a server-side session and the `errorCode`
+option is `0x00` (No Error), a "graceful" shutdown will be initiated. During a
+"graceful" shutdown, the session will first send a `GOAWAY` frame to
+the connected peer identifying the last processed stream as 231-1.
Then, on the next tick of the event loop, a second `GOAWAY` frame identifying
the most recently processed stream identifier is sent. This process allows the
remote peer to begin preparing for the connection to be terminated.
```js
-session.shutdown({
- graceful: true,
+session.close({
opaqueData: Buffer.from('add some debugging data here')
}, () => session.destroy());
```
@@ -556,8 +569,8 @@ while the session is waiting for the remote peer to acknowledge the new
settings.
*Note*: The new settings will not become effective until the SETTINGS
-acknowledgement is received and the `'localSettings'` event is emitted. It
-is possible to send multiple SETTINGS frames while acknowledgement is still
+acknowledgment is received and the `'localSettings'` event is emitted. It
+is possible to send multiple SETTINGS frames while acknowledgment is still
pending.
#### http2session.type
@@ -572,6 +585,174 @@ The `http2session.type` will be equal to
server, and `http2.constants.NGHTTP2_SESSION_CLIENT` if the instance is a
client.
+#### http2session.unref()
+
+
+Calls [`unref()`][`net.Socket.prototype.unref`] on this `Http2Session`
+instance's underlying [`net.Socket`].
+
+### Class: ServerHttp2Session
+
+
+#### serverhttp2session.altsvc(alt, originOrStream)
+
+
+* `alt` {string} A description of the alternative service configuration as
+ defined by [RFC 7838][].
+* `originOrStream` {number|string|URL|Object} Either a URL string specifying
+ the origin (or an Object with an `origin` property) or the numeric identifier
+ of an active `Http2Stream` as given by the `http2stream.id` property.
+
+Submits an `ALTSVC` frame (as defined by [RFC 7838][]) to the connected client.
+
+```js
+const http2 = require('http2');
+
+const server = http2.createServer();
+server.on('session', (session) => {
+ // Set altsvc for origin https://example.org:80
+ session.altsvc('h2=":8000"', 'https://example.org:80');
+});
+
+server.on('stream', (stream) => {
+ // Set altsvc for a specific stream
+ stream.session.altsvc('h2=":8000"', stream.id);
+});
+```
+
+Sending an `ALTSVC` frame with a specific stream ID indicates that the alternate
+service is associated with the origin of the given `Http2Stream`.
+
+The `alt` and origin string *must* contain only ASCII bytes and are
+strictly interpreted as a sequence of ASCII bytes. The special value `'clear'`
+may be passed to clear any previously set alternative service for a given
+domain.
+
+When a string is passed for the `originOrStream` argument, it will be parsed as
+a URL and the origin will be derived. For instance, the origin for the
+HTTP URL `'https://example.org/foo/bar'` is the ASCII string
+`'https://example.org'`. An error will be thrown if either the given string
+cannot be parsed as a URL or if a valid origin cannot be derived.
+
+A `URL` object, or any object with an `origin` property, may be passed as
+`originOrStream`, in which case the value of the `origin` property will be
+used. The value of the `origin` property *must* be a properly serialized
+ASCII origin.
+
+#### Specifying alternative services
+
+The format of the `alt` parameter is strictly defined by [RFC 7838][] as an
+ASCII string containing a comma-delimited list of "alternative" protocols
+associated with a specific host and port.
+
+For example, the value `'h2="example.org:81"'` indicates that the HTTP/2
+protocol is available on the host `'example.org'` on TCP/IP port 81. The
+host and port *must* be contained within the quote (`"`) characters.
+
+Multiple alternatives may be specified, for instance: `'h2="example.org:81",
+h2=":82"'`
+
+The protocol identifier (`'h2'` in the examples) may be any valid
+[ALPN Protocol ID][].
+
+The syntax of these values is not validated by the Node.js implementation and
+are passed through as provided by the user or received from the peer.
+
+### Class: ClientHttp2Session
+
+
+#### Event: 'altsvc'
+
+
+The `'altsvc'` event is emitted whenever an `ALTSVC` frame is received by
+the client. The event is emitted with the `ALTSVC` value, origin, and stream
+ID, if any. If no `origin` is provided in the `ALTSVC` frame, `origin` will
+be an empty string.
+
+```js
+const http2 = require('http2');
+const client = http2.connect('https://example.org');
+
+client.on('altsvc', (alt, origin, stream) => {
+ console.log(alt);
+ console.log(origin);
+ console.log(stream);
+});
+```
+
+#### clienthttp2session.request(headers[, options])
+
+
+* `headers` {[Headers Object][]}
+* `options` {Object}
+ * `endStream` {boolean} `true` if the `Http2Stream` *writable* side should
+ be closed initially, such as when sending a `GET` request that should not
+ expect a payload body.
+ * `exclusive` {boolean} When `true` and `parent` identifies a parent Stream,
+ the created stream is made the sole direct dependency of the parent, with
+ all other existing dependents made a dependent of the newly created stream.
+ **Default:** `false`
+ * `parent` {number} Specifies the numeric identifier of a stream the newly
+ created stream is dependent on.
+ * `weight` {number} Specifies the relative dependency of a stream in relation
+ to other streams with the same `parent`. The value is a number between `1`
+ and `256` (inclusive).
+ * `getTrailers` {Function} Callback function invoked to collect trailer
+ headers.
+
+* Returns: {ClientHttp2Stream}
+
+For HTTP/2 Client `Http2Session` instances only, the `http2session.request()`
+creates and returns an `Http2Stream` instance that can be used to send an
+HTTP/2 request to the connected server.
+
+This method is only available if `http2session.type` is equal to
+`http2.constants.NGHTTP2_SESSION_CLIENT`.
+
+```js
+const http2 = require('http2');
+const clientSession = http2.connect('https://localhost:1234');
+const {
+ HTTP2_HEADER_PATH,
+ HTTP2_HEADER_STATUS
+} = http2.constants;
+
+const req = clientSession.request({ [HTTP2_HEADER_PATH]: '/' });
+req.on('response', (headers) => {
+ console.log(headers[HTTP2_HEADER_STATUS]);
+ req.on('data', (chunk) => { /** .. **/ });
+ req.on('end', () => { /** .. **/ });
+});
+```
+
+When set, the `options.getTrailers()` function is called immediately after
+queuing the last chunk of payload data to be sent. The callback is passed a
+single object (with a `null` prototype) that the listener may use to specify
+the trailing header fields to send to the peer.
+
+*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
+pseudo-header fields (e.g. `':method'`, `':path'`, etc). An `'error'` event
+will be emitted if the `getTrailers` callback attempts to set such header
+fields.
+
+The `:method` and `:path` pseudo-headers are not specified within `headers`,
+they respectively default to:
+
+* `:method` = `'GET'`
+* `:path` = `/`
+
### Class: Http2Stream
The `'timeout'` event is emitted after no activity is received for this
-`'Http2Stream'` within the number of millseconds set using
+`'Http2Stream'` within the number of milliseconds set using
`http2stream.setTimeout()`.
#### Event: 'trailers'
@@ -720,6 +901,29 @@ added: v8.4.0
Set to `true` if the `Http2Stream` instance was aborted abnormally. When set,
the `'aborted'` event will have been emitted.
+#### http2stream.close(code[, callback])
+
+
+* code {number} Unsigned 32-bit integer identifying the error code. **Default:**
+ `http2.constant.NGHTTP2_NO_ERROR` (`0x00`)
+* `callback` {Function} An optional function registered to listen for the
+ `'close'` event.
+* Returns: {undefined}
+
+Closes the `Http2Stream` instance by sending an `RST_STREAM` frame to the
+connected HTTP/2 peer.
+
+#### http2stream.closed
+
+
+* Value: {boolean}
+
+Set to `true` if the `Http2Stream` instance has been closed.
+
#### http2stream.destroyed
+
+* Value: {boolean}
+
+Set to `true` if the `Http2Stream` instance has not yet been assigned a
+numeric stream identifier.
+
#### http2stream.priority(options)
-
-* code {number} Unsigned 32-bit integer identifying the error code. **Default:**
- `http2.constant.NGHTTP2_NO_ERROR` (`0x00`)
-* Returns: {undefined}
-
-Sends an `RST_STREAM` frame to the connected HTTP/2 peer, causing this
-`Http2Stream` to be closed on both sides using [error code][] `code`.
-
-#### http2stream.rstWithNoError()
-
-
-* Returns: {undefined}
-
-Shortcut for `http2stream.rstStream()` using error code `0x00` (No Error).
-
-#### http2stream.rstWithProtocolError()
-
-
-* Returns: {undefined}
-
-Shortcut for `http2stream.rstStream()` using error code `0x01` (Protocol Error).
-
-#### http2stream.rstWithCancel()
-
-
-* Returns: {undefined}
-
-Shortcut for `http2stream.rstStream()` using error code `0x08` (Cancel).
-
-#### http2stream.rstWithRefuse()
-
-
-* Returns: {undefined}
-
-Shortcut for `http2stream.rstStream()` using error code `0x07` (Refused Stream).
-
-#### http2stream.rstWithInternalError()
-
-
-* Returns: {undefined}
-
-Shortcut for `http2stream.rstStream()` using error code `0x02` (Internal Error).
-
#### http2stream.session
The `'headers'` event is emitted when an additional block of headers is received
-for a stream, such as when a block of `1xx` informational headers are received.
+for a stream, such as when a block of `1xx` informational headers is received.
The listener callback is passed the [Headers Object][] and flags associated with
the headers.
@@ -1041,7 +1198,7 @@ server.on('stream', (stream) => {
When set, the `options.getTrailers()` function is called immediately after
queuing the last chunk of payload data to be sent. The callback is passed a
-single object (with a `null` prototype) that the listener may used to specify
+single object (with a `null` prototype) that the listener may use to specify
the trailing header fields to send to the peer.
```js
@@ -1058,7 +1215,7 @@ server.on('stream', (stream) => {
```
*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
-"pseudo-header" fields (e.g. `':status'`, `':path'`, etc). An `'error'` event
+pseudo-header fields (e.g. `':status'`, `':path'`, etc). An `'error'` event
will be emitted if the `getTrailers` callback attempts to set such header
fields.
@@ -1115,7 +1272,7 @@ requests.
When set, the `options.getTrailers()` function is called immediately after
queuing the last chunk of payload data to be sent. The callback is passed a
-single object (with a `null` prototype) that the listener may used to specify
+single object (with a `null` prototype) that the listener may use to specify
the trailing header fields to send to the peer.
```js
@@ -1142,7 +1299,7 @@ server.on('close', () => fs.closeSync(fd));
```
*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
-"pseudo-header" fields (e.g. `':status'`, `':path'`, etc). An `'error'` event
+pseudo-header fields (e.g. `':status'`, `':path'`, etc). An `'error'` event
will be emitted if the `getTrailers` callback attempts to set such header
fields.
@@ -1174,7 +1331,7 @@ of the given file:
If an error occurs while attempting to read the file data, the `Http2Stream`
will be closed using an `RST_STREAM` frame using the standard `INTERNAL_ERROR`
-code. If the `onError` callback is defined it will be called, otherwise
+code. If the `onError` callback is defined, then it will be called. Otherwise
the stream will be destroyed.
Example using a file path:
@@ -1234,7 +1391,7 @@ default behavior is to destroy the stream.
When set, the `options.getTrailers()` function is called immediately after
queuing the last chunk of payload data to be sent. The callback is passed a
-single object (with a `null` prototype) that the listener may used to specify
+single object (with a `null` prototype) that the listener may use to specify
the trailing header fields to send to the peer.
```js
@@ -1251,7 +1408,7 @@ server.on('stream', (stream) => {
```
*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
-"pseudo-header" fields (e.g. `':status'`, `':path'`, etc). An `'error'` event
+pseudo-header fields (e.g. `':status'`, `':path'`, etc). An `'error'` event
will be emitted if the `getTrailers` callback attempts to set such header
fields.
@@ -1264,16 +1421,49 @@ added: v8.4.0
In `Http2Server`, there is no `'clientError'` event as there is in
HTTP1. However, there are `'socketError'`, `'sessionError'`, and
-`'streamError'`, for error happened on the socket, session, or stream
-respectively.
+`'streamError'`, for errors emitted on the socket, `Http2Session`, or
+`Http2Stream`.
+
+#### Event: 'checkContinue'
+
+
+* `request` {http2.Http2ServerRequest}
+* `response` {http2.Http2ServerResponse}
+
+If a [`'request'`][] listener is registered or [`http2.createServer()`][] is
+supplied a callback function, the `'checkContinue'` event is emitted each time
+a request with an HTTP `Expect: 100-continue` is received. If this event is
+not listened for, the server will automatically respond with a status
+`100 Continue` as appropriate.
+
+Handling this event involves calling [`response.writeContinue()`][] if the client
+should continue to send the request body, or generating an appropriate HTTP
+response (e.g. 400 Bad Request) if the client should not continue to send the
+request body.
-#### Event: 'socketError'
+Note that when this event is emitted and handled, the [`'request'`][] event will
+not be emitted.
+
+#### Event: 'request'
-The `'socketError'` event is emitted when a `'socketError'` event is emitted by
-an `Http2Session` associated with the server.
+* `request` {http2.Http2ServerRequest}
+* `response` {http2.Http2ServerResponse}
+
+Emitted each time there is a request. Note that there may be multiple requests
+per session. See the [Compatibility API][].
+
+#### Event: 'session'
+
+
+The `'session'` event is emitted when a new `Http2Session` is created by the
+`Http2Server`.
#### Event: 'sessionError'
The `'sessionError'` event is emitted when an `'error'` event is emitted by
-an `Http2Session` object. If no listener is registered for this event, an
-`'error'` event is emitted.
+an `Http2Session` object associated with the `Http2Server`.
#### Event: 'streamError'
-* `socket` {http2.ServerHttp2Stream}
-
-If an `ServerHttp2Stream` emits an `'error'` event, it will be forwarded here.
+If a `ServerHttp2Stream` emits an `'error'` event, it will be forwarded here.
The stream will already be destroyed when this event is triggered.
#### Event: 'stream'
@@ -1325,17 +1512,6 @@ server.on('stream', (stream, headers, flags) => {
});
```
-#### Event: 'request'
-
-
-* `request` {http2.Http2ServerRequest}
-* `response` {http2.Http2ServerResponse}
-
-Emitted each time there is a request. Note that there may be multiple requests
-per session. See the [Compatibility API][].
-
#### Event: 'timeout'
-
-* `request` {http2.Http2ServerRequest}
-* `response` {http2.Http2ServerResponse}
-
-If a [`'request'`][] listener is registered or [`http2.createServer()`][] is
-supplied a callback function, the `'checkContinue'` event is emitted each time
-a request with an HTTP `Expect: 100-continue` is received. If this event is
-not listened for, the server will automatically respond with a status
-`100 Continue` as appropriate.
-
-Handling this event involves calling [`response.writeContinue()`][] if the client
-should continue to send the request body, or generating an appropriate HTTP
-response (e.g. 400 Bad Request) if the client should not continue to send the
-request body.
-
-Note that when this event is emitted and handled, the [`'request'`][] event will
-not be emitted.
-
### Class: Http2SecureServer
The `'sessionError'` event is emitted when an `'error'` event is emitted by
-an `Http2Session` object. If no listener is registered for this event, an
-`'error'` event is emitted on the `Http2Session` instance instead.
-
-#### Event: 'socketError'
-
-
-The `'socketError'` event is emitted when a `'socketError'` event is emitted by
-an `Http2Session` associated with the server.
+an `Http2Session` object associated with the `Http2SecureServer`.
#### Event: 'unknownProtocol'
+
+
+
+```js
+const http2 = require('../common/http2');
+```
+
+### Class: Frame
+
+The `http2.Frame` is a base class that creates a `Buffer` containing a
+serialized HTTP/2 frame header.
+
+
+
+
+
+```js
+// length is a 24-bit unsigned integer
+// type is an 8-bit unsigned integer identifying the frame type
+// flags is an 8-bit unsigned integer containing the flag bits
+// id is the 32-bit stream identifier, if any.
+const frame = new http2.Frame(length, type, flags, id);
+
+// Write the frame data to a socket
+socket.write(frame.data);
+```
+
+The serialized `Buffer` may be retrieved using the `frame.data` property.
+
+### Class: DataFrame extends Frame
+
+The `http2.DataFrame` is a subclass of `http2.Frame` that serializes a `DATA`
+frame.
+
+
+
+
+
+```js
+// id is the 32-bit stream identifier
+// payload is a Buffer containing the DATA payload
+// padlen is an 8-bit integer giving the number of padding bytes to include
+// final is a boolean indicating whether the End-of-stream flag should be set,
+// defaults to false.
+const data = new http2.DataFrame(id, payload, padlen, final);
+
+socket.write(frame.data);
+```
+
+### Class: HeadersFrame
+
+The `http2.HeadersFrame` is a subclass of `http2.Frame` that serializes a
+`HEADERS` frame.
+
+
+
+
+
+```js
+// id is the 32-bit stream identifier
+// payload is a Buffer containing the HEADERS payload (see either
+// http2.kFakeRequestHeaders or http2.kFakeResponseHeaders).
+// padlen is an 8-bit integer giving the number of padding bytes to include
+// final is a boolean indicating whether the End-of-stream flag should be set,
+// defaults to false.
+const data = new http2.HeadersFrame(id, http2.kFakeRequestHeaders,
+ padlen, final);
+
+socket.write(frame.data);
+```
+
+### Class: SettingsFrame
+
+The `http2.SettingsFrame` is a subclass of `http2.Frame` that serializes an
+empty `SETTINGS` frame.
+
+
+
+
+
+```js
+// ack is a boolean indicating whether or not to set the ACK flag.
+const frame = new http2.SettingsFrame(ack);
+
+socket.write(frame.data);
+```
+
+### http2.kFakeRequestHeaders
+
+Set to a `Buffer` instance that contains a minimal set of serialized HTTP/2
+request headers to be used as the payload of a `http2.HeadersFrame`.
+
+
+
+
+
+```js
+const frame = new http2.HeadersFrame(1, http2.kFakeRequestHeaders, 0, true);
+
+socket.write(frame.data);
+```
+
+### http2.kFakeResponseHeaders
+
+Set to a `Buffer` instance that contains a minimal set of serialized HTTP/2
+response headers to be used as the payload a `http2.HeadersFrame`.
+
+
+
+
+
+```js
+const frame = new http2.HeadersFrame(1, http2.kFakeResponseHeaders, 0, true);
+
+socket.write(frame.data);
+```
+
+### http2.kClientMagic
+
+Set to a `Buffer` containing the preamble bytes an HTTP/2 client must send
+upon initial establishment of a connection.
+
+
+
+
+
+```js
+socket.write(http2.kClientMagic);
+```
+
+
[<Array>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
[<ArrayBufferView[]>]: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
[<Boolean>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type
diff --git a/test/common/http2.js b/test/common/http2.js
new file mode 100644
index 00000000000000..1d4c269fffd5b5
--- /dev/null
+++ b/test/common/http2.js
@@ -0,0 +1,139 @@
+/* eslint-disable required-modules */
+'use strict';
+
+// An HTTP/2 testing tool used to create mock frames for direct testing
+// of HTTP/2 endpoints.
+
+const kFrameData = Symbol('frame-data');
+const FLAG_EOS = 0x1;
+const FLAG_ACK = 0x1;
+const FLAG_EOH = 0x4;
+const FLAG_PADDED = 0x8;
+const PADDING = Buffer.alloc(255);
+
+const kClientMagic = Buffer.from('505249202a20485454502f322' +
+ 'e300d0a0d0a534d0d0a0d0a', 'hex');
+
+const kFakeRequestHeaders = Buffer.from('828684410f7777772e65' +
+ '78616d706c652e636f6d', 'hex');
+
+
+const kFakeResponseHeaders = Buffer.from('4803333032580770726976617465611d' +
+ '4d6f6e2c203231204f63742032303133' +
+ '2032303a31333a323120474d546e1768' +
+ '747470733a2f2f7777772e6578616d70' +
+ '6c652e636f6d', 'hex');
+
+function isUint32(val) {
+ return val >>> 0 === val;
+}
+
+function isUint24(val) {
+ return val >>> 0 === val && val <= 0xFFFFFF;
+}
+
+function isUint8(val) {
+ return val >>> 0 === val && val <= 0xFF;
+}
+
+function write32BE(array, pos, val) {
+ if (!isUint32(val))
+ throw new RangeError('val is not a 32-bit number');
+ array[pos++] = (val >> 24) & 0xff;
+ array[pos++] = (val >> 16) & 0xff;
+ array[pos++] = (val >> 8) & 0xff;
+ array[pos++] = val & 0xff;
+}
+
+function write24BE(array, pos, val) {
+ if (!isUint24(val))
+ throw new RangeError('val is not a 24-bit number');
+ array[pos++] = (val >> 16) & 0xff;
+ array[pos++] = (val >> 8) & 0xff;
+ array[pos++] = val & 0xff;
+}
+
+function write8(array, pos, val) {
+ if (!isUint8(val))
+ throw new RangeError('val is not an 8-bit number');
+ array[pos] = val;
+}
+
+class Frame {
+ constructor(length, type, flags, id) {
+ this[kFrameData] = Buffer.alloc(9);
+ write24BE(this[kFrameData], 0, length);
+ write8(this[kFrameData], 3, type);
+ write8(this[kFrameData], 4, flags);
+ write32BE(this[kFrameData], 5, id);
+ }
+
+ get data() {
+ return this[kFrameData];
+ }
+}
+
+class SettingsFrame extends Frame {
+ constructor(ack = false) {
+ let flags = 0;
+ if (ack)
+ flags |= FLAG_ACK;
+ super(0, 4, flags, 0);
+ }
+}
+
+class DataFrame extends Frame {
+ constructor(id, payload, padlen = 0, final = false) {
+ let len = payload.length;
+ let flags = 0;
+ if (final) flags |= FLAG_EOS;
+ const buffers = [payload];
+ if (padlen > 0) {
+ buffers.unshift(Buffer.from([padlen]));
+ buffers.push(PADDING.slice(0, padlen));
+ len += padlen + 1;
+ flags |= FLAG_PADDED;
+ }
+ super(len, 0, flags, id);
+ buffers.unshift(this[kFrameData]);
+ this[kFrameData] = Buffer.concat(buffers);
+ }
+}
+
+class HeadersFrame extends Frame {
+ constructor(id, payload, padlen = 0, final = false) {
+ let len = payload.length;
+ let flags = FLAG_EOH;
+ if (final) flags |= FLAG_EOS;
+ const buffers = [payload];
+ if (padlen > 0) {
+ buffers.unshift(Buffer.from([padlen]));
+ buffers.push(PADDING.slice(0, padlen));
+ len += padlen + 1;
+ flags |= FLAG_PADDED;
+ }
+ super(len, 1, flags, id);
+ buffers.unshift(this[kFrameData]);
+ this[kFrameData] = Buffer.concat(buffers);
+ }
+}
+
+class PingFrame extends Frame {
+ constructor(ack = false) {
+ const buffers = [Buffer.alloc(8)];
+ super(8, 6, ack ? 1 : 0, 0);
+ buffers.unshift(this[kFrameData]);
+ this[kFrameData] = Buffer.concat(buffers);
+ }
+}
+
+module.exports = {
+ Frame,
+ DataFrame,
+ HeadersFrame,
+ SettingsFrame,
+ PingFrame,
+ kFakeRequestHeaders,
+ kFakeResponseHeaders,
+ kClientMagic
+};
diff --git a/test/parallel/test-http2-altsvc.js b/test/parallel/test-http2-altsvc.js
new file mode 100644
index 00000000000000..9fd9a9fc278552
--- /dev/null
+++ b/test/parallel/test-http2-altsvc.js
@@ -0,0 +1,126 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const http2 = require('http2');
+const { URL } = require('url');
+const Countdown = require('../common/countdown');
+
+const server = http2.createServer();
+server.on('stream', common.mustCall((stream) => {
+ stream.session.altsvc('h2=":8000"', stream.id);
+ stream.respond();
+ stream.end('ok');
+}));
+server.on('session', common.mustCall((session) => {
+ // Origin may be specified by string, URL object, or object with an
+ // origin property. For string and URL object, origin is guaranteed
+ // to be an ASCII serialized origin. For object with an origin
+ // property, it is up to the user to ensure proper serialization.
+ session.altsvc('h2=":8000"', 'https://example.org:8111/this');
+ session.altsvc('h2=":8000"', new URL('https://example.org:8111/this'));
+ session.altsvc('h2=":8000"', { origin: 'https://example.org:8111' });
+
+ // Won't error, but won't send anything because the stream does not exist
+ session.altsvc('h2=":8000"', 3);
+
+ // Will error because the numeric stream id is out of valid range
+ [0, -1, 1.1, 0xFFFFFFFF + 1, Infinity, -Infinity].forEach((i) => {
+ common.expectsError(
+ () => session.altsvc('h2=":8000"', i),
+ {
+ code: 'ERR_OUT_OF_RANGE',
+ type: RangeError
+ }
+ );
+ });
+
+ // First argument must be a string
+ [0, {}, [], null, Infinity].forEach((i) => {
+ common.expectsError(
+ () => session.altsvc(i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError
+ }
+ );
+ });
+
+ ['\u0001', 'h2="\uff20"', '👀'].forEach((i) => {
+ common.expectsError(
+ () => session.altsvc(i),
+ {
+ code: 'ERR_INVALID_CHAR',
+ type: TypeError
+ }
+ );
+ });
+
+ [{}, [], true].forEach((i) => {
+ common.expectsError(
+ () => session.altsvc('clear', i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError
+ }
+ );
+ });
+
+ [
+ 'abc:',
+ new URL('abc:'),
+ { origin: 'null' },
+ { origin: '' }
+ ].forEach((i) => {
+ common.expectsError(
+ () => session.altsvc('h2=":8000', i),
+ {
+ code: 'ERR_HTTP2_ALTSVC_INVALID_ORIGIN',
+ type: TypeError
+ }
+ );
+ });
+
+ // arguments + origin are too long for an ALTSVC frame
+ common.expectsError(
+ () => {
+ session.altsvc('h2=":8000"',
+ `http://example.${'a'.repeat(17000)}.org:8000`);
+ },
+ {
+ code: 'ERR_HTTP2_ALTSVC_LENGTH',
+ type: TypeError
+ }
+ );
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+
+ const countdown = new Countdown(4, () => {
+ client.close();
+ server.close();
+ });
+
+ client.on('altsvc', common.mustCall((alt, origin, stream) => {
+ assert.strictEqual(alt, 'h2=":8000"');
+ switch (stream) {
+ case 0:
+ assert.strictEqual(origin, 'https://example.org:8111');
+ break;
+ case 1:
+ assert.strictEqual(origin, '');
+ break;
+ default:
+ assert.fail('should not happen');
+ }
+ countdown.dec();
+ }, 4));
+
+ const req = client.request();
+ req.resume();
+ req.on('close', common.mustCall());
+}));
diff --git a/test/parallel/test-http2-client-data-end.js b/test/parallel/test-http2-client-data-end.js
index 43665029630c12..2f251692d5c412 100644
--- a/test/parallel/test-http2-client-data-end.js
+++ b/test/parallel/test-http2-client-data-end.js
@@ -5,53 +5,37 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
+const Countdown = require('../common/countdown');
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) => {
+ stream.pushStream({ ':path': '/foobar' }, (err, push, headers) => {
+ assert.ifError(err);
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');
- });
+ setImmediate(() => push.end('data'));
stream.end('st');
});
}
- stream.respond({
- 'content-type': 'text/html',
- ':status': 200
- });
+ stream.respond({ 'content-type': 'text/html' });
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);
+ const req = client.request();
- let expected = 2;
- function maybeClose() {
- if (--expected === 0) {
- server.close();
- client.destroy();
- }
- }
+ const countdown = new Countdown(2, () => {
+ server.close();
+ client.close();
+ });
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
@@ -70,13 +54,11 @@ server.listen(0, common.mustCall(() => {
stream.setEncoding('utf8');
let pushData = '';
- stream.on('data', common.mustCall((d) => {
- pushData += d;
- }, 2));
+ stream.on('data', (d) => pushData += d);
stream.on('end', common.mustCall(() => {
assert.strictEqual(pushData, 'pushed by server data');
- maybeClose();
}));
+ stream.on('close', () => countdown.dec());
}));
let data = '';
@@ -85,7 +67,6 @@ server.listen(0, common.mustCall(() => {
req.on('data', common.mustCallAtLeast((d) => data += d));
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'test');
- maybeClose();
}));
- req.end();
+ req.on('close', () => countdown.dec());
}));
diff --git a/test/parallel/test-http2-client-destroy.js b/test/parallel/test-http2-client-destroy.js
index bb93366247aef7..fab8a4fc24d652 100644
--- a/test/parallel/test-http2-client-destroy.js
+++ b/test/parallel/test-http2-client-destroy.js
@@ -8,139 +8,115 @@ if (!common.hasCrypto)
const assert = require('assert');
const h2 = require('http2');
const { kSocket } = require('internal/http2/util');
+const Countdown = require('../common/countdown');
{
const server = h2.createServer();
- server.listen(
- 0,
- common.mustCall(() => {
- const destroyCallbacks = [
- (client) => client.destroy(),
- (client) => client[kSocket].destroy()
- ];
-
- let remaining = destroyCallbacks.length;
-
- destroyCallbacks.forEach((destroyCallback) => {
- const client = h2.connect(`http://localhost:${server.address().port}`);
- client.on(
- 'connect',
- common.mustCall(() => {
- const socket = client[kSocket];
-
- assert(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[kSocket],
- '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();
- }
- })
+ server.listen(0, common.mustCall(() => {
+ const destroyCallbacks = [
+ (client) => client.destroy(),
+ (client) => client[kSocket].destroy()
+ ];
+
+ const countdown = new Countdown(destroyCallbacks.length, () => {
+ server.close();
+ });
+
+ destroyCallbacks.forEach((destroyCallback) => {
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+ client.on('connect', common.mustCall(() => {
+ const socket = client[kSocket];
+
+ assert(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'
+ );
+
+ destroyCallback(client);
+
+ client.on('close', common.mustCall(() => {
+ assert(client.destroyed);
+ }));
+
+ countdown.dec();
+ }));
+ });
+ }));
}
// test destroy before client operations
{
const server = h2.createServer();
- server.listen(
- 0,
- common.mustCall(() => {
- const client = h2.connect(`http://localhost:${server.address().port}`);
- const req = client.request();
- client.destroy();
-
- req.on('response', common.mustNotCall());
- req.resume();
-
- const sessionError = {
- type: Error,
- code: 'ERR_HTTP2_INVALID_SESSION',
- message: 'The session has been destroyed'
- };
-
+ server.listen(0, common.mustCall(() => {
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+ const socket = client[kSocket];
+ socket.on('close', common.mustCall(() => {
+ assert(socket.destroyed);
+ }));
+
+
+ const req = client.request();
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_CANCEL',
+ type: Error,
+ message: 'The pending stream has been canceled'
+ }));
+
+ client.destroy();
+
+ req.on('response', common.mustNotCall());
+
+ const sessionError = {
+ type: Error,
+ code: 'ERR_HTTP2_INVALID_SESSION',
+ message: 'The session has been destroyed'
+ };
+
+ common.expectsError(() => client.request(), sessionError);
+ common.expectsError(() => client.settings({}), sessionError);
+ client.close(); // should be a non-op at this point
+
+ // Wait for setImmediate call from destroy() to complete
+ // so that state.destroyed is set to true
+ setImmediate(() => {
common.expectsError(() => client.request(), sessionError);
common.expectsError(() => client.settings({}), sessionError);
- common.expectsError(() => client.shutdown(), sessionError);
-
- // Wait for setImmediate call from destroy() to complete
- // so that state.destroyed is set to true
- setImmediate(() => {
- common.expectsError(() => client.request(), sessionError);
- common.expectsError(() => client.settings({}), sessionError);
- common.expectsError(() => client.shutdown(), sessionError);
- });
-
- req.on(
- 'end',
- common.mustCall(() => {
- server.close();
- })
- );
- req.end();
- })
- );
+ client.close(); // should be a non-op at this point
+ });
+
+ req.resume();
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => server.close()));
+ }));
}
// test destroy before goaway
{
const server = h2.createServer();
- server.on(
- 'stream',
- common.mustCall((stream) => {
- stream.on('error', common.mustCall());
- stream.session.shutdown();
- })
- );
- server.listen(
- 0,
- common.mustCall(() => {
- const client = h2.connect(`http://localhost:${server.address().port}`);
+ server.on('stream', common.mustCall((stream) => {
+ stream.session.destroy();
+ }));
+
+ server.listen(0, common.mustCall(() => {
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+ // On some platforms (e.g. windows), an ECONNRESET may occur at this
+ // point -- or it may not. Do not make this a mustCall
+ client.on('error', () => {});
+
+ client.on('close', () => {
+ server.close();
+ // calling destroy in here should not matter
+ client.destroy();
+ });
- client.on(
- 'goaway',
- common.mustCall(() => {
- // We ought to be able to destroy the client in here without an error
- server.close();
- client.destroy();
- })
- );
-
- client.request();
- })
- );
+ const req = client.request();
+ // On some platforms (e.g. windows), an ECONNRESET may occur at this
+ // point -- or it may not. Do not make this a mustCall
+ req.on('error', () => {});
+ }));
}
diff --git a/test/parallel/test-http2-client-http1-server.js b/test/parallel/test-http2-client-http1-server.js
index 53f7bf42c465e1..616427b3904e16 100644
--- a/test/parallel/test-http2-client-http1-server.js
+++ b/test/parallel/test-http2-client-http1-server.js
@@ -7,6 +7,7 @@ if (!common.hasCrypto)
const http = require('http');
const http2 = require('http2');
+// Creating an http1 server here...
const server = http.createServer(common.mustNotCall());
server.listen(0, common.mustCall(() => {
@@ -15,13 +16,17 @@ server.listen(0, common.mustCall(() => {
const req = client.request();
req.on('close', common.mustCall());
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_ERROR',
+ type: Error,
+ message: 'Protocol error'
+ }));
+
client.on('error', common.expectsError({
code: 'ERR_HTTP2_ERROR',
type: Error,
message: 'Protocol error'
}));
- client.on('close', (...args) => {
- server.close();
- });
+ client.on('close', common.mustCall(() => server.close()));
}));
diff --git a/test/parallel/test-http2-client-onconnect-errors.js b/test/parallel/test-http2-client-onconnect-errors.js
index 08007753654878..44fe6875602187 100644
--- a/test/parallel/test-http2-client-onconnect-errors.js
+++ b/test/parallel/test-http2-client-onconnect-errors.js
@@ -1,13 +1,14 @@
'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
const {
constants,
Http2Session,
nghttp2ErrorString
} = process.binding('http2');
-const common = require('../common');
-if (!common.hasCrypto)
- common.skip('missing crypto');
const http2 = require('http2');
// tests error handling within requestOnConnect
@@ -69,6 +70,8 @@ server.listen(0, common.mustCall(() => runTest(tests.shift())));
function runTest(test) {
const client = http2.connect(`http://localhost:${server.address().port}`);
+ client.on('close', common.mustCall());
+
const req = client.request({ ':method': 'POST' });
currentError = test.ngError;
@@ -83,15 +86,15 @@ function runTest(test) {
if (test.type === 'stream') {
client.on('error', errorMustNotCall);
req.on('error', errorMustCall);
- req.on('error', common.mustCall(() => {
- client.destroy();
- }));
} else {
client.on('error', errorMustCall);
- req.on('error', errorMustNotCall);
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_CANCEL'
+ }));
}
- req.on('end', common.mustCall(() => {
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => {
client.destroy();
if (!tests.length) {
diff --git a/test/parallel/test-http2-client-port-80.js b/test/parallel/test-http2-client-port-80.js
index a9d19eb5b9f008..fc82e231f6af8f 100644
--- a/test/parallel/test-http2-client-port-80.js
+++ b/test/parallel/test-http2-client-port-80.js
@@ -16,4 +16,10 @@ net.connect = common.mustCall((...args) => {
});
const client = http2.connect('http://localhost:80');
-client.destroy();
+
+// A socket error may or may not occur depending on whether there is something
+// currently listening on port 80. Keep this as a non-op and not a mustCall or
+// mustNotCall.
+client.on('error', () => {});
+
+client.close();
diff --git a/test/parallel/test-http2-client-priority-before-connect.js b/test/parallel/test-http2-client-priority-before-connect.js
index b062107e4ab7f7..a9615d2cd69f12 100644
--- a/test/parallel/test-http2-client-priority-before-connect.js
+++ b/test/parallel/test-http2-client-priority-before-connect.js
@@ -8,31 +8,21 @@ 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(() => {
+server.on('stream', common.mustCall((stream) => {
+ stream.respond();
+ stream.end('ok');
+}));
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
-
- const req = client.request({ ':path': '/' });
+ const req = client.request();
req.priority({});
req.on('response', common.mustCall());
req.resume();
- req.on('end', common.mustCall(() => {
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
- req.end();
-
}));
diff --git a/test/parallel/test-http2-client-promisify-connect.js b/test/parallel/test-http2-client-promisify-connect.js
index b66827c1507302..2eb7da3b9cfd85 100644
--- a/test/parallel/test-http2-client-promisify-connect.js
+++ b/test/parallel/test-http2-client-promisify-connect.js
@@ -15,7 +15,6 @@ server.on('stream', common.mustCall((stream) => {
stream.end('ok');
}));
server.listen(0, common.mustCall(() => {
-
const connect = util.promisify(http2.connect);
connect(`http://localhost:${server.address().port}`)
@@ -28,7 +27,7 @@ server.listen(0, common.mustCall(() => {
req.on('data', (chunk) => data += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'ok');
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-client-request-options-errors.js b/test/parallel/test-http2-client-request-options-errors.js
index 4b146a1bc8c2e4..3ad808cb1fbe23 100644
--- a/test/parallel/test-http2-client-request-options-errors.js
+++ b/test/parallel/test-http2-client-request-options-errors.js
@@ -33,29 +33,27 @@ server.listen(0, common.mustCall(() => {
const port = server.address().port;
const client = http2.connect(`http://localhost:${port}`);
- Object.keys(optionsToTest).forEach((option) => {
- Object.keys(types).forEach((type) => {
- if (type === optionsToTest[option]) {
- return;
- }
-
- common.expectsError(
- () => client.request({
- ':method': 'CONNECT',
- ':authority': `localhost:${port}`
- }, {
- [option]: types[type]
- }),
- {
- type: TypeError,
- code: 'ERR_INVALID_OPT_VALUE',
- message: `The value "${String(types[type])}" is invalid ` +
- `for option "${option}"`
- }
- );
+ client.on('connect', () => {
+ Object.keys(optionsToTest).forEach((option) => {
+ Object.keys(types).forEach((type) => {
+ if (type === optionsToTest[option])
+ return;
+
+ common.expectsError(
+ () => client.request({
+ ':method': 'CONNECT',
+ ':authority': `localhost:${port}`
+ }, {
+ [option]: types[type]
+ }), {
+ type: TypeError,
+ code: 'ERR_INVALID_OPT_VALUE',
+ message: `The value "${String(types[type])}" is invalid ` +
+ `for option "${option}"`
+ });
+ });
});
+ server.close();
+ client.close();
});
-
- server.close();
- client.destroy();
}));
diff --git a/test/parallel/test-http2-client-rststream-before-connect.js b/test/parallel/test-http2-client-rststream-before-connect.js
index eb3a0087d7893c..b0faaa5de2a398 100644
--- a/test/parallel/test-http2-client-rststream-before-connect.js
+++ b/test/parallel/test-http2-client-rststream-before-connect.js
@@ -8,33 +8,37 @@ const h2 = require('http2');
const server = h2.createServer();
server.on('stream', (stream) => {
+ stream.on('close', common.mustCall());
stream.respond();
stream.end('ok');
});
-server.listen(0);
-
-server.on('listening', common.mustCall(() => {
-
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
-
- const req = client.request({ ':path': '/' });
- req.rstStream(0);
+ const req = client.request();
+ req.close(1);
+ assert.strictEqual(req.closed, true);
// make sure that destroy is called
req._destroy = common.mustCall(req._destroy.bind(req));
// second call doesn't do anything
- assert.doesNotThrow(() => req.rstStream(8));
+ assert.doesNotThrow(() => req.close(8));
req.on('close', common.mustCall((code) => {
assert.strictEqual(req.destroyed, true);
- assert.strictEqual(code, 0);
+ assert.strictEqual(code, 1);
server.close();
- client.destroy();
+ client.close();
+ }));
+
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 1'
}));
- req.on('response', common.mustNotCall());
+ req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall());
req.end();
diff --git a/test/parallel/test-http2-client-set-priority.js b/test/parallel/test-http2-client-set-priority.js
index f3e1d7afa50d7e..64b7b56dfa2543 100644
--- a/test/parallel/test-http2-client-set-priority.js
+++ b/test/parallel/test-http2-client-set-priority.js
@@ -10,26 +10,20 @@ 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.respond();
stream.end('test');
}));
server.listen(0, common.mustCall(() => {
- const port = server.address().port;
- const client = http2.connect(`http://localhost:${port}`);
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+ const req = client.request({}, { weight: actual });
- const headers = { ':path': '/' };
- const req = client.request(headers, { weight: actual });
-
- req.on('data', common.mustCall(() => {}));
- req.on('end', common.mustCall(() => {
+ req.on('data', common.mustCall());
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
- req.end();
}));
};
diff --git a/test/parallel/test-http2-client-settings-before-connect.js b/test/parallel/test-http2-client-settings-before-connect.js
index 27caa9e601897b..4642bf5220f554 100644
--- a/test/parallel/test-http2-client-settings-before-connect.js
+++ b/test/parallel/test-http2-client-settings-before-connect.js
@@ -3,62 +3,53 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
-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(() => {
+server.on('stream', common.mustCall((stream, headers, flags) => {
+ stream.respond();
+ stream.end('ok');
+}));
+server.on('session', common.mustCall((session) => {
+ session.on('remoteSettings', common.mustCall(2));
+}));
+server.listen(0, 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);
+ [
+ ['headerTableSize', -1, RangeError],
+ ['headerTableSize', 2 ** 32, RangeError],
+ ['initialWindowSize', -1, RangeError],
+ ['initialWindowSize', 2 ** 32, RangeError],
+ ['maxFrameSize', 1, RangeError],
+ ['maxFrameSize', 2 ** 24, RangeError],
+ ['maxConcurrentStreams', -1, RangeError],
+ ['maxConcurrentStreams', 2 ** 31, RangeError],
+ ['maxHeaderListSize', -1, RangeError],
+ ['maxHeaderListSize', 2 ** 32, RangeError],
+ ['enablePush', 'a', TypeError],
+ ['enablePush', 1, TypeError],
+ ['enablePush', 0, TypeError],
+ ['enablePush', null, TypeError],
+ ['enablePush', {}, TypeError]
+ ].forEach((i) => {
+ common.expectsError(
+ () => client.settings({ [i[0]]: i[1] }),
+ {
+ code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
+ type: i[2] });
});
client.settings({ maxFrameSize: 1234567 });
- const req = client.request({ ':path': '/' });
-
+ const req = client.request();
req.on('response', common.mustCall());
req.resume();
- req.on('end', common.mustCall(() => {
+ req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
- req.end();
-
}));
diff --git a/test/parallel/test-http2-client-shutdown-before-connect.js b/test/parallel/test-http2-client-shutdown-before-connect.js
index 4fed0ee3ad0703..bd971ebf7de69c 100644
--- a/test/parallel/test-http2-client-shutdown-before-connect.js
+++ b/test/parallel/test-http2-client-shutdown-before-connect.js
@@ -10,15 +10,7 @@ const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustNotCall());
-server.listen(0);
-
-server.on('listening', common.mustCall(() => {
-
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
-
- client.shutdown({ graceful: true }, common.mustCall(() => {
- server.close();
- client.destroy();
- }));
-
+ client.close(common.mustCall(() => server.close()));
}));
diff --git a/test/parallel/test-http2-client-socket-destroy.js b/test/parallel/test-http2-client-socket-destroy.js
index faf4643b0304e3..3eb7e898edcfc1 100644
--- a/test/parallel/test-http2-client-socket-destroy.js
+++ b/test/parallel/test-http2-client-socket-destroy.js
@@ -14,38 +14,27 @@ const body =
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
+server.on('stream', common.mustCall((stream) => {
stream.on('aborted', common.mustCall());
-
- stream.respond({
- 'content-type': 'text/html',
- ':status': 200
- });
+ stream.on('close', common.mustCall());
+ stream.respond();
stream.write(body);
-}
-
-server.listen(0);
+ // purposefully do not end()
+}));
-server.on('listening', common.mustCall(function() {
+server.listen(0, common.mustCall(function() {
const client = h2.connect(`http://localhost:${this.address().port}`);
-
- const req = client.request({ ':path': '/' });
+ const req = client.request();
req.on('response', common.mustCall(() => {
// send a premature socket close
client[kSocket].destroy();
}));
- req.on('data', common.mustNotCall());
- req.on('end', common.mustCall(() => {
- server.close();
- }));
+ req.resume();
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => server.close()));
// On the client, the close event must call
client.on('close', common.mustCall());
- req.end();
-
}));
diff --git a/test/parallel/test-http2-client-stream-destroy-before-connect.js b/test/parallel/test-http2-client-stream-destroy-before-connect.js
index 06afbf3ce8ceb2..a2412b9f1d646a 100644
--- a/test/parallel/test-http2-client-stream-destroy-before-connect.js
+++ b/test/parallel/test-http2-client-stream-destroy-before-connect.js
@@ -20,36 +20,29 @@ server.on('stream', (stream) => {
assert.strictEqual(err.code, 'ERR_HTTP2_STREAM_ERROR');
assert.strictEqual(err.message, 'Stream closed with error code 2');
});
- stream.respond({});
+ stream.respond();
stream.end();
});
-server.listen(0);
-
-server.on('listening', common.mustCall(() => {
-
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
- const req = client.request({ ':path': '/' });
- const err = new Error('test');
- req.destroy(err);
+ const req = client.request();
+ req.destroy(new Error('test'));
- req.on('error', common.mustCall((err) => {
- common.expectsError({
- type: Error,
- message: 'test'
- })(err);
+ req.on('error', common.expectsError({
+ type: Error,
+ message: 'test'
}));
req.on('close', common.mustCall((code) => {
assert.strictEqual(req.rstCode, NGHTTP2_INTERNAL_ERROR);
assert.strictEqual(code, NGHTTP2_INTERNAL_ERROR);
server.close();
- client.destroy();
+ client.close();
}));
req.on('response', common.mustNotCall());
req.resume();
req.on('end', common.mustCall());
-
}));
diff --git a/test/parallel/test-http2-client-unescaped-path.js b/test/parallel/test-http2-client-unescaped-path.js
index adfbd61fe762b4..190f8ce75e8917 100644
--- a/test/parallel/test-http2-client-unescaped-path.js
+++ b/test/parallel/test-http2-client-unescaped-path.js
@@ -4,6 +4,7 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');
+const Countdown = require('../common/countdown');
const server = http2.createServer();
@@ -13,14 +14,12 @@ const count = 32;
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
+ client.setMaxListeners(33);
- let remaining = count + 1;
- function maybeClose() {
- if (--remaining === 0) {
- server.close();
- client.destroy();
- }
- }
+ const countdown = new Countdown(count + 1, () => {
+ server.close();
+ client.close();
+ });
// nghttp2 will catch the bad header value for us.
function doTest(i) {
@@ -30,7 +29,7 @@ server.listen(0, common.mustCall(() => {
type: Error,
message: 'Stream closed with error code 1'
}));
- req.on('close', common.mustCall(maybeClose));
+ req.on('close', common.mustCall(() => countdown.dec()));
}
for (let i = 0; i <= count; i += 1)
diff --git a/test/parallel/test-http2-client-upload.js b/test/parallel/test-http2-client-upload.js
index 8fb5f369ca4cb7..70a8ff3ced01c6 100644
--- a/test/parallel/test-http2-client-upload.js
+++ b/test/parallel/test-http2-client-upload.js
@@ -9,6 +9,7 @@ const assert = require('assert');
const http2 = require('http2');
const fs = require('fs');
const fixtures = require('../common/fixtures');
+const Countdown = require('../common/countdown');
const loc = fixtures.path('person.jpg');
let fileData;
@@ -34,20 +35,21 @@ fs.readFile(loc, common.mustCall((err, data) => {
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
- let remaining = 2;
- function maybeClose() {
- if (--remaining === 0) {
- server.close();
- client.shutdown();
- }
- }
+ const countdown = new Countdown(2, () => {
+ server.close();
+ client.close();
+ });
const req = client.request({ ':method': 'POST' });
req.on('response', common.mustCall());
+
req.resume();
- req.on('end', common.mustCall(maybeClose));
+ req.on('end', common.mustCall());
+
+ req.on('finish', () => countdown.dec());
const str = fs.createReadStream(loc);
- req.on('finish', common.mustCall(maybeClose));
+ str.on('end', common.mustCall());
+ str.on('close', () => countdown.dec());
str.pipe(req);
}));
}));
diff --git a/test/parallel/test-http2-client-write-before-connect.js b/test/parallel/test-http2-client-write-before-connect.js
index 26674dcad369e3..6588d7dccd139d 100644
--- a/test/parallel/test-http2-client-write-before-connect.js
+++ b/test/parallel/test-http2-client-write-before-connect.js
@@ -8,47 +8,30 @@ 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) {
+server.on('stream', common.mustCall((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(() => {
+ stream.respond();
+ stream.end('ok');
+}));
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
- const req = client.request({
- [HTTP2_HEADER_PATH]: '/',
- [HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST });
+ const req = client.request({ ':method': 'POST' });
req.write('some data ');
- req.write('more data');
+ req.end('more data');
req.on('response', common.mustCall());
req.resume();
- req.on('end', common.mustCall(() => {
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
- req.end();
-
}));
diff --git a/test/parallel/test-http2-compat-errors.js b/test/parallel/test-http2-compat-errors.js
index 5774d1a922bd52..c84318bad68e35 100644
--- a/test/parallel/test-http2-compat-errors.js
+++ b/test/parallel/test-http2-compat-errors.js
@@ -4,9 +4,7 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
-const assert = require('assert');
const h2 = require('http2');
-const { Http2Stream } = require('internal/http2/core');
// Errors should not be reported both in Http2ServerRequest
// and Http2ServerResponse
@@ -14,6 +12,7 @@ const { Http2Stream } = require('internal/http2/core');
let expected = null;
const server = h2.createServer(common.mustCall(function(req, res) {
+ res.stream.on('error', common.mustCall());
req.on('error', common.mustNotCall());
res.on('error', common.mustNotCall());
req.on('aborted', common.mustCall());
@@ -26,27 +25,12 @@ const server = h2.createServer(common.mustCall(function(req, res) {
server.close();
}));
-server.on('streamError', common.mustCall(function(err, stream) {
- assert.strictEqual(err, expected);
- assert.strictEqual(stream instanceof Http2Stream, true);
-}));
-
server.listen(0, common.mustCall(function() {
- const port = server.address().port;
-
- 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('data', common.mustCall(function(chunk) {
- // cause an error on the server side
+ const url = `http://localhost:${server.address().port}`;
+ const client = h2.connect(url, common.mustCall(() => {
+ const request = client.request();
+ request.on('data', common.mustCall((chunk) => {
client.destroy();
}));
- request.end();
}));
}));
diff --git a/test/parallel/test-http2-compat-expect-continue-check.js b/test/parallel/test-http2-compat-expect-continue-check.js
index 800df1c432944f..6aded8b52935c1 100644
--- a/test/parallel/test-http2-compat-expect-continue-check.js
+++ b/test/parallel/test-http2-compat-expect-continue-check.js
@@ -12,74 +12,47 @@ const testResBody = 'other stuff!\n';
// through server receiving it, triggering 'checkContinue' custom handler,
// writing the rest of the request to finally the client receiving to.
-function handler(req, res) {
- console.error('Server sent full response');
+const server = http2.createServer(
+ common.mustNotCall('Full request received before 100 Continue')
+);
- res.writeHead(200, {
- 'content-type': 'text/plain',
- 'abcd': '1'
- });
+server.on('checkContinue', common.mustCall((req, res) => {
+ res.writeContinue();
+ res.writeHead(200, {});
res.end(testResBody);
// should simply return false if already too late to write
assert.strictEqual(res.writeContinue(), false);
res.on('finish', common.mustCall(
() => process.nextTick(() => assert.strictEqual(res.writeContinue(), false))
));
-}
-
-const server = http2.createServer(
- common.mustNotCall('Full request received before 100 Continue')
-);
-
-server.on('checkContinue', common.mustCall((req, res) => {
- console.error('Server received Expect: 100-continue');
-
- res.writeContinue();
-
- // timeout so that we allow the client to receive continue first
- setTimeout(
- common.mustCall(() => handler(req, res)),
- common.platformTimeout(100)
- );
}));
-server.listen(0);
-
-server.on('listening', common.mustCall(() => {
+server.listen(0, common.mustCall(() => {
let body = '';
- const port = server.address().port;
- const client = http2.connect(`http://localhost:${port}`);
+ const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request({
':method': 'POST',
- ':path': '/world',
expect: '100-continue'
});
- console.error('Client sent request');
let gotContinue = false;
req.on('continue', common.mustCall(() => {
- console.error('Client received 100-continue');
gotContinue = true;
}));
req.on('response', common.mustCall((headers) => {
- console.error('Client received response headers');
-
assert.strictEqual(gotContinue, true);
assert.strictEqual(headers[':status'], 200);
- assert.strictEqual(headers['abcd'], '1');
+ req.end();
}));
req.setEncoding('utf-8');
req.on('data', common.mustCall((chunk) => { body += chunk; }));
req.on('end', common.mustCall(() => {
- console.error('Client received full response');
-
assert.strictEqual(body, testResBody);
-
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-compat-expect-continue.js b/test/parallel/test-http2-compat-expect-continue.js
index 6f08e813ef385a..42fa80ae4e8620 100644
--- a/test/parallel/test-http2-compat-expect-continue.js
+++ b/test/parallel/test-http2-compat-expect-continue.js
@@ -17,8 +17,6 @@ const server = http2.createServer();
let sentResponse = false;
server.on('request', common.mustCall((req, res) => {
- console.error('Server sent full response');
-
res.end(testResBody);
sentResponse = true;
}));
@@ -28,38 +26,29 @@ server.listen(0);
server.on('listening', common.mustCall(() => {
let body = '';
- const port = server.address().port;
- const client = http2.connect(`http://localhost:${port}`);
+ const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request({
':method': 'POST',
- ':path': '/world',
expect: '100-continue'
});
- console.error('Client sent request');
let gotContinue = false;
req.on('continue', common.mustCall(() => {
- console.error('Client received 100-continue');
gotContinue = true;
}));
req.on('response', common.mustCall((headers) => {
- console.error('Client received response headers');
-
assert.strictEqual(gotContinue, true);
assert.strictEqual(sentResponse, true);
assert.strictEqual(headers[':status'], 200);
+ req.end();
}));
req.setEncoding('utf8');
req.on('data', common.mustCall((chunk) => { body += chunk; }));
-
req.on('end', common.mustCall(() => {
- console.error('Client received full response');
-
assert.strictEqual(body, testResBody);
-
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-compat-expect-handling.js b/test/parallel/test-http2-compat-expect-handling.js
index 0a5de368c6cfff..f36032c972fc45 100644
--- a/test/parallel/test-http2-compat-expect-handling.js
+++ b/test/parallel/test-http2-compat-expect-handling.js
@@ -39,7 +39,7 @@ function nextTest(testsToRun) {
}));
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
nextTest(testsToRun - 1);
}));
}
diff --git a/test/parallel/test-http2-compat-method-connect.js b/test/parallel/test-http2-compat-method-connect.js
index 1f43b3891b24ed..21ad23e92ba65b 100644
--- a/test/parallel/test-http2-compat-method-connect.js
+++ b/test/parallel/test-http2-compat-method-connect.js
@@ -33,7 +33,7 @@ function testMethodConnect(testsToRun) {
}));
req.resume();
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
testMethodConnect(testsToRun - 1);
}));
req.end();
diff --git a/test/parallel/test-http2-compat-serverrequest-end.js b/test/parallel/test-http2-compat-serverrequest-end.js
index b6bfd04089a103..d34372118582db 100644
--- a/test/parallel/test-http2-compat-serverrequest-end.js
+++ b/test/parallel/test-http2-compat-serverrequest-end.js
@@ -31,18 +31,11 @@ server.listen(0, common.mustCall(function() {
}));
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);
+ const client = h2.connect(url, common.mustCall(() => {
+ const request = client.request();
request.resume();
- request.on('end', common.mustCall(function() {
- client.destroy();
+ request.on('end', common.mustCall(() => {
+ client.close();
}));
- request.end();
}));
}));
diff --git a/test/parallel/test-http2-compat-serverrequest-headers.js b/test/parallel/test-http2-compat-serverrequest-headers.js
index 58cc52c64f6c91..5843104c019189 100644
--- a/test/parallel/test-http2-compat-serverrequest-headers.js
+++ b/test/parallel/test-http2-compat-serverrequest-headers.js
@@ -79,7 +79,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverrequest-pause.js b/test/parallel/test-http2-compat-serverrequest-pause.js
index f8494bb0ddee39..62a23997c75bd8 100644
--- a/test/parallel/test-http2-compat-serverrequest-pause.js
+++ b/test/parallel/test-http2-compat-serverrequest-pause.js
@@ -46,7 +46,7 @@ server.listen(0, common.mustCall(() => {
request.resume();
request.end(testStr);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-compat-serverrequest-pipe.js b/test/parallel/test-http2-compat-serverrequest-pipe.js
index 04c8cfe546f329..becc62c6621e7f 100644
--- a/test/parallel/test-http2-compat-serverrequest-pipe.js
+++ b/test/parallel/test-http2-compat-serverrequest-pipe.js
@@ -35,7 +35,7 @@ server.listen(0, common.mustCall(() => {
function maybeClose() {
if (--remaining === 0) {
server.close();
- client.destroy();
+ client.close();
}
}
diff --git a/test/parallel/test-http2-compat-serverrequest-settimeout.js b/test/parallel/test-http2-compat-serverrequest-settimeout.js
index 460eb576bfd4f6..f7189161802301 100644
--- a/test/parallel/test-http2-compat-serverrequest-settimeout.js
+++ b/test/parallel/test-http2-compat-serverrequest-settimeout.js
@@ -12,7 +12,6 @@ const server = http2.createServer();
server.on('request', (req, res) => {
req.setTimeout(msecs, common.mustCall(() => {
res.end();
- req.setTimeout(msecs, common.mustNotCall());
}));
res.on('finish', common.mustCall(() => {
req.setTimeout(msecs, common.mustNotCall());
@@ -35,7 +34,7 @@ server.listen(0, common.mustCall(() => {
':authority': `localhost:${port}`
});
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
}));
req.resume();
req.end();
diff --git a/test/parallel/test-http2-compat-serverrequest-trailers.js b/test/parallel/test-http2-compat-serverrequest-trailers.js
index b4d90281918d9e..285178cab66816 100644
--- a/test/parallel/test-http2-compat-serverrequest-trailers.js
+++ b/test/parallel/test-http2-compat-serverrequest-trailers.js
@@ -62,7 +62,7 @@ server.listen(0, common.mustCall(function() {
request.resume();
request.on('end', common.mustCall(function() {
server.close();
- client.destroy();
+ client.close();
}));
request.write('test\n');
request.end('test');
diff --git a/test/parallel/test-http2-compat-serverrequest.js b/test/parallel/test-http2-compat-serverrequest.js
index edcd7a8f8cdea4..d92da61d943cb7 100644
--- a/test/parallel/test-http2-compat-serverrequest.js
+++ b/test/parallel/test-http2-compat-serverrequest.js
@@ -46,7 +46,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-close.js b/test/parallel/test-http2-compat-serverresponse-close.js
index 35e39b9670868e..0ff6bd3a83f600 100644
--- a/test/parallel/test-http2-compat-serverresponse-close.js
+++ b/test/parallel/test-http2-compat-serverresponse-close.js
@@ -16,26 +16,17 @@ const server = h2.createServer(common.mustCall((req, res) => {
req.on('close', common.mustCall());
res.on('close', common.mustCall());
+ req.on('error', common.mustNotCall());
}));
server.listen(0);
-server.on('listening', function() {
- const port = server.address().port;
-
- 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);
+server.on('listening', () => {
+ const url = `http://localhost:${server.address().port}`;
+ const client = h2.connect(url, common.mustCall(() => {
+ const request = client.request();
request.on('data', common.mustCall(function(chunk) {
- // cause an error on the server side
client.destroy();
server.close();
}));
- request.end();
}));
});
diff --git a/test/parallel/test-http2-compat-serverresponse-createpushresponse.js b/test/parallel/test-http2-compat-serverresponse-createpushresponse.js
index bd9d5c1399792a..b168c3563c32e2 100644
--- a/test/parallel/test-http2-compat-serverresponse-createpushresponse.js
+++ b/test/parallel/test-http2-compat-serverresponse-createpushresponse.js
@@ -43,7 +43,7 @@ const server = h2.createServer((request, response) => {
':path': '/pushed',
':method': 'GET'
}, common.mustCall((error) => {
- assert.strictEqual(error.code, 'ERR_HTTP2_STREAM_CLOSED');
+ assert.strictEqual(error.code, 'ERR_HTTP2_INVALID_STREAM');
}));
});
}));
@@ -61,7 +61,7 @@ server.listen(0, common.mustCall(() => {
let remaining = 2;
function maybeClose() {
if (--remaining === 0) {
- client.destroy();
+ client.close();
server.close();
}
}
diff --git a/test/parallel/test-http2-compat-serverresponse-destroy.js b/test/parallel/test-http2-compat-serverresponse-destroy.js
index 77e761b6227702..54214737840061 100644
--- a/test/parallel/test-http2-compat-serverresponse-destroy.js
+++ b/test/parallel/test-http2-compat-serverresponse-destroy.js
@@ -5,6 +5,7 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
+const Countdown = require('../common/countdown');
// Check that destroying the Http2ServerResponse stream produces
// the expected result, including the ability to throw an error
@@ -30,63 +31,54 @@ const server = http2.createServer(common.mustCall((req, res) => {
if (req.url !== '/') {
nextError = errors.shift();
}
+
res.destroy(nextError);
}, 3));
-server.on(
- 'streamError',
- common.mustCall((err) => assert.strictEqual(err, nextError), 2)
-);
-
server.listen(0, common.mustCall(() => {
- const port = server.address().port;
- const client = http2.connect(`http://localhost:${port}`);
- const req = client.request({
- ':path': '/',
- ':method': 'GET',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- });
+ const client = http2.connect(`http://localhost:${server.address().port}`);
- req.on('response', common.mustNotCall());
- req.on('error', common.mustNotCall());
- req.on('end', common.mustCall());
+ const countdown = new Countdown(3, () => {
+ server.close();
+ client.close();
+ });
- req.resume();
- req.end();
+ {
+ const req = client.request();
+ req.on('response', common.mustNotCall());
+ req.on('error', common.mustNotCall());
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => countdown.dec()));
+ req.resume();
+ }
- const req2 = client.request({
- ':path': '/error',
- ':method': 'GET',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- });
+ {
+ const req = client.request({ ':path': '/error' });
- req2.on('response', common.mustNotCall());
- req2.on('error', common.mustNotCall());
- req2.on('end', common.mustCall());
+ req.on('response', common.mustNotCall());
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 2'
+ }));
+ req.on('close', common.mustCall(() => countdown.dec()));
- req2.resume();
- req2.end();
+ req.resume();
+ req.on('end', common.mustCall());
+ }
- const req3 = client.request({
- ':path': '/error',
- ':method': 'GET',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- });
+ {
+ const req = client.request({ ':path': '/error' });
- req3.on('response', common.mustNotCall());
- req3.on('error', common.expectsError({
- code: 'ERR_HTTP2_STREAM_ERROR',
- type: Error,
- message: 'Stream closed with error code 2'
- }));
- req3.on('end', common.mustCall(() => {
- server.close();
- client.destroy();
- }));
+ req.on('response', common.mustNotCall());
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 2'
+ }));
+ req.on('close', common.mustCall(() => countdown.dec()));
- req3.resume();
- req3.end();
+ req.resume();
+ req.on('end', common.mustCall());
+ }
}));
diff --git a/test/parallel/test-http2-compat-serverresponse-drain.js b/test/parallel/test-http2-compat-serverresponse-drain.js
index e2465cfa00d1f6..7ccbb1f4d21209 100644
--- a/test/parallel/test-http2-compat-serverresponse-drain.js
+++ b/test/parallel/test-http2-compat-serverresponse-drain.js
@@ -37,7 +37,7 @@ server.listen(0, common.mustCall(() => {
request.on('end', common.mustCall(function() {
assert.strictEqual(data, testString.repeat(2));
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-compat-serverresponse-end.js b/test/parallel/test-http2-compat-serverresponse-end.js
index 366d52321554fe..0e846a5948e3cc 100644
--- a/test/parallel/test-http2-compat-serverresponse-end.js
+++ b/test/parallel/test-http2-compat-serverresponse-end.js
@@ -52,7 +52,7 @@ const {
request.on('data', (chunk) => (data += chunk));
request.on('end', mustCall(() => {
strictEqual(data, 'end');
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
@@ -83,7 +83,7 @@ const {
request.on('data', (chunk) => (data += chunk));
request.on('end', mustCall(() => {
strictEqual(data, 'test\uD83D\uDE00');
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
@@ -110,7 +110,7 @@ const {
};
const request = client.request(headers);
request.on('data', mustNotCall());
- request.on('end', mustCall(() => client.destroy()));
+ request.on('end', mustCall(() => client.close()));
request.end();
request.resume();
}));
@@ -143,7 +143,7 @@ const {
}));
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
@@ -172,7 +172,7 @@ const {
const request = client.request(headers);
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
@@ -208,7 +208,7 @@ const {
}));
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
@@ -243,7 +243,7 @@ const {
}));
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
@@ -283,7 +283,7 @@ const {
}));
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
@@ -315,7 +315,7 @@ const {
}));
request.on('data', mustNotCall());
request.on('end', mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
diff --git a/test/parallel/test-http2-compat-serverresponse-finished.js b/test/parallel/test-http2-compat-serverresponse-finished.js
index b816b922202dd6..ceaa6eb5c3cf2c 100644
--- a/test/parallel/test-http2-compat-serverresponse-finished.js
+++ b/test/parallel/test-http2-compat-serverresponse-finished.js
@@ -39,7 +39,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-flushheaders.js b/test/parallel/test-http2-compat-serverresponse-flushheaders.js
index 68d4789f69be53..d155b07863d26c 100644
--- a/test/parallel/test-http2-compat-serverresponse-flushheaders.js
+++ b/test/parallel/test-http2-compat-serverresponse-flushheaders.js
@@ -51,7 +51,7 @@ server.listen(0, common.mustCall(function() {
serverResponse.end();
}, 1));
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js b/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js
index fb1e369f786bf0..99e3ccc948184e 100644
--- a/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js
+++ b/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js
@@ -13,8 +13,6 @@ const server = h2.createServer();
server.listen(0, common.mustCall(function() {
const port = server.address().port;
server.once('request', common.mustCall(function(request, response) {
- response.destroy();
-
response.on('finish', common.mustCall(() => {
assert.strictEqual(response.headersSent, false);
assert.doesNotThrow(() => response.setHeader('test', 'value'));
@@ -27,6 +25,8 @@ server.listen(0, common.mustCall(function() {
server.close();
});
}));
+
+ response.destroy();
}));
const url = `http://localhost:${port}`;
@@ -39,7 +39,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-headers.js b/test/parallel/test-http2-compat-serverresponse-headers.js
index 6da74307e0d340..7b5313b8e7b037 100644
--- a/test/parallel/test-http2-compat-serverresponse-headers.js
+++ b/test/parallel/test-http2-compat-serverresponse-headers.js
@@ -179,7 +179,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-settimeout.js b/test/parallel/test-http2-compat-serverresponse-settimeout.js
index 6d06d07f7dc0ab..bb09633727ccf7 100644
--- a/test/parallel/test-http2-compat-serverresponse-settimeout.js
+++ b/test/parallel/test-http2-compat-serverresponse-settimeout.js
@@ -12,7 +12,6 @@ const server = http2.createServer();
server.on('request', (req, res) => {
res.setTimeout(msecs, common.mustCall(() => {
res.end();
- res.setTimeout(msecs, common.mustNotCall());
}));
res.on('finish', common.mustCall(() => {
res.setTimeout(msecs, common.mustNotCall());
@@ -35,7 +34,7 @@ server.listen(0, common.mustCall(() => {
':authority': `localhost:${port}`
});
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
}));
req.resume();
req.end();
diff --git a/test/parallel/test-http2-compat-serverresponse-statuscode.js b/test/parallel/test-http2-compat-serverresponse-statuscode.js
index 6a573da88f9858..ecb6da5df68c11 100644
--- a/test/parallel/test-http2-compat-serverresponse-statuscode.js
+++ b/test/parallel/test-http2-compat-serverresponse-statuscode.js
@@ -69,7 +69,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-statusmessage-property-set.js b/test/parallel/test-http2-compat-serverresponse-statusmessage-property-set.js
index 45a876d674313b..87e172402899f2 100644
--- a/test/parallel/test-http2-compat-serverresponse-statusmessage-property-set.js
+++ b/test/parallel/test-http2-compat-serverresponse-statusmessage-property-set.js
@@ -42,7 +42,7 @@ server.listen(0, common.mustCall(function() {
assert.strictEqual(headers[':status'], 200);
}, 1));
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-statusmessage-property.js b/test/parallel/test-http2-compat-serverresponse-statusmessage-property.js
index 21a5b6ea4e2820..8a083cf3ba1638 100644
--- a/test/parallel/test-http2-compat-serverresponse-statusmessage-property.js
+++ b/test/parallel/test-http2-compat-serverresponse-statusmessage-property.js
@@ -41,7 +41,7 @@ server.listen(0, common.mustCall(function() {
assert.strictEqual(headers[':status'], 200);
}, 1));
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-statusmessage.js b/test/parallel/test-http2-compat-serverresponse-statusmessage.js
index 841bafe724a7a8..dee916d1aeef54 100644
--- a/test/parallel/test-http2-compat-serverresponse-statusmessage.js
+++ b/test/parallel/test-http2-compat-serverresponse-statusmessage.js
@@ -45,7 +45,7 @@ server.listen(0, common.mustCall(function() {
assert.strictEqual(headers['foo-bar'], 'abc123');
}, 1));
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-serverresponse-trailers.js b/test/parallel/test-http2-compat-serverresponse-trailers.js
index 7332f9e8d0b63d..66ad8843fa33b9 100644
--- a/test/parallel/test-http2-compat-serverresponse-trailers.js
+++ b/test/parallel/test-http2-compat-serverresponse-trailers.js
@@ -68,7 +68,7 @@ server.listen(0, common.mustCall(() => {
}));
request.resume();
request.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-compat-serverresponse-write-no-cb.js b/test/parallel/test-http2-compat-serverresponse-write-no-cb.js
index 58a4ca053d222c..a62bb1b0ac78f1 100644
--- a/test/parallel/test-http2-compat-serverresponse-write-no-cb.js
+++ b/test/parallel/test-http2-compat-serverresponse-write-no-cb.js
@@ -6,44 +6,33 @@ const { mustCall,
hasCrypto, skip } = require('../common');
if (!hasCrypto)
skip('missing crypto');
-const { throws } = require('assert');
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();
+ const request = client.request();
request.resume();
+ request.on('end', mustCall());
+ request.on('close', mustCall(() => {
+ client.close();
+ }));
}));
server.once('request', mustCall((request, response) => {
client.destroy();
response.stream.session.on('close', mustCall(() => {
response.on('error', mustNotCall());
- throws(
+ expectsError(
() => { response.write('muahaha'); },
- expectsError({
- code: 'ERR_HTTP2_STREAM_CLOSED',
- type: Error,
- message: 'The stream is already closed'
- })
+ {
+ code: 'ERR_HTTP2_INVALID_STREAM'
+ }
);
server.close();
}));
@@ -57,21 +46,21 @@ const expectedError = expectsError({
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();
+ const request = client.request();
request.resume();
+ request.on('end', mustCall());
+ request.on('close', mustCall(() => client.close()));
}));
server.once('request', mustCall((request, response) => {
client.destroy();
response.stream.session.on('close', mustCall(() => {
- response.write('muahaha', mustCall(expectedError));
+ expectsError(
+ () => response.write('muahaha'),
+ {
+ code: 'ERR_HTTP2_INVALID_STREAM'
+ }
+ );
server.close();
}));
}));
@@ -84,20 +73,20 @@ const expectedError = expectsError({
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();
+ const request = client.request();
request.resume();
+ request.on('end', mustCall());
+ request.on('close', mustCall(() => client.close()));
}));
server.once('request', mustCall((request, response) => {
response.stream.session.on('close', mustCall(() => {
- response.write('muahaha', 'utf8', mustCall(expectedError));
+ expectsError(
+ () => response.write('muahaha', 'utf8'),
+ {
+ code: 'ERR_HTTP2_INVALID_STREAM'
+ }
+ );
server.close();
}));
client.destroy();
diff --git a/test/parallel/test-http2-compat-serverresponse-writehead.js b/test/parallel/test-http2-compat-serverresponse-writehead.js
index 704f199ca27e99..5fd787e100350c 100644
--- a/test/parallel/test-http2-compat-serverresponse-writehead.js
+++ b/test/parallel/test-http2-compat-serverresponse-writehead.js
@@ -23,7 +23,7 @@ server.listen(0, common.mustCall(function() {
server.close();
process.nextTick(common.mustCall(() => {
common.expectsError(() => { response.writeHead(300); }, {
- code: 'ERR_HTTP2_STREAM_CLOSED'
+ code: 'ERR_HTTP2_INVALID_STREAM'
});
}));
}));
@@ -44,7 +44,7 @@ server.listen(0, common.mustCall(function() {
assert.strictEqual(headers[':status'], 418);
}, 1));
request.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-compat-socket-set.js b/test/parallel/test-http2-compat-socket-set.js
index ff53d998f4d207..05beb09d548e91 100644
--- a/test/parallel/test-http2-compat-socket-set.js
+++ b/test/parallel/test-http2-compat-socket-set.js
@@ -98,7 +98,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
request.end();
diff --git a/test/parallel/test-http2-compat-socket.js b/test/parallel/test-http2-compat-socket.js
index d30b2f2a11613b..80c8b1d30d10b3 100644
--- a/test/parallel/test-http2-compat-socket.js
+++ b/test/parallel/test-http2-compat-socket.js
@@ -81,7 +81,7 @@ server.listen(0, common.mustCall(function() {
};
const request = client.request(headers);
request.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
}));
request.end();
request.resume();
diff --git a/test/parallel/test-http2-connect-method.js b/test/parallel/test-http2-connect-method.js
index 4d443d5c217421..0ddbc60433cf85 100644
--- a/test/parallel/test-http2-connect-method.js
+++ b/test/parallel/test-http2-connect-method.js
@@ -13,7 +13,8 @@ const {
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
- NGHTTP2_CONNECT_ERROR
+ NGHTTP2_CONNECT_ERROR,
+ NGHTTP2_REFUSED_STREAM
} = http2.constants;
const server = net.createServer(common.mustCall((socket) => {
@@ -34,7 +35,7 @@ server.listen(0, common.mustCall(() => {
const proxy = http2.createServer();
proxy.on('stream', common.mustCall((stream, headers) => {
if (headers[HTTP2_HEADER_METHOD] !== 'CONNECT') {
- stream.rstWithRefused();
+ stream.close(NGHTTP2_REFUSED_STREAM);
return;
}
const auth = new URL(`tcp://${headers[HTTP2_HEADER_AUTHORITY]}`);
@@ -47,7 +48,7 @@ server.listen(0, common.mustCall(() => {
});
socket.on('close', common.mustCall());
socket.on('error', (error) => {
- stream.rstStream(NGHTTP2_CONNECT_ERROR);
+ stream.close(NGHTTP2_CONNECT_ERROR);
});
}));
@@ -99,7 +100,7 @@ server.listen(0, common.mustCall(() => {
req.on('data', (chunk) => data += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'hello');
- client.destroy();
+ client.close();
proxy.close();
server.close();
}));
diff --git a/test/parallel/test-http2-connect.js b/test/parallel/test-http2-connect.js
index e5a4e429907090..894c51fe3d9330 100644
--- a/test/parallel/test-http2-connect.js
+++ b/test/parallel/test-http2-connect.js
@@ -20,7 +20,7 @@ const { createServer, connect } = require('http2');
for (const client of clients) {
client.once('connect', mustCall((headers) => {
- client.destroy();
+ client.close();
clients.delete(client);
if (clients.size === 0) {
server.close();
@@ -33,7 +33,11 @@ const { createServer, connect } = require('http2');
// check for https as protocol
{
const authority = 'https://localhost';
- doesNotThrow(() => connect(authority));
+ doesNotThrow(() => {
+ // A socket error may or may not be reported, keep this as a non-op
+ // instead of a mustCall or mustNotCall
+ connect(authority).on('error', () => {});
+ });
}
// check for error for an invalid protocol (not http or https)
diff --git a/test/parallel/test-http2-cookies.js b/test/parallel/test-http2-cookies.js
index 48b08b6367b4b3..cf763915389287 100644
--- a/test/parallel/test-http2-cookies.js
+++ b/test/parallel/test-http2-cookies.js
@@ -54,7 +54,7 @@ server.on('listening', common.mustCall(() => {
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
diff --git a/test/parallel/test-http2-create-client-connect.js b/test/parallel/test-http2-create-client-connect.js
index cd7d8b4fc8c3f9..fa9711fe1b28a7 100644
--- a/test/parallel/test-http2-create-client-connect.js
+++ b/test/parallel/test-http2-create-client-connect.js
@@ -30,7 +30,7 @@ const URL = url.URL;
() => setImmediate(() => server.close()));
const maybeClose = common.mustCall((client) => {
- client.destroy();
+ client.close();
serverClose.dec();
}, items.length);
@@ -42,7 +42,7 @@ const URL = url.URL;
// Will fail because protocol does not match the server.
h2.connect({ port: port, protocol: 'https:' })
- .on('socketError', common.mustCall(() => serverClose.dec()));
+ .on('error', common.mustCall(() => serverClose.dec()));
}));
}
@@ -55,10 +55,8 @@ const URL = url.URL;
};
const server = h2.createSecureServer(options);
- server.listen(0);
-
- server.on('listening', common.mustCall(function() {
- const port = this.address().port;
+ server.listen(0, common.mustCall(() => {
+ const port = server.address().port;
const opts = { rejectUnauthorized: false };
@@ -74,7 +72,7 @@ const URL = url.URL;
() => setImmediate(() => server.close()));
const maybeClose = common.mustCall((client) => {
- client.destroy();
+ client.close();
serverClose.dec();
}, items.length);
diff --git a/test/parallel/test-http2-create-client-secure-session.js b/test/parallel/test-http2-create-client-secure-session.js
index 811ef772d5903a..6120a58602065d 100644
--- a/test/parallel/test-http2-create-client-secure-session.js
+++ b/test/parallel/test-http2-create-client-secure-session.js
@@ -19,6 +19,14 @@ function loadKey(keyname) {
function onStream(stream, headers) {
const socket = stream.session[kSocket];
+
+ assert(stream.session.encrypted);
+ assert(stream.session.alpnProtocol, 'h2');
+ const originSet = stream.session.originSet;
+ assert(Array.isArray(originSet));
+ assert.strictEqual(originSet[0],
+ `https://${socket.servername}:${socket.remotePort}`);
+
assert(headers[':authority'].startsWith(socket.servername));
stream.respond({ 'content-type': 'application/json' });
stream.end(JSON.stringify({
@@ -39,6 +47,17 @@ function verifySecureSession(key, cert, ca, opts) {
assert.strictEqual(client.socket.listenerCount('secureConnect'), 1);
const req = client.request();
+ client.on('connect', common.mustCall(() => {
+ assert(client.encrypted);
+ assert.strictEqual(client.alpnProtocol, 'h2');
+ const originSet = client.originSet;
+ assert(Array.isArray(originSet));
+ assert.strictEqual(originSet.length, 1);
+ assert.strictEqual(
+ originSet[0],
+ `https://${opts.servername || 'localhost'}:${server.address().port}`);
+ }));
+
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['content-type'], 'application/json');
diff --git a/test/parallel/test-http2-create-client-session.js b/test/parallel/test-http2-create-client-session.js
index 149b5164231a21..b5be6bc8581452 100644
--- a/test/parallel/test-http2-create-client-session.js
+++ b/test/parallel/test-http2-create-client-session.js
@@ -5,6 +5,8 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
+const Countdown = require('../common/countdown');
+
const body =
'
this is some data';
@@ -23,21 +25,32 @@ function onStream(stream, headers, flags) {
'content-type': 'text/html',
':status': 200
});
- stream.end(body);
+ stream.write(body.slice(0, 20));
+ stream.end(body.slice(20));
}
server.listen(0);
-let expected = count;
+server.on('listening', common.mustCall(() => {
+
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+ client.setMaxListeners(101);
-server.on('listening', common.mustCall(function() {
+ client.on('goaway', console.log);
- const client = h2.connect(`http://localhost:${this.address().port}`);
+ client.on('connect', common.mustCall(() => {
+ assert(!client.encrypted);
+ assert(!client.originSet);
+ assert.strictEqual(client.alpnProtocol, 'h2c');
+ }));
- const headers = { ':path': '/' };
+ const countdown = new Countdown(count, () => {
+ client.close();
+ server.close();
+ });
for (let n = 0; n < count; n++) {
- const req = client.request(headers);
+ const req = client.request();
req.on('response', common.mustCall(function(headers) {
assert.strictEqual(headers[':status'], 200, 'status code is set');
@@ -51,12 +64,7 @@ server.on('listening', common.mustCall(function() {
req.on('data', (d) => data += d);
req.on('end', common.mustCall(() => {
assert.strictEqual(body, data);
- if (--expected === 0) {
- server.close();
- client.destroy();
- }
}));
- req.end();
+ req.on('close', common.mustCall(() => countdown.dec()));
}
-
}));
diff --git a/test/parallel/test-http2-createsecureserver-nooptions.js b/test/parallel/test-http2-createsecureserver-nooptions.js
index 8ff7f22e3ffcf6..71764f5783e404 100644
--- a/test/parallel/test-http2-createsecureserver-nooptions.js
+++ b/test/parallel/test-http2-createsecureserver-nooptions.js
@@ -6,7 +6,7 @@ if (!common.hasCrypto)
const http2 = require('http2');
-const invalidOptions = [() => {}, 1, 'test', null, undefined];
+const invalidOptions = [() => {}, 1, 'test', null];
const invalidArgTypeError = {
type: TypeError,
code: 'ERR_INVALID_ARG_TYPE',
@@ -14,9 +14,9 @@ const invalidArgTypeError = {
};
// Error if options are not passed to createSecureServer
-invalidOptions.forEach((invalidOption) =>
+invalidOptions.forEach((invalidOption) => {
common.expectsError(
() => http2.createSecureServer(invalidOption),
invalidArgTypeError
- )
-);
+ );
+});
diff --git a/test/parallel/test-http2-createwritereq.js b/test/parallel/test-http2-createwritereq.js
index ca394a5d425470..1d2b31676284d0 100644
--- a/test/parallel/test-http2-createwritereq.js
+++ b/test/parallel/test-http2-createwritereq.js
@@ -1,5 +1,7 @@
'use strict';
+// Flags: --expose-gc
+
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
@@ -54,7 +56,7 @@ server.listen(0, common.mustCall(function() {
req.resume();
req.on('end', common.mustCall(function() {
- client.destroy();
+ client.close();
testsFinished++;
if (testsFinished === testsToRun) {
@@ -62,6 +64,15 @@ server.listen(0, common.mustCall(function() {
}
}));
+ // Ref: https://github.com/nodejs/node/issues/17840
+ const origDestroy = req.destroy;
+ req.destroy = function(...args) {
+ // Schedule a garbage collection event at the end of the current
+ // MakeCallback() run.
+ process.nextTick(global.gc);
+ return origDestroy.call(this, ...args);
+ };
+
req.end();
});
}));
diff --git a/test/parallel/test-http2-date-header.js b/test/parallel/test-http2-date-header.js
index ab0654e64cbcd7..2b63e1b7899a2e 100644
--- a/test/parallel/test-http2-date-header.js
+++ b/test/parallel/test-http2-date-header.js
@@ -24,6 +24,6 @@ server.listen(0, common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
}));
diff --git a/test/parallel/test-http2-dont-lose-data.js b/test/parallel/test-http2-dont-lose-data.js
new file mode 100644
index 00000000000000..eb85277b7b124c
--- /dev/null
+++ b/test/parallel/test-http2-dont-lose-data.js
@@ -0,0 +1,58 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const assert = require('assert');
+const http2 = require('http2');
+
+const server = http2.createServer();
+
+server.on('stream', (s) => {
+ assert(s.pushAllowed);
+
+ s.pushStream({ ':path': '/file' }, common.mustCall((err, pushStream) => {
+ assert.ifError(err);
+ pushStream.respond();
+ pushStream.end('a push stream');
+ }));
+
+ s.respond();
+ s.end('hello world');
+});
+
+server.listen(0, () => {
+ server.unref();
+
+ const url = `http://localhost:${server.address().port}`;
+
+ const client = http2.connect(url);
+ const req = client.request();
+
+ let pushStream;
+
+ client.on('stream', common.mustCall((s, headers) => {
+ assert.strictEqual(headers[':path'], '/file');
+ pushStream = s;
+ }));
+
+ req.on('response', common.mustCall((headers) => {
+ let pushData = '';
+ pushStream.setEncoding('utf8');
+ pushStream.on('data', (d) => pushData += d);
+ pushStream.on('end', common.mustCall(() => {
+ assert.strictEqual(pushData, 'a push stream');
+
+ // removing the setImmediate causes the test to pass
+ setImmediate(function() {
+ let data = '';
+ req.setEncoding('utf8');
+ req.on('data', (d) => data += d);
+ req.on('end', common.mustCall(() => {
+ assert.strictEqual(data, 'hello world');
+ client.close();
+ }));
+ });
+ }));
+ }));
+});
diff --git a/test/parallel/test-http2-dont-override.js b/test/parallel/test-http2-dont-override.js
index cc60a2fe802a82..b45713deb3ca60 100644
--- a/test/parallel/test-http2-dont-override.js
+++ b/test/parallel/test-http2-dont-override.js
@@ -44,6 +44,6 @@ server.listen(0, common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
}));
diff --git a/test/parallel/test-http2-generic-streams-sendfile.js b/test/parallel/test-http2-generic-streams-sendfile.js
index 1054574a8b1ca2..b752b0fdcb815a 100644
--- a/test/parallel/test-http2-generic-streams-sendfile.js
+++ b/test/parallel/test-http2-generic-streams-sendfile.js
@@ -20,7 +20,7 @@ const makeDuplexPair = require('../common/duplexpair');
createConnection: common.mustCall(() => clientSide)
});
- const req = client.request({ ':path': '/' });
+ const req = client.request();
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
@@ -28,9 +28,7 @@ const makeDuplexPair = require('../common/duplexpair');
req.setEncoding('utf8');
let data = '';
- req.on('data', (chunk) => {
- data += chunk;
- });
+ req.on('data', (chunk) => data += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(data, fs.readFileSync(__filename, 'utf8'));
clientSide.destroy();
diff --git a/test/parallel/test-http2-goaway-opaquedata.js b/test/parallel/test-http2-goaway-opaquedata.js
index d8895a82edf464..3f1fb4d7954414 100644
--- a/test/parallel/test-http2-goaway-opaquedata.js
+++ b/test/parallel/test-http2-goaway-opaquedata.js
@@ -10,32 +10,23 @@ 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.session.goaway(0, 0, data);
+ stream.respond();
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);
+ client.once('goaway', common.mustCall((code, lastStreamID, buf) => {
+ assert.deepStrictEqual(code, 0);
+ assert.deepStrictEqual(lastStreamID, 1);
assert.deepStrictEqual(data, buf);
- // Call shutdown() here so that emitGoaway calls destroy()
- client.shutdown();
server.close();
}));
- const req = client.request({ ':path': '/' });
+ const req = client.request();
req.resume();
req.on('end', common.mustCall());
+ req.on('close', common.mustCall());
req.end();
-
});
diff --git a/test/parallel/test-http2-head-request.js b/test/parallel/test-http2-head-request.js
index d15665624ac192..a56abf3c9006f3 100644
--- a/test/parallel/test-http2-head-request.js
+++ b/test/parallel/test-http2-head-request.js
@@ -54,6 +54,6 @@ server.listen(0, () => {
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
});
diff --git a/test/parallel/test-http2-https-fallback.js b/test/parallel/test-http2-https-fallback.js
index 04e9ca480c9099..01b694e586dd49 100644
--- a/test/parallel/test-http2-https-fallback.js
+++ b/test/parallel/test-http2-https-fallback.js
@@ -52,7 +52,7 @@ function onSession(session) {
strictEqual(alpnProtocol, 'h2');
strictEqual(httpVersion, '2.0');
- session.destroy();
+ session.close();
this.cleanup();
}));
request.end();
diff --git a/test/parallel/test-http2-info-headers-errors.js b/test/parallel/test-http2-info-headers-errors.js
index b671bece4f76fc..1df76334558529 100644
--- a/test/parallel/test-http2-info-headers-errors.js
+++ b/test/parallel/test-http2-info-headers-errors.js
@@ -48,10 +48,6 @@ server.on('stream', common.mustCall((stream, headers) => {
if (currentError.type === 'stream') {
stream.session.on('error', errorMustNotCall);
stream.on('error', errorMustCall);
- stream.on('error', common.mustCall(() => {
- stream.respond();
- stream.end();
- }));
} else {
stream.session.once('error', errorMustCall);
stream.on('error', errorMustNotCall);
@@ -63,24 +59,21 @@ server.on('stream', common.mustCall((stream, headers) => {
server.listen(0, common.mustCall(() => runTest(tests.shift())));
function runTest(test) {
- const port = server.address().port;
- const url = `http://localhost:${port}`;
- const headers = {
- ':path': '/',
- ':method': 'POST',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- };
-
- const client = http2.connect(url);
- const req = client.request(headers);
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+ const req = client.request({ ':method': 'POST' });
currentError = test;
req.resume();
req.end();
- req.on('end', common.mustCall(() => {
- client.destroy();
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 2'
+ }));
+
+ req.on('close', common.mustCall(() => {
+ client.close();
if (!tests.length) {
server.close();
diff --git a/test/parallel/test-http2-info-headers.js b/test/parallel/test-http2-info-headers.js
index 332c688429c37e..2313040de12a97 100644
--- a/test/parallel/test-http2-info-headers.js
+++ b/test/parallel/test-http2-info-headers.js
@@ -88,7 +88,7 @@ server.on('listening', common.mustCall(() => {
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
diff --git a/test/parallel/test-http2-invalidargtypes-errors.js b/test/parallel/test-http2-invalidargtypes-errors.js
index 3471e46fdf4ca4..ff189a2977559f 100644
--- a/test/parallel/test-http2-invalidargtypes-errors.js
+++ b/test/parallel/test-http2-invalidargtypes-errors.js
@@ -7,29 +7,25 @@ const http2 = require('http2');
const server = http2.createServer();
-server.on(
- 'stream',
- common.mustCall((stream) => {
- const invalidArgTypeError = (param, type) => ({
- type: TypeError,
+server.on('stream', common.mustCall((stream) => {
+ common.expectsError(
+ () => stream.close('string'),
+ {
code: 'ERR_INVALID_ARG_TYPE',
- message: `The "${param}" argument must be of type ${type}`
- });
- common.expectsError(
- () => stream.rstStream('string'),
- invalidArgTypeError('code', 'number')
- );
- stream.session.destroy();
- })
-);
+ type: TypeError,
+ message: 'The "code" argument must be of type number'
+ }
+ );
+ stream.respond();
+ stream.end('ok');
+}));
-server.listen(
- 0,
- common.mustCall(() => {
- const client = http2.connect(`http://localhost:${server.address().port}`);
- const req = client.request();
- req.resume();
- req.on('end', common.mustCall(() => server.close()));
- req.end();
- })
-);
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+ const req = client.request();
+ req.resume();
+ req.on('close', common.mustCall(() => {
+ server.close();
+ client.close();
+ }));
+}));
diff --git a/test/parallel/test-http2-max-concurrent-streams.js b/test/parallel/test-http2-max-concurrent-streams.js
index a65ac90c535b03..ffc04e98f134b2 100644
--- a/test/parallel/test-http2-max-concurrent-streams.js
+++ b/test/parallel/test-http2-max-concurrent-streams.js
@@ -5,64 +5,52 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
-
-const {
- HTTP2_HEADER_METHOD,
- HTTP2_HEADER_STATUS,
- HTTP2_HEADER_PATH,
- HTTP2_METHOD_POST
-} = h2.constants;
+const Countdown = require('../common/countdown');
// 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.respond();
stream.end('hello world');
}));
-server.listen(0);
-
-server.on('listening', common.mustCall(() => {
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
- let reqs = 2;
- function onEnd() {
- if (--reqs === 0) {
- server.close();
- client.destroy();
- }
- }
+ const countdown = new Countdown(2, () => {
+ server.close();
+ client.close();
+ });
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'
- })));
+ {
+ const req = client.request({ ':method': 'POST' });
+ req.on('aborted', common.mustNotCall());
+ req.on('response', common.mustCall());
+ req.resume();
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => countdown.dec()));
+ req.end();
+ }
+ {
+ // This one should be aborted
+ const req = client.request({ ':method': 'POST' });
+ req.on('aborted', common.mustCall());
+ req.on('response', common.mustNotCall());
+ req.resume();
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => countdown.dec()));
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 7'
+ }));
+ }
}));
diff --git a/test/parallel/test-http2-methods.js b/test/parallel/test-http2-methods.js
index 36f64f13abcf86..a291bdf00800d5 100644
--- a/test/parallel/test-http2-methods.js
+++ b/test/parallel/test-http2-methods.js
@@ -41,7 +41,7 @@ server.on('listening', common.mustCall(() => {
req.on('end', common.mustCall(() => {
if (--expected === 0) {
server.close();
- client.destroy();
+ client.close();
}
}));
req.end();
diff --git a/test/parallel/test-http2-misbehaving-flow-control-paused.js b/test/parallel/test-http2-misbehaving-flow-control-paused.js
index ee799b1d5a27d3..0b7299d5ac80a8 100644
--- a/test/parallel/test-http2-misbehaving-flow-control-paused.js
+++ b/test/parallel/test-http2-misbehaving-flow-control-paused.js
@@ -56,32 +56,24 @@ let client;
const server = h2.createServer({ settings: { initialWindowSize: 36 } });
server.on('stream', (stream) => {
-
- // Not reading causes the flow control window to get backed up.
stream.pause();
-
- stream.on('error', common.mustCall((err) => {
- common.expectsError({
- code: 'ERR_HTTP2_STREAM_ERROR',
- type: Error,
- message: 'Stream closed with error code 3'
- })(err);
+ stream.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 3'
+ }));
+ stream.on('close', common.mustCall(() => {
server.close();
client.destroy();
}));
-
stream.on('end', common.mustNotCall());
-
stream.respond();
stream.end('ok');
});
server.listen(0, () => {
client = net.connect(server.address().port, () => {
- client.on('error', console.log);
-
client.write(preamble);
-
client.write(data);
client.write(data);
client.write(data);
diff --git a/test/parallel/test-http2-misbehaving-flow-control.js b/test/parallel/test-http2-misbehaving-flow-control.js
index 010e07741316b6..8a0b411b8de65c 100644
--- a/test/parallel/test-http2-misbehaving-flow-control.js
+++ b/test/parallel/test-http2-misbehaving-flow-control.js
@@ -29,6 +29,21 @@ const preamble = Buffer.from([
]);
const data = Buffer.from([
+ 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c,
+ 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a,
+ 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c,
+ 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a,
+ 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c,
+ 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a,
+ 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c,
+ 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a,
+ 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c,
+ 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a,
0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c,
0x6c, 0x6f, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a
@@ -51,30 +66,23 @@ const data = Buffer.from([
let client;
const server = h2.createServer({ settings: { initialWindowSize: 18 } });
server.on('stream', (stream) => {
-
- stream.resume();
-
- stream.on('error', common.mustCall((err) => {
- common.expectsError({
- code: 'ERR_HTTP2_STREAM_ERROR',
- type: Error,
- message: 'Stream closed with error code 3'
- })(err);
+ stream.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 3'
+ }));
+ stream.on('close', common.mustCall(() => {
server.close();
client.destroy();
}));
-
+ stream.resume();
stream.respond();
stream.end('ok');
});
server.listen(0, () => {
client = net.connect(server.address().port, () => {
- client.on('error', console.log);
-
client.write(preamble);
-
- client.write(data);
client.write(data);
client.write(data);
});
diff --git a/test/parallel/test-http2-misbehaving-multiplex.js b/test/parallel/test-http2-misbehaving-multiplex.js
new file mode 100644
index 00000000000000..7d5a7a2f552d49
--- /dev/null
+++ b/test/parallel/test-http2-misbehaving-multiplex.js
@@ -0,0 +1,59 @@
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const h2 = require('http2');
+const net = require('net');
+const h2test = require('../common/http2');
+let client;
+
+const server = h2.createServer();
+server.on('stream', common.mustCall((stream) => {
+ stream.respond();
+ stream.end('ok');
+}, 2));
+server.on('session', common.mustCall((session) => {
+ session.on('error', common.expectsError({
+ code: 'ERR_HTTP2_ERROR',
+ type: Error,
+ message: 'Stream was already closed or invalid'
+ }));
+}));
+
+const settings = new h2test.SettingsFrame();
+const settingsAck = new h2test.SettingsFrame(true);
+const head1 = new h2test.HeadersFrame(1, h2test.kFakeRequestHeaders, 0, true);
+const head2 = new h2test.HeadersFrame(3, h2test.kFakeRequestHeaders, 0, true);
+const head3 = new h2test.HeadersFrame(1, h2test.kFakeRequestHeaders, 0, true);
+const head4 = new h2test.HeadersFrame(5, h2test.kFakeRequestHeaders, 0, true);
+
+server.listen(0, () => {
+ client = net.connect(server.address().port, () => {
+ client.write(h2test.kClientMagic, () => {
+ client.write(settings.data, () => {
+ client.write(settingsAck.data);
+ // This will make it ok.
+ client.write(head1.data, () => {
+ // This will make it ok.
+ client.write(head2.data, () => {
+ // This will cause an error to occur because the client is
+ // attempting to reuse an already closed stream. This must
+ // cause the server session to be torn down.
+ client.write(head3.data, () => {
+ // This won't ever make it to the server
+ client.write(head4.data);
+ });
+ });
+ });
+ });
+ });
+ });
+
+ // An error may or may not be emitted on the client side, we don't care
+ // either way if it is, but we don't want to die if it is.
+ client.on('error', () => {});
+ client.on('close', common.mustCall(() => server.close()));
+});
diff --git a/test/parallel/test-http2-misused-pseudoheaders.js b/test/parallel/test-http2-misused-pseudoheaders.js
index 1f501598c1c421..b47462b14fd0e1 100644
--- a/test/parallel/test-http2-misused-pseudoheaders.js
+++ b/test/parallel/test-http2-misused-pseudoheaders.js
@@ -7,11 +7,7 @@ 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) {
-
+server.on('stream', common.mustCall((stream) => {
[
':path',
':authority',
@@ -24,10 +20,7 @@ function onStream(stream, headers, flags) {
});
});
- stream.respond({
- 'content-type': 'text/html',
- ':status': 200
- }, {
+ stream.respond({}, {
getTrailers: common.mustCall((trailers) => {
trailers[':status'] = 'bar';
})
@@ -38,22 +31,24 @@ function onStream(stream, headers, flags) {
}));
stream.end('hello world');
-}
-
-server.listen(0);
+}));
-server.on('listening', common.mustCall(() => {
+server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
+ const req = client.request();
- const req = client.request({ ':path': '/' });
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 2'
+ }));
req.on('response', common.mustCall());
req.resume();
- req.on('end', common.mustCall(() => {
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
- req.end();
-
}));
diff --git a/test/parallel/test-http2-multi-content-length.js b/test/parallel/test-http2-multi-content-length.js
index d0f0094d2408aa..4d18356f127da0 100644
--- a/test/parallel/test-http2-multi-content-length.js
+++ b/test/parallel/test-http2-multi-content-length.js
@@ -4,6 +4,7 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');
+const Countdown = require('../common/countdown');
const server = http2.createServer();
@@ -15,29 +16,25 @@ server.on('stream', common.mustCall((stream) => {
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();
- }
- }
+ const countdown = new Countdown(2, () => {
+ server.close();
+ client.close();
+ });
- {
- // 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({
+ // Request 1 will fail because there are two content-length header values
+ common.expectsError(
+ () => {
+ client.request({
+ ':method': 'POST',
+ 'content-length': 1,
+ 'Content-Length': 2
+ });
+ }, {
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
@@ -46,7 +43,8 @@ server.listen(0, common.mustCall(() => {
'content-length': 1
});
req.resume();
- req.on('end', common.mustCall(maybeClose));
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => countdown.dec()));
req.end('a');
}
@@ -55,7 +53,8 @@ server.listen(0, common.mustCall(() => {
// header to be set for non-payload bearing requests...
const req = client.request({ 'content-length': 1 });
req.resume();
- req.on('end', common.mustCall(maybeClose));
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => countdown.dec()));
req.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
diff --git a/test/parallel/test-http2-multiheaders-raw.js b/test/parallel/test-http2-multiheaders-raw.js
index c06bf23bff3071..50486450d5aeb7 100644
--- a/test/parallel/test-http2-multiheaders-raw.js
+++ b/test/parallel/test-http2-multiheaders-raw.js
@@ -44,6 +44,6 @@ server.listen(0, common.mustCall(() => {
const req = client.request(src);
req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
}));
diff --git a/test/parallel/test-http2-multiheaders.js b/test/parallel/test-http2-multiheaders.js
index 5e477104091cb1..9bf8f76d22e60e 100644
--- a/test/parallel/test-http2-multiheaders.js
+++ b/test/parallel/test-http2-multiheaders.js
@@ -56,6 +56,6 @@ server.listen(0, common.mustCall(() => {
req.on('response', common.mustCall(checkHeaders));
req.on('close', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
}));
diff --git a/test/parallel/test-http2-multiplex.js b/test/parallel/test-http2-multiplex.js
index c818a28572eca7..1778bced5f92f4 100644
--- a/test/parallel/test-http2-multiplex.js
+++ b/test/parallel/test-http2-multiplex.js
@@ -8,6 +8,7 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
+const Countdown = require('../common/countdown');
const server = http2.createServer();
@@ -20,15 +21,12 @@ server.on('stream', common.mustCall((stream) => {
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
+ client.setMaxListeners(100);
- let remaining = count;
-
- function maybeClose() {
- if (--remaining === 0) {
- server.close();
- client.destroy();
- }
- }
+ const countdown = new Countdown(count, () => {
+ server.close();
+ client.close();
+ });
function doRequest() {
const req = client.request({ ':method': 'POST ' });
@@ -38,8 +36,8 @@ server.listen(0, common.mustCall(() => {
req.on('data', (chunk) => data += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'abcdefghij');
- maybeClose();
}));
+ req.on('close', common.mustCall(() => countdown.dec()));
let n = 0;
function writeChunk() {
diff --git a/test/parallel/test-http2-no-more-streams.js b/test/parallel/test-http2-no-more-streams.js
index 6f4169756c0b4a..dd06a709f23023 100644
--- a/test/parallel/test-http2-no-more-streams.js
+++ b/test/parallel/test-http2-no-more-streams.js
@@ -25,7 +25,7 @@ server.listen(0, common.mustCall(() => {
const countdown = new Countdown(2, common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
{
diff --git a/test/parallel/test-http2-options-max-headers-block-length.js b/test/parallel/test-http2-options-max-headers-block-length.js
index 402803dd33a73a..a728c28c6576d4 100644
--- a/test/parallel/test-http2-options-max-headers-block-length.js
+++ b/test/parallel/test-http2-options-max-headers-block-length.js
@@ -10,9 +10,7 @@ const server = h2.createServer();
// we use the lower-level API here
server.on('stream', common.mustNotCall());
-server.listen(0);
-
-server.on('listening', common.mustCall(() => {
+server.listen(0, common.mustCall(() => {
// Setting the maxSendHeaderBlockLength, then attempting to send a
// headers block that is too big should cause a 'frameError' to
@@ -24,13 +22,13 @@ server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`,
options);
- const req = client.request({ ':path': '/' });
-
+ const req = client.request();
req.on('response', common.mustNotCall());
req.resume();
- req.on('end', common.mustCall(() => {
- client.destroy();
+ req.on('close', common.mustCall(() => {
+ client.close();
+ server.close();
}));
req.on('frameError', common.mustCall((type, code) => {
@@ -42,33 +40,4 @@ server.on('listening', common.mustCall(() => {
type: Error,
message: 'Stream closed with error code 7'
}));
-
- req.end();
-
- // if no frameError listener, should emit 'error' with
- // code ERR_HTTP2_FRAME_ERROR
- const req2 = client.request({ ':path': '/' });
-
- req2.on('response', common.mustNotCall());
-
- req2.resume();
- req2.on('end', common.mustCall(() => {
- server.close();
- client.destroy();
- }));
-
- req2.once('error', common.mustCall((err) => {
- common.expectsError({
- code: 'ERR_HTTP2_FRAME_ERROR',
- type: Error
- })(err);
- req2.on('error', common.expectsError({
- code: 'ERR_HTTP2_STREAM_ERROR',
- type: Error,
- message: 'Stream closed with error code 7'
- }));
- }));
-
- req2.end();
-
}));
diff --git a/test/parallel/test-http2-options-max-reserved-streams.js b/test/parallel/test-http2-options-max-reserved-streams.js
index d54ca6a7886b3c..994a8817451686 100644
--- a/test/parallel/test-http2-options-max-reserved-streams.js
+++ b/test/parallel/test-http2-options-max-reserved-streams.js
@@ -5,20 +5,24 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
+const Countdown = require('../common/countdown');
const server = h2.createServer();
+let client;
+
+const countdown = new Countdown(3, () => {
+ server.close();
+ client.close();
+});
// 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 });
+ }, common.mustCall((err, pushedStream) => {
+ assert.ifError(err);
+ pushedStream.respond();
pushedStream.end();
pushedStream.on('aborted', common.mustNotCall());
}));
@@ -27,52 +31,41 @@ server.on('stream', common.mustCall((stream) => {
// 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 });
+ }, common.mustCall((err, pushedStream) => {
+ assert.ifError(err);
+ pushedStream.respond();
pushedStream.on('aborted', common.mustCall());
pushedStream.on('error', common.mustNotCall());
- pushedStream.on('close',
- common.mustCall((code) => assert.strictEqual(code, 8)));
+ pushedStream.on('close', common.mustCall((code) => {
+ assert.strictEqual(code, 8);
+ countdown.dec();
+ }));
}));
+ stream.respond();
stream.end('hello world');
}));
server.listen(0);
server.on('listening', common.mustCall(() => {
+ client = h2.connect(`http://localhost:${server.address().port}`,
+ { maxReservedRemoteStreams: 1 });
- const options = {
- maxReservedRemoteStreams: 1
- };
-
- const client = h2.connect(`http://localhost:${server.address().port}`,
- options);
-
- let remaining = 2;
- function maybeClose() {
- if (--remaining === 0) {
- server.close();
- client.destroy();
- }
- }
-
- const req = client.request({ ':path': '/' });
+ const req = client.request();
// 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('push', common.mustCall());
stream.on('end', common.mustCall());
- stream.on('close', common.mustCall(maybeClose));
+ stream.on('close', common.mustCall(() => countdown.dec()));
}));
req.on('response', common.mustCall());
-
req.resume();
req.on('end', common.mustCall());
- req.on('close', common.mustCall(maybeClose));
+ req.on('close', common.mustCall(() => countdown.dec()));
}));
diff --git a/test/parallel/test-http2-padding-aligned.js b/test/parallel/test-http2-padding-aligned.js
new file mode 100644
index 00000000000000..183eaef7389360
--- /dev/null
+++ b/test/parallel/test-http2-padding-aligned.js
@@ -0,0 +1,68 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const assert = require('assert');
+const http2 = require('http2');
+const { PADDING_STRATEGY_ALIGNED } = http2.constants;
+const makeDuplexPair = require('../common/duplexpair');
+
+{
+ const testData = 'Hello World.
';
+ const server = http2.createServer({
+ paddingStrategy: PADDING_STRATEGY_ALIGNED
+ });
+ server.on('stream', common.mustCall((stream, headers) => {
+ stream.respond({
+ 'content-type': 'text/html',
+ ':status': 200
+ });
+ stream.end(testData);
+ }));
+
+ const { clientSide, serverSide } = makeDuplexPair();
+
+ // The lengths of the expected writes... note that this is highly
+ // sensitive to how the internals are implemented.
+ const serverLengths = [24, 9, 9, 32];
+ const clientLengths = [9, 9, 48, 9, 1, 21, 1, 16];
+
+ // Adjust for the 24-byte preamble and two 9-byte settings frames, and
+ // the result must be equally divisible by 8
+ assert.strictEqual(
+ (serverLengths.reduce((i, n) => i + n) - 24 - 9 - 9) % 8, 0);
+
+ // Adjust for two 9-byte settings frames, and the result must be equally
+ // divisible by 8
+ assert.strictEqual(
+ (clientLengths.reduce((i, n) => i + n) - 9 - 9) % 8, 0);
+
+ serverSide.on('data', common.mustCall((chunk) => {
+ assert.strictEqual(chunk.length, serverLengths.shift());
+ }, serverLengths.length));
+ clientSide.on('data', common.mustCall((chunk) => {
+ assert.strictEqual(chunk.length, clientLengths.shift());
+ }, clientLengths.length));
+
+ server.emit('connection', serverSide);
+
+ const client = http2.connect('http://localhost:80', {
+ paddingStrategy: PADDING_STRATEGY_ALIGNED,
+ createConnection: common.mustCall(() => clientSide)
+ });
+
+ const req = client.request({ ':path': '/a' });
+
+ req.on('response', common.mustCall());
+
+ req.setEncoding('utf8');
+ req.on('data', common.mustCall((data) => {
+ assert.strictEqual(data, testData);
+ }));
+ req.on('close', common.mustCall(() => {
+ clientSide.destroy();
+ clientSide.end();
+ }));
+ req.end();
+}
diff --git a/test/parallel/test-http2-padding-callback.js b/test/parallel/test-http2-padding-callback.js
index af547ad498da1b..6d6a6b27221b07 100644
--- a/test/parallel/test-http2-padding-callback.js
+++ b/test/parallel/test-http2-padding-callback.js
@@ -45,7 +45,7 @@ server.on('listening', common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
}));
diff --git a/test/parallel/test-http2-perf_hooks.js b/test/parallel/test-http2-perf_hooks.js
new file mode 100644
index 00000000000000..f2ef29cec25e06
--- /dev/null
+++ b/test/parallel/test-http2-perf_hooks.js
@@ -0,0 +1,95 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const assert = require('assert');
+const h2 = require('http2');
+
+const { PerformanceObserver } = require('perf_hooks');
+
+const obs = new PerformanceObserver((items) => {
+ const entry = items.getEntries()[0];
+ assert.strictEqual(entry.entryType, 'http2');
+ assert.strictEqual(typeof entry.startTime, 'number');
+ assert.strictEqual(typeof entry.duration, 'number');
+ switch (entry.name) {
+ case 'Http2Session':
+ assert.strictEqual(typeof entry.pingRTT, 'number');
+ assert.strictEqual(typeof entry.streamAverageDuration, 'number');
+ assert.strictEqual(typeof entry.streamCount, 'number');
+ assert.strictEqual(typeof entry.framesReceived, 'number');
+ switch (entry.type) {
+ case 'server':
+ assert.strictEqual(entry.streamCount, 1);
+ assert.strictEqual(entry.framesReceived, 5);
+ break;
+ case 'client':
+ assert.strictEqual(entry.streamCount, 1);
+ assert.strictEqual(entry.framesReceived, 8);
+ break;
+ default:
+ assert.fail('invalid Http2Session type');
+ }
+ break;
+ case 'Http2Stream':
+ assert.strictEqual(typeof entry.timeToFirstByte, 'number');
+ assert.strictEqual(typeof entry.timeToFirstHeader, 'number');
+ break;
+ default:
+ assert.fail('invalid entry name');
+ }
+});
+obs.observe({ entryTypes: ['http2'] });
+
+const body =
+ 'this is some data';
+
+const server = h2.createServer();
+
+// we use the lower-level API here
+server.on('stream', common.mustCall(onStream));
+
+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.write(body.slice(0, 20));
+ stream.end(body.slice(20));
+}
+
+server.on('session', common.mustCall((session) => {
+ session.ping(common.mustCall());
+}));
+
+server.listen(0);
+
+server.on('listening', common.mustCall(() => {
+
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+
+ client.on('connect', common.mustCall(() => {
+ client.ping(common.mustCall());
+ }));
+
+ const req = client.request();
+
+ req.on('response', common.mustCall());
+
+ let data = '';
+ req.setEncoding('utf8');
+ req.on('data', (d) => data += d);
+ req.on('end', common.mustCall(() => {
+ assert.strictEqual(body, data);
+ }));
+ req.on('close', common.mustCall(() => {
+ client.close();
+ server.close();
+ }));
+
+}));
diff --git a/test/parallel/test-http2-ping-unsolicited-ack.js b/test/parallel/test-http2-ping-unsolicited-ack.js
new file mode 100644
index 00000000000000..5a3a261cb098b1
--- /dev/null
+++ b/test/parallel/test-http2-ping-unsolicited-ack.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const http2 = require('http2');
+const net = require('net');
+const http2util = require('../common/http2');
+
+// Test that ping flooding causes the session to be torn down
+
+const kSettings = new http2util.SettingsFrame();
+const kPingAck = new http2util.PingFrame(true);
+
+const server = http2.createServer();
+
+server.on('stream', common.mustNotCall());
+server.on('session', common.mustCall((session) => {
+ session.on('error', common.expectsError({
+ code: 'ERR_HTTP2_ERROR',
+ message: 'Protocol error'
+ }));
+ session.on('close', common.mustCall(() => server.close()));
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = net.connect(server.address().port);
+
+ client.on('connect', common.mustCall(() => {
+ client.write(http2util.kClientMagic, () => {
+ client.write(kSettings.data);
+ // Send an unsolicited ping ack
+ client.write(kPingAck.data);
+ });
+ }));
+
+ // An error event may or may not be emitted, depending on operating system
+ // and timing. We do not really care if one is emitted here or not, as the
+ // error on the server side is what we are testing for. Do not make this
+ // a common.mustCall() and there's no need to check the error details.
+ client.on('error', () => {});
+}));
diff --git a/test/parallel/test-http2-ping.js b/test/parallel/test-http2-ping.js
index 4892d67b4d738d..32fb8926e4716c 100644
--- a/test/parallel/test-http2-ping.js
+++ b/test/parallel/test-http2-ping.js
@@ -80,7 +80,7 @@ server.listen(0, common.mustCall(() => {
const req = client.request();
req.resume();
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-pipe.js b/test/parallel/test-http2-pipe.js
index 8b446f4f88118b..891fc6e292b909 100644
--- a/test/parallel/test-http2-pipe.js
+++ b/test/parallel/test-http2-pipe.js
@@ -8,7 +8,6 @@ const assert = require('assert');
const http2 = require('http2');
const fs = require('fs');
const path = require('path');
-const Countdown = require('../common/countdown');
// piping should work as expected with createWriteStream
@@ -20,28 +19,28 @@ const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
const dest = stream.pipe(fs.createWriteStream(fn));
- dest.on('finish', common.mustCall(() => {
- assert.strictEqual(fs.readFileSync(loc).length, fs.readFileSync(fn).length);
- fs.unlinkSync(fn);
- stream.respond();
- stream.end();
- }));
+
+ dest.on('finish', () => {
+ assert.strictEqual(fs.readFileSync(loc).length,
+ fs.readFileSync(fn).length);
+ });
+ stream.respond();
+ stream.end();
}));
server.listen(0, common.mustCall(() => {
- const port = server.address().port;
- const client = http2.connect(`http://localhost:${port}`);
-
- const countdown = new Countdown(2, common.mustCall(() => {
- server.close();
- client.destroy();
- }));
+ 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(() => countdown.dec()));
+
+ req.on('close', common.mustCall(() => {
+ server.close();
+ client.close();
+ }));
+
const str = fs.createReadStream(loc);
- str.on('end', common.mustCall(() => countdown.dec()));
+ str.on('end', common.mustCall());
str.pipe(req);
}));
diff --git a/test/parallel/test-http2-priority-cycle-.js b/test/parallel/test-http2-priority-cycle-.js
new file mode 100644
index 00000000000000..af0d66d8343cbf
--- /dev/null
+++ b/test/parallel/test-http2-priority-cycle-.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const assert = require('assert');
+const http2 = require('http2');
+const Countdown = require('../common/countdown');
+
+const server = http2.createServer();
+const largeBuffer = Buffer.alloc(1e4);
+
+// Verify that a dependency cycle may exist, but that it doesn't crash anything
+
+server.on('stream', common.mustCall((stream) => {
+ stream.respond();
+ setImmediate(() => {
+ stream.end(largeBuffer);
+ });
+}, 3));
+server.on('session', common.mustCall((session) => {
+ session.on('priority', (id, parent, weight, exclusive) => {
+ assert.strictEqual(weight, 16);
+ assert.strictEqual(exclusive, false);
+ switch (id) {
+ case 1:
+ assert.strictEqual(parent, 5);
+ break;
+ case 3:
+ assert.strictEqual(parent, 1);
+ break;
+ case 5:
+ assert.strictEqual(parent, 3);
+ break;
+ default:
+ assert.fail('should not happen');
+ }
+ });
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+
+ const countdown = new Countdown(3, () => {
+ client.close();
+ server.close();
+ });
+
+ {
+ const req = client.request();
+ req.priority({ parent: 5 });
+ req.resume();
+ req.on('close', () => countdown.dec());
+ }
+
+ {
+ const req = client.request();
+ req.priority({ parent: 1 });
+ req.resume();
+ req.on('close', () => countdown.dec());
+ }
+
+ {
+ const req = client.request();
+ req.priority({ parent: 3 });
+ req.resume();
+ req.on('close', () => countdown.dec());
+ }
+}));
diff --git a/test/parallel/test-http2-priority-event.js b/test/parallel/test-http2-priority-event.js
index b0704902d31101..fe04ffb342d70d 100644
--- a/test/parallel/test-http2-priority-event.js
+++ b/test/parallel/test-http2-priority-event.js
@@ -54,7 +54,7 @@ server.on('listening', common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-errors.js b/test/parallel/test-http2-respond-errors.js
index dcc05357faedad..2a48456c9394a4 100644
--- a/test/parallel/test-http2-respond-errors.js
+++ b/test/parallel/test-http2-respond-errors.js
@@ -74,13 +74,18 @@ function runTest(test) {
const client = http2.connect(url);
const req = client.request(headers);
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 2'
+ }));
currentError = test;
req.resume();
req.end();
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
if (!tests.length) {
server.close();
diff --git a/test/parallel/test-http2-respond-file-204.js b/test/parallel/test-http2-respond-file-204.js
index 4be2d42c779a5e..b0ba634e67df69 100644
--- a/test/parallel/test-http2-respond-file-204.js
+++ b/test/parallel/test-http2-respond-file-204.js
@@ -34,7 +34,7 @@ server.listen(0, () => {
req.on('response', common.mustCall());
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-304.js b/test/parallel/test-http2-respond-file-304.js
index e6e0842c7f9448..536c48c624e73c 100644
--- a/test/parallel/test-http2-respond-file-304.js
+++ b/test/parallel/test-http2-respond-file-304.js
@@ -38,7 +38,7 @@ server.listen(0, () => {
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-404.js b/test/parallel/test-http2-respond-file-404.js
index ba62f384485bc0..60bc21f185dd5c 100644
--- a/test/parallel/test-http2-respond-file-404.js
+++ b/test/parallel/test-http2-respond-file-404.js
@@ -40,7 +40,7 @@ server.listen(0, () => {
}));
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-compat.js b/test/parallel/test-http2-respond-file-compat.js
index 0f6e3199d68ab2..0205f2d0d85aaf 100644
--- a/test/parallel/test-http2-respond-file-compat.js
+++ b/test/parallel/test-http2-respond-file-compat.js
@@ -16,7 +16,7 @@ server.listen(0, () => {
const req = client.request();
req.on('response', common.mustCall());
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-error-dir.js b/test/parallel/test-http2-respond-file-error-dir.js
index 18a9540451f865..6818616227df89 100644
--- a/test/parallel/test-http2-respond-file-error-dir.js
+++ b/test/parallel/test-http2-respond-file-error-dir.js
@@ -6,14 +6,10 @@ if (!common.hasCrypto)
const http2 = require('http2');
const assert = require('assert');
-const {
- HTTP2_HEADER_CONTENT_TYPE
-} = http2.constants;
-
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFile(process.cwd(), {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}, {
onError(err) {
common.expectsError({
@@ -38,7 +34,7 @@ server.listen(0, () => {
}));
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-errors.js b/test/parallel/test-http2-respond-file-errors.js
index c2c749873c82ac..83d3900bc5c288 100644
--- a/test/parallel/test-http2-respond-file-errors.js
+++ b/test/parallel/test-http2-respond-file-errors.js
@@ -6,11 +6,6 @@ if (!common.hasCrypto)
const fixtures = require('../common/fixtures');
const http2 = require('http2');
-const {
- HTTP2_HEADER_CONTENT_TYPE,
- HTTP2_HEADER_METHOD
-} = http2.constants;
-
const optionsWithTypeError = {
offset: 'number',
length: 'number',
@@ -33,6 +28,7 @@ const fname = fixtures.path('elipses.txt');
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
+
// Check for all possible TypeError triggers on options
Object.keys(optionsWithTypeError).forEach((option) => {
Object.keys(types).forEach((type) => {
@@ -42,7 +38,7 @@ server.on('stream', common.mustCall((stream) => {
common.expectsError(
() => stream.respondWithFile(fname, {
- [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}, {
[option]: types[type]
}),
@@ -59,7 +55,7 @@ server.on('stream', common.mustCall((stream) => {
// Should throw if :status 204, 205 or 304
[204, 205, 304].forEach((status) => common.expectsError(
() => stream.respondWithFile(fname, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain',
+ 'content-type': 'text/plain',
':status': status,
}),
{
@@ -68,31 +64,11 @@ server.on('stream', common.mustCall((stream) => {
}
));
- // should emit an error on the stream if headers aren't valid
- stream.respondWithFile(fname, {
- [HTTP2_HEADER_METHOD]: 'POST'
- }, {
- statCheck: common.mustCall(() => {
- // give time to the current test case to finish
- process.nextTick(continueTest, stream);
- return true;
- })
- });
- stream.once('error', common.expectsError({
- code: 'ERR_HTTP2_INVALID_PSEUDOHEADER',
- type: Error,
- message: '":method" is an invalid pseudoheader or is used incorrectly'
- }));
-}));
-
-function continueTest(stream) {
// Should throw if headers already sent
- stream.respond({
- ':status': 200,
- });
+ stream.respond({ ':status': 200 });
common.expectsError(
() => stream.respondWithFile(fname, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}),
{
code: 'ERR_HTTP2_HEADERS_SENT',
@@ -104,21 +80,21 @@ function continueTest(stream) {
stream.destroy();
common.expectsError(
() => stream.respondWithFile(fname, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}),
{
code: 'ERR_HTTP2_INVALID_STREAM',
message: 'The stream has been destroyed'
}
);
-}
+}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('close', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-fd-errors.js b/test/parallel/test-http2-respond-file-fd-errors.js
index 9458b2f49af087..44876b60e1c4cb 100644
--- a/test/parallel/test-http2-respond-file-fd-errors.js
+++ b/test/parallel/test-http2-respond-file-fd-errors.js
@@ -7,11 +7,6 @@ const fixtures = require('../common/fixtures');
const http2 = require('http2');
const fs = require('fs');
-const {
- HTTP2_HEADER_CONTENT_TYPE,
- HTTP2_HEADER_METHOD
-} = http2.constants;
-
const optionsWithTypeError = {
offset: 'number',
length: 'number',
@@ -43,7 +38,7 @@ server.on('stream', common.mustCall((stream) => {
common.expectsError(
() => stream.respondWithFD(types[type], {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}),
{
type: TypeError,
@@ -62,7 +57,7 @@ server.on('stream', common.mustCall((stream) => {
common.expectsError(
() => stream.respondWithFD(fd, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}, {
[option]: types[type]
}),
@@ -79,7 +74,7 @@ server.on('stream', common.mustCall((stream) => {
// Should throw if :status 204, 205 or 304
[204, 205, 304].forEach((status) => common.expectsError(
() => stream.respondWithFD(fd, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain',
+ 'content-type': 'text/plain',
':status': status,
}),
{
@@ -89,35 +84,11 @@ server.on('stream', common.mustCall((stream) => {
}
));
- // should emit an error on the stream if headers aren't valid
- stream.respondWithFD(fd, {
- [HTTP2_HEADER_METHOD]: 'POST'
- }, {
- statCheck() {
- return true;
- }
- });
- stream.once('error', common.expectsError({
- code: 'ERR_HTTP2_INVALID_PSEUDOHEADER',
- type: Error,
- message: '":method" is an invalid pseudoheader or is used incorrectly'
- }));
- stream.respondWithFD(fd, {
- [HTTP2_HEADER_METHOD]: 'POST'
- });
- stream.once('error', common.expectsError({
- code: 'ERR_HTTP2_INVALID_PSEUDOHEADER',
- type: Error,
- message: '":method" is an invalid pseudoheader or is used incorrectly'
- }));
-
// Should throw if headers already sent
- stream.respond({
- ':status': 200,
- });
+ stream.respond();
common.expectsError(
() => stream.respondWithFD(fd, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}),
{
code: 'ERR_HTTP2_HEADERS_SENT',
@@ -130,7 +101,7 @@ server.on('stream', common.mustCall((stream) => {
stream.destroy();
common.expectsError(
() => stream.respondWithFD(fd, {
- [HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
+ 'content-type': 'text/plain'
}),
{
code: 'ERR_HTTP2_INVALID_STREAM',
@@ -145,7 +116,7 @@ server.listen(0, common.mustCall(() => {
const req = client.request();
req.on('close', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-fd-invalid.js b/test/parallel/test-http2-respond-file-fd-invalid.js
index f3bcab8904bee4..77a4d3df00d0d6 100644
--- a/test/parallel/test-http2-respond-file-fd-invalid.js
+++ b/test/parallel/test-http2-respond-file-fd-invalid.js
@@ -31,7 +31,7 @@ server.listen(0, () => {
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
assert.strictEqual(req.rstCode, NGHTTP2_INTERNAL_ERROR);
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-fd-range.js b/test/parallel/test-http2-respond-file-fd-range.js
index 8479dca518558e..2dd73e0001544c 100644
--- a/test/parallel/test-http2-respond-file-fd-range.js
+++ b/test/parallel/test-http2-respond-file-fd-range.js
@@ -9,6 +9,7 @@ const fixtures = require('../common/fixtures');
const http2 = require('http2');
const assert = require('assert');
const fs = require('fs');
+const Countdown = require('../common/countdown');
const {
HTTP2_HEADER_CONTENT_TYPE,
@@ -39,7 +40,7 @@ server.on('stream', (stream, headers) => {
statCheck: common.mustCall((stat, headers, options) => {
assert.strictEqual(options.length, length);
assert.strictEqual(options.offset, offset);
- headers[HTTP2_HEADER_CONTENT_LENGTH] =
+ headers['content-length'] =
Math.min(options.length, stat.size - offset);
}),
offset: offset,
@@ -47,23 +48,21 @@ server.on('stream', (stream, headers) => {
});
});
server.on('close', common.mustCall(() => fs.closeSync(fd)));
+
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
- let remaining = 2;
- function maybeClose() {
- if (--remaining === 0) {
- client.destroy();
- server.close();
- }
- }
+ const countdown = new Countdown(2, () => {
+ client.close();
+ server.close();
+ });
{
const req = client.request({ range: 'bytes=8-11' });
req.on('response', common.mustCall((headers) => {
- assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
- assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], 3);
+ assert.strictEqual(headers['content-type'], 'text/plain');
+ assert.strictEqual(+headers['content-length'], 3);
}));
req.setEncoding('utf8');
let check = '';
@@ -71,7 +70,7 @@ server.listen(0, () => {
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8', 8, 11));
}));
- req.on('close', common.mustCall(maybeClose));
+ req.on('close', common.mustCall(() => countdown.dec()));
req.end();
}
@@ -88,7 +87,7 @@ server.listen(0, () => {
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8', 8, 28));
}));
- req.on('close', common.mustCall(maybeClose));
+ req.on('close', common.mustCall(() => countdown.dec()));
req.end();
}
diff --git a/test/parallel/test-http2-respond-file-fd.js b/test/parallel/test-http2-respond-file-fd.js
index 303d25be3f2b66..7d4395bbc360aa 100644
--- a/test/parallel/test-http2-respond-file-fd.js
+++ b/test/parallel/test-http2-respond-file-fd.js
@@ -40,7 +40,7 @@ server.listen(0, () => {
req.on('data', (chunk) => check += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8'));
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file-push.js b/test/parallel/test-http2-respond-file-push.js
index 4f7b179faf81a8..a5229beb07d1a7 100644
--- a/test/parallel/test-http2-respond-file-push.js
+++ b/test/parallel/test-http2-respond-file-push.js
@@ -29,7 +29,8 @@ server.on('stream', (stream) => {
stream.pushStream({
':path': '/file.txt',
':method': 'GET'
- }, (stream) => {
+ }, (err, stream) => {
+ assert.ifError(err);
stream.respondWithFD(fd, {
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain',
[HTTP2_HEADER_CONTENT_LENGTH]: stat.size,
@@ -50,7 +51,7 @@ server.listen(0, () => {
function maybeClose() {
if (--expected === 0) {
server.close();
- client.destroy();
+ client.close();
}
}
diff --git a/test/parallel/test-http2-respond-file-range.js b/test/parallel/test-http2-respond-file-range.js
index a5995cbba77c1c..4e6a6074514f14 100644
--- a/test/parallel/test-http2-respond-file-range.js
+++ b/test/parallel/test-http2-respond-file-range.js
@@ -46,7 +46,7 @@ server.listen(0, () => {
req.on('data', (chunk) => check += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8', 8, 11));
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-file.js b/test/parallel/test-http2-respond-file.js
index c2f513b7cae2b7..9ad8e7a69648dc 100644
--- a/test/parallel/test-http2-respond-file.js
+++ b/test/parallel/test-http2-respond-file.js
@@ -45,7 +45,7 @@ server.listen(0, () => {
req.on('data', (chunk) => check += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(check, data.toString('utf8'));
- client.destroy();
+ client.close();
server.close();
}));
req.end();
diff --git a/test/parallel/test-http2-respond-no-data.js b/test/parallel/test-http2-respond-no-data.js
index d891fe4e8ddd2b..9572bdffe54927 100644
--- a/test/parallel/test-http2-respond-no-data.js
+++ b/test/parallel/test-http2-respond-no-data.js
@@ -27,7 +27,7 @@ function makeRequest() {
req.resume();
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
if (!status.length) {
server.close();
diff --git a/test/parallel/test-http2-respond-with-fd-errors.js b/test/parallel/test-http2-respond-with-fd-errors.js
index c8ecfcf5f3490e..0b215134663bda 100644
--- a/test/parallel/test-http2-respond-with-fd-errors.js
+++ b/test/parallel/test-http2-respond-with-fd-errors.js
@@ -82,12 +82,18 @@ function runTest(test) {
const client = http2.connect(url);
const req = client.request(headers);
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 2'
+ }));
+
currentError = test;
req.resume();
req.end();
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
if (!tests.length) {
server.close();
diff --git a/test/parallel/test-http2-response-splitting.js b/test/parallel/test-http2-response-splitting.js
index 1d9b616105f450..9613eca9636ae4 100644
--- a/test/parallel/test-http2-response-splitting.js
+++ b/test/parallel/test-http2-response-splitting.js
@@ -55,7 +55,7 @@ server.listen(0, common.mustCall(() => {
function maybeClose() {
if (remaining === 0) {
server.close();
- client.destroy();
+ client.close();
}
}
diff --git a/test/parallel/test-http2-rststream-errors.js b/test/parallel/test-http2-rststream-errors.js
deleted file mode 100644
index f53956ce998e93..00000000000000
--- a/test/parallel/test-http2-rststream-errors.js
+++ /dev/null
@@ -1,94 +0,0 @@
-'use strict';
-
-const common = require('../common');
-if (!common.hasCrypto)
- common.skip('missing crypto');
-const http2 = require('http2');
-const {
- constants,
- Http2Stream,
- nghttp2ErrorString
-} = process.binding('http2');
-
-// tests error handling within rstStream
-// - every other NGHTTP2 error from binding (should emit stream error)
-
-const specificTestKeys = [];
-const specificTests = [];
-
-const genericTests = Object.getOwnPropertyNames(constants)
- .filter((key) => (
- key.indexOf('NGHTTP2_ERR') === 0 && specificTestKeys.indexOf(key) < 0
- ))
- .map((key) => ({
- ngError: constants[key],
- error: {
- code: 'ERR_HTTP2_ERROR',
- type: Error,
- message: nghttp2ErrorString(constants[key])
- },
- type: 'stream'
- }));
-
-
-const tests = specificTests.concat(genericTests);
-
-let currentError;
-
-// mock submitRstStream because we only care about testing error handling
-Http2Stream.prototype.rstStream = () => currentError.ngError;
-
-const server = http2.createServer();
-server.on('stream', common.mustCall((stream, headers) => {
- const errorMustCall = common.expectsError(currentError.error);
- const errorMustNotCall = common.mustNotCall(
- `${currentError.error.code} should emit on ${currentError.type}`
- );
-
- if (currentError.type === 'stream') {
- stream.session.on('error', errorMustNotCall);
- stream.on('error', errorMustCall);
- stream.on('error', common.mustCall(() => {
- stream.session.destroy();
- }));
- } else {
- stream.session.once('error', errorMustCall);
- stream.on('error', errorMustNotCall);
- }
-
- stream.rstStream();
-}, tests.length));
-
-server.listen(0, common.mustCall(() => runTest(tests.shift())));
-
-function runTest(test) {
- const port = server.address().port;
- const url = `http://localhost:${port}`;
- const headers = {
- ':path': '/',
- ':method': 'POST',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- };
-
- const client = http2.connect(url);
- const req = client.request(headers);
-
- currentError = test;
- req.resume();
- req.end();
-
- if (currentError.type === 'stream') {
- req.on('error', common.mustCall());
- }
-
- req.on('end', common.mustCall(() => {
- client.destroy();
-
- if (!tests.length) {
- server.close();
- } else {
- runTest(tests.shift());
- }
- }));
-}
diff --git a/test/parallel/test-http2-serve-file.js b/test/parallel/test-http2-serve-file.js
index af82360e464b31..7b73fe639e0cc5 100644
--- a/test/parallel/test-http2-serve-file.js
+++ b/test/parallel/test-http2-serve-file.js
@@ -48,7 +48,7 @@ server.listen(0, () => {
let remaining = 2;
function maybeClose() {
if (--remaining === 0) {
- client.destroy();
+ client.close();
server.close();
}
}
diff --git a/test/parallel/test-http2-server-errors.js b/test/parallel/test-http2-server-errors.js
index 7d7db6a24538fd..a3586bd64d46e7 100644
--- a/test/parallel/test-http2-server-errors.js
+++ b/test/parallel/test-http2-server-errors.js
@@ -6,7 +6,6 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
-const { Http2Stream } = require('internal/http2/core');
// Errors should not be reported both in Http2ServerRequest
// and Http2ServerResponse
@@ -29,11 +28,6 @@ const { Http2Stream } = require('internal/http2/core');
server.close();
}));
- server.on('streamError', common.mustCall(function(err, stream) {
- assert.strictEqual(err, expected);
- assert.strictEqual(stream instanceof Http2Stream, true);
- }));
-
server.listen(0, common.mustCall(function() {
const port = server.address().port;
@@ -70,11 +64,6 @@ const { Http2Stream } = require('internal/http2/core');
server.close();
}));
- server.on('streamError', common.mustCall(function(err, stream) {
- assert.strictEqual(err, expected);
- assert.strictEqual(stream instanceof Http2Stream, true);
- }));
-
server.listen(0, common.mustCall(function() {
const port = server.address().port;
diff --git a/test/parallel/test-http2-server-http1-client.js b/test/parallel/test-http2-server-http1-client.js
index ef3a79c0fd143a..34a8f48b5e130d 100644
--- a/test/parallel/test-http2-server-http1-client.js
+++ b/test/parallel/test-http2-server-http1-client.js
@@ -12,11 +12,14 @@ const server = http2.createServer();
server.on('stream', common.mustNotCall());
server.on('session', common.mustCall((session) => {
session.on('close', common.mustCall());
+ session.on('error', common.expectsError({
+ code: 'ERR_HTTP2_ERROR',
+ type: Error,
+ message: 'Received bad client magic byte string'
+ }));
}));
server.listen(0, common.mustCall(() => {
const req = http.get(`http://localhost:${server.address().port}`);
- req.on('error', (error) => {
- server.close();
- });
+ req.on('error', (error) => server.close());
}));
diff --git a/test/parallel/test-http2-server-push-disabled.js b/test/parallel/test-http2-server-push-disabled.js
index 33390f2ecae886..9a0b748d354276 100644
--- a/test/parallel/test-http2-server-push-disabled.js
+++ b/test/parallel/test-http2-server-push-disabled.js
@@ -48,7 +48,7 @@ server.listen(0, common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
}));
diff --git a/test/parallel/test-http2-server-push-stream-errors-args.js b/test/parallel/test-http2-server-push-stream-errors-args.js
index 73fa064b439199..f752f44310c0df 100644
--- a/test/parallel/test-http2-server-push-stream-errors-args.js
+++ b/test/parallel/test-http2-server-push-stream-errors-args.js
@@ -50,7 +50,7 @@ server.listen(0, common.mustCall(() => {
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'test');
server.close();
- client.destroy();
+ client.close();
}));
req.end();
}));
diff --git a/test/parallel/test-http2-server-push-stream-errors.js b/test/parallel/test-http2-server-push-stream-errors.js
index 56e329dcff1cd2..7eaf4dc94d15e2 100644
--- a/test/parallel/test-http2-server-push-stream-errors.js
+++ b/test/parallel/test-http2-server-push-stream-errors.js
@@ -34,9 +34,8 @@ const specificTests = [
{
ngError: constants.NGHTTP2_ERR_STREAM_CLOSED,
error: {
- code: 'ERR_HTTP2_STREAM_CLOSED',
- type: Error,
- message: 'The stream is already closed'
+ code: 'ERR_HTTP2_INVALID_STREAM',
+ type: Error
},
type: 'stream'
},
@@ -66,47 +65,25 @@ Http2Stream.prototype.pushPromise = () => currentError.ngError;
const server = http2.createServer();
server.on('stream', common.mustCall((stream, headers) => {
- const errorMustCall = common.expectsError(currentError.error);
- const errorMustNotCall = common.mustNotCall(
- `${currentError.error.code} should emit on ${currentError.type}`
- );
-
- if (currentError.type === 'stream') {
- stream.session.on('error', errorMustNotCall);
- stream.on('error', errorMustCall);
- stream.on('error', common.mustCall(() => {
- stream.respond();
- stream.end();
- }));
- } else {
- stream.session.once('error', errorMustCall);
- stream.on('error', errorMustNotCall);
- }
-
- stream.pushStream({}, () => {});
+ stream.pushStream({}, common.expectsError(currentError.error));
+ stream.respond();
+ stream.end();
}, tests.length));
server.listen(0, common.mustCall(() => runTest(tests.shift())));
function runTest(test) {
- const port = server.address().port;
- const url = `http://localhost:${port}`;
- const headers = {
- ':path': '/',
- ':method': 'POST',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- };
+ const url = `http://localhost:${server.address().port}`;
const client = http2.connect(url);
- const req = client.request(headers);
+ const req = client.request();
currentError = test;
req.resume();
req.end();
- req.on('end', common.mustCall(() => {
- client.destroy();
+ req.on('close', common.mustCall(() => {
+ client.close();
if (!tests.length) {
server.close();
diff --git a/test/parallel/test-http2-server-push-stream-head.js b/test/parallel/test-http2-server-push-stream-head.js
index c2fc8db4a92eba..cd2276746f4bdd 100644
--- a/test/parallel/test-http2-server-push-stream-head.js
+++ b/test/parallel/test-http2-server-push-stream-head.js
@@ -5,6 +5,7 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
+const Countdown = require('../common/countdown');
// Check that pushStream handles method HEAD correctly
// - stream should end immediately (no body)
@@ -17,8 +18,10 @@ server.on('stream', common.mustCall((stream, headers) => {
':scheme': 'http',
':method': 'HEAD',
':authority': `localhost:${port}`,
- }, common.mustCall((push, headers) => {
+ }, common.mustCall((err, push, headers) => {
assert.strictEqual(push._writableState.ended, true);
+ push.respond();
+ assert(!push.write('test'));
stream.end('test');
}));
}
@@ -30,15 +33,26 @@ server.on('stream', common.mustCall((stream, headers) => {
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);
+
+ const countdown = new Countdown(2, () => {
+ server.close();
+ client.close();
+ });
+
+ const req = client.request();
req.setEncoding('utf8');
client.on('stream', common.mustCall((stream, headers) => {
+ assert.strictEqual(headers[':method'], 'HEAD');
assert.strictEqual(headers[':scheme'], 'http');
assert.strictEqual(headers[':path'], '/');
assert.strictEqual(headers[':authority'], `localhost:${port}`);
+ stream.on('push', common.mustCall(() => {
+ stream.on('data', common.mustNotCall());
+ stream.on('end', common.mustCall());
+ }));
+ stream.on('close', common.mustCall(() => countdown.dec()));
}));
let data = '';
@@ -46,8 +60,7 @@ server.listen(0, common.mustCall(() => {
req.on('data', common.mustCall((d) => data += d));
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'test');
- server.close();
- client.destroy();
}));
+ req.on('close', common.mustCall(() => countdown.dec()));
req.end();
}));
diff --git a/test/parallel/test-http2-server-push-stream.js b/test/parallel/test-http2-server-push-stream.js
index 395743869198ca..6ac10cae77f951 100644
--- a/test/parallel/test-http2-server-push-stream.js
+++ b/test/parallel/test-http2-server-push-stream.js
@@ -14,7 +14,8 @@ server.on('stream', common.mustCall((stream, headers) => {
':scheme': 'http',
':path': '/foobar',
':authority': `localhost:${port}`,
- }, common.mustCall((push, headers) => {
+ }, common.mustCall((err, push, headers) => {
+ assert.ifError(err);
push.respond({
'content-type': 'text/html',
':status': 200,
@@ -53,7 +54,7 @@ server.listen(0, common.mustCall(() => {
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'test');
server.close();
- client.destroy();
+ client.close();
}));
req.end();
}));
diff --git a/test/parallel/test-http2-server-rst-before-respond.js b/test/parallel/test-http2-server-rst-before-respond.js
index 950beea4eb39ab..74181f4ffa0bbe 100644
--- a/test/parallel/test-http2-server-rst-before-respond.js
+++ b/test/parallel/test-http2-server-rst-before-respond.js
@@ -12,35 +12,26 @@ const server = h2.createServer();
server.on('stream', common.mustCall(onStream));
function onStream(stream, headers, flags) {
- stream.rstStream();
+ stream.close();
common.expectsError(() => {
stream.additionalHeaders({
':status': 123,
abc: 123
});
- }, {
- code: 'ERR_HTTP2_INVALID_STREAM',
- message: /^The stream has been destroyed$/
- });
+ }, { code: 'ERR_HTTP2_INVALID_STREAM' });
}
server.listen(0);
server.on('listening', common.mustCall(() => {
-
const client = h2.connect(`http://localhost:${server.address().port}`);
-
- const req = client.request({ ':path': '/' });
-
+ const req = client.request();
req.on('headers', common.mustNotCall());
-
req.on('close', common.mustCall((code) => {
assert.strictEqual(h2.constants.NGHTTP2_NO_ERROR, code);
server.close();
- client.destroy();
+ client.close();
}));
-
req.on('response', common.mustNotCall());
-
}));
diff --git a/test/parallel/test-http2-server-rst-stream.js b/test/parallel/test-http2-server-rst-stream.js
index 4b04f29c8ec7c0..c2d938c22f4483 100644
--- a/test/parallel/test-http2-server-rst-stream.js
+++ b/test/parallel/test-http2-server-rst-stream.js
@@ -16,39 +16,38 @@ const {
} = http2.constants;
const tests = [
- ['rstStream', NGHTTP2_NO_ERROR, false],
- ['rstWithNoError', NGHTTP2_NO_ERROR, false],
- ['rstWithProtocolError', NGHTTP2_PROTOCOL_ERROR, true],
- ['rstWithCancel', NGHTTP2_CANCEL, false],
- ['rstWithRefuse', NGHTTP2_REFUSED_STREAM, true],
- ['rstWithInternalError', NGHTTP2_INTERNAL_ERROR, true]
+ [NGHTTP2_NO_ERROR, false],
+ [NGHTTP2_NO_ERROR, false],
+ [NGHTTP2_PROTOCOL_ERROR, true],
+ [NGHTTP2_CANCEL, false],
+ [NGHTTP2_REFUSED_STREAM, true],
+ [NGHTTP2_INTERNAL_ERROR, true]
];
const server = http2.createServer();
server.on('stream', (stream, headers) => {
- const method = headers['rstmethod'];
- stream[method]();
+ stream.close(headers['rstcode'] | 0);
});
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const countdown = new Countdown(tests.length, common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
tests.forEach((test) => {
const req = client.request({
':method': 'POST',
- rstmethod: test[0]
+ rstcode: test[0]
});
req.on('close', common.mustCall((code) => {
- assert.strictEqual(code, test[1]);
+ assert.strictEqual(code, test[0]);
countdown.dec();
}));
req.on('aborted', common.mustCall());
- if (test[2])
+ if (test[1])
req.on('error', common.mustCall());
else
req.on('error', common.mustNotCall());
diff --git a/test/parallel/test-http2-server-sessionerror.js b/test/parallel/test-http2-server-sessionerror.js
new file mode 100644
index 00000000000000..525eb2e6efd11a
--- /dev/null
+++ b/test/parallel/test-http2-server-sessionerror.js
@@ -0,0 +1,48 @@
+// Flags: --expose-internals
+
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const http2 = require('http2');
+const { kSocket } = require('internal/http2/util');
+
+const server = http2.createServer();
+server.on('stream', common.mustNotCall());
+
+let test = 0;
+
+server.on('session', common.mustCall((session) => {
+ switch (++test) {
+ case 1:
+ server.on('error', common.mustNotCall());
+ session.on('error', common.expectsError({
+ type: Error,
+ message: 'test'
+ }));
+ session[kSocket].emit('error', new Error('test'));
+ break;
+ case 2:
+ // If the server does not have a socketError listener,
+ // error will be silent on the server but will close
+ // the session
+ session[kSocket].emit('error', new Error('test'));
+ break;
+ }
+}, 2));
+
+server.listen(0, common.mustCall(() => {
+ const url = `http://localhost:${server.address().port}`;
+ http2.connect(url)
+ // An ECONNRESET error may occur depending on the platform (due largely
+ // to differences in the timing of socket closing). Do not wrap this in
+ // a common must call.
+ .on('error', () => {})
+ .on('close', () => {
+ server.removeAllListeners('error');
+ http2.connect(url)
+ .on('error', () => {})
+ .on('close', () => server.close());
+ });
+}));
diff --git a/test/parallel/test-http2-server-set-header.js b/test/parallel/test-http2-server-set-header.js
index ed27638f6849f4..4b6228053f8ece 100644
--- a/test/parallel/test-http2-server-set-header.js
+++ b/test/parallel/test-http2-server-set-header.js
@@ -29,7 +29,7 @@ server.listen(0, common.mustCall(() => {
req.on('end', () => {
assert.strictEqual(body, data);
server.close();
- client.destroy();
+ client.close();
});
req.end();
}));
diff --git a/test/parallel/test-http2-server-shutdown-before-respond.js b/test/parallel/test-http2-server-shutdown-before-respond.js
index c3ad9714b5f39b..33f224fc69a9d5 100644
--- a/test/parallel/test-http2-server-shutdown-before-respond.js
+++ b/test/parallel/test-http2-server-shutdown-before-respond.js
@@ -11,24 +11,26 @@ const server = h2.createServer();
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.session.goaway(1);
+ stream.respond();
stream.end('data');
}
server.listen(0);
server.on('listening', common.mustCall(() => {
-
const client = h2.connect(`http://localhost:${server.address().port}`);
client.on('goaway', common.mustCall());
+ client.on('error', common.expectsError({
+ code: 'ERR_HTTP2_SESSION_ERROR'
+ }));
const req = client.request();
-
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_SESSION_ERROR'
+ }));
req.resume();
+ req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => server.close()));
}));
diff --git a/test/parallel/test-http2-server-shutdown-options-errors.js b/test/parallel/test-http2-server-shutdown-options-errors.js
index 2bf2b47dd73dec..6cee8f25729535 100644
--- a/test/parallel/test-http2-server-shutdown-options-errors.js
+++ b/test/parallel/test-http2-server-shutdown-options-errors.js
@@ -7,55 +7,63 @@ const http2 = require('http2');
const server = http2.createServer();
-const optionsToTest = {
- opaqueData: 'Uint8Array',
- graceful: 'boolean',
- errorCode: 'number',
- lastStreamID: 'number'
-};
+const types = [
+ true,
+ {},
+ [],
+ null,
+ new Date()
+];
-const types = {
- boolean: true,
- number: 1,
- object: {},
- array: [],
- null: null,
- Uint8Array: Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5])
-};
+server.on('stream', common.mustCall((stream) => {
+ const session = stream.session;
-server.on(
- 'stream',
- common.mustCall((stream) => {
- Object.keys(optionsToTest).forEach((option) => {
- Object.keys(types).forEach((type) => {
- if (type === optionsToTest[option]) {
- return;
- }
- common.expectsError(
- () =>
- stream.session.shutdown(
- { [option]: types[type] },
- common.mustNotCall()
- ),
- {
- type: TypeError,
- code: 'ERR_INVALID_OPT_VALUE',
- message: `The value "${String(types[type])}" is invalid ` +
- `for option "${option}"`
- }
- );
- });
- });
- stream.session.destroy();
- })
-);
+ types.forEach((i) => {
+ common.expectsError(
+ () => session.goaway(i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message: 'The "code" argument must be of type number'
+ }
+ );
+ common.expectsError(
+ () => session.goaway(0, i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message: 'The "lastStreamID" argument must be of type number'
+ }
+ );
+ common.expectsError(
+ () => session.goaway(0, 0, i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message: 'The "opaqueData" argument must be one of type Buffer, ' +
+ 'TypedArray, or DataView'
+ }
+ );
+ });
+
+ stream.session.destroy();
+}));
server.listen(
0,
common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
+ // On certain operating systems, an ECONNRESET may occur. We do not need
+ // to test for it here. Do not make this a mustCall
+ client.on('error', () => {});
const req = client.request();
+ // On certain operating systems, an ECONNRESET may occur. We do not need
+ // to test for it here. Do not make this a mustCall
+ req.on('error', () => {});
req.resume();
- req.on('end', common.mustCall(() => server.close()));
+ req.on('close', common.mustCall(() => {
+ client.close();
+ server.close();
+ }));
})
);
diff --git a/test/parallel/test-http2-server-shutdown-redundant.js b/test/parallel/test-http2-server-shutdown-redundant.js
index 708be0868c2f9e..f47a0d2c951c86 100644
--- a/test/parallel/test-http2-server-shutdown-redundant.js
+++ b/test/parallel/test-http2-server-shutdown-redundant.js
@@ -3,27 +3,38 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
-const assert = require('assert');
const http2 = require('http2');
const server = http2.createServer();
-// Test blank return when a stream.session.shutdown is called twice
-// Also tests stream.session.shutdown with just a callback function (no options)
server.on('stream', common.mustCall((stream) => {
- stream.session.shutdown(common.mustCall(() => {
- assert.strictEqual(
- stream.session.shutdown(common.mustNotCall()),
- undefined
+ const session = stream.session;
+ session.goaway(1);
+ session.goaway(2);
+ stream.session.on('close', common.mustCall(() => {
+ common.expectsError(
+ () => session.goaway(3),
+ {
+ code: 'ERR_HTTP2_INVALID_SESSION',
+ type: Error
+ }
);
}));
- stream.session.shutdown(common.mustNotCall());
}));
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
+ client.on('error', common.expectsError({
+ code: 'ERR_HTTP2_SESSION_ERROR'
+ }));
const req = client.request();
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_SESSION_ERROR'
+ }));
req.resume();
- req.on('end', common.mustCall(() => server.close()));
+ req.on('close', common.mustCall(() => {
+ server.close();
+ client.close();
+ }));
}));
diff --git a/test/parallel/test-http2-server-socket-destroy.js b/test/parallel/test-http2-server-socket-destroy.js
index 8291c415284571..03afc1957b8af4 100644
--- a/test/parallel/test-http2-server-socket-destroy.js
+++ b/test/parallel/test-http2-server-socket-destroy.js
@@ -9,22 +9,13 @@ const assert = require('assert');
const h2 = require('http2');
const { kSocket } = require('internal/http2/util');
-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.respond();
stream.write('test');
const socket = stream.session[kSocket];
@@ -32,6 +23,7 @@ function onStream(stream) {
// When the socket is destroyed, the close events must be triggered
// on the socket, server and session.
socket.on('close', common.mustCall());
+ stream.on('close', common.mustCall());
server.on('close', common.mustCall());
stream.session.on('close', common.mustCall(() => server.close()));
@@ -40,23 +32,25 @@ function onStream(stream) {
assert.notStrictEqual(stream.session, undefined);
socket.destroy();
- stream.on('destroy', common.mustCall(() => {
- assert.strictEqual(stream.session, undefined);
- }));
}
server.listen(0);
server.on('listening', common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
+ // The client may have an ECONNRESET error here depending on the operating
+ // system, due mainly to differences in the timing of socket closing. Do
+ // not wrap this in a common mustCall.
+ client.on('error', () => {});
+ client.on('close', common.mustCall());
- const req = client.request({
- [HTTP2_HEADER_PATH]: '/',
- [HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST });
+ const req = client.request({ ':method': 'POST' });
+ // The client may have an ECONNRESET error here depending on the operating
+ // system, due mainly to differences in the timing of socket closing. Do
+ // not wrap this in a common mustCall.
+ req.on('error', () => {});
req.on('aborted', common.mustCall());
req.resume();
req.on('end', common.mustCall());
-
- client.on('close', common.mustCall());
}));
diff --git a/test/parallel/test-http2-server-socketerror.js b/test/parallel/test-http2-server-socketerror.js
deleted file mode 100644
index 9f52b9280d2779..00000000000000
--- a/test/parallel/test-http2-server-socketerror.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// Flags: --expose-internals
-
-'use strict';
-
-const common = require('../common');
-if (!common.hasCrypto)
- common.skip('missing crypto');
-const assert = require('assert');
-const http2 = require('http2');
-const { kSocket } = require('internal/http2/util');
-
-const server = http2.createServer();
-server.on('stream', common.mustCall((stream) => {
- stream.respond();
- stream.end('ok');
-}));
-server.on('session', common.mustCall((session) => {
- // First, test that the socketError event is forwarded to the session object
- // and not the server object.
- const handler = common.mustCall((error, socket) => {
- common.expectsError({
- type: Error,
- message: 'test'
- })(error);
- assert.strictEqual(socket, session[kSocket]);
- });
- const isNotCalled = common.mustNotCall();
- session.on('socketError', handler);
- server.on('socketError', isNotCalled);
- session[kSocket].emit('error', new Error('test'));
- session.removeListener('socketError', handler);
- server.removeListener('socketError', isNotCalled);
-
- // Second, test that the socketError is forwarded to the server object when
- // no socketError listener is registered for the session
- server.on('socketError', common.mustCall((error, socket, session) => {
- common.expectsError({
- type: Error,
- message: 'test'
- })(error);
- assert.strictEqual(socket, session[kSocket]);
- assert.strictEqual(session, session);
- }));
- session[kSocket].emit('error', new Error('test'));
-}));
-
-server.listen(0, common.mustCall(() => {
- const client = http2.connect(`http://localhost:${server.address().port}`);
- const req = client.request();
- req.resume();
- req.on('end', common.mustCall());
- req.on('close', common.mustCall(() => {
- client.destroy();
- server.close();
- }));
-}));
diff --git a/test/parallel/test-http2-server-stream-session-destroy.js b/test/parallel/test-http2-server-stream-session-destroy.js
index 24d064a448f87d..5eb04a8d376635 100644
--- a/test/parallel/test-http2-server-stream-session-destroy.js
+++ b/test/parallel/test-http2-server-stream-session-destroy.js
@@ -8,56 +8,41 @@ const h2 = require('http2');
const server = h2.createServer();
-server.on(
- 'stream',
- common.mustCall((stream) => {
- stream.session.destroy();
-
- // Test that stream.state getter returns an empty object
- // when the stream session has been destroyed
- assert.deepStrictEqual({}, stream.state);
-
- // Test that ERR_HTTP2_INVALID_STREAM is thrown while calling
- // stream operations after the stream session has been destroyed
- const invalidStreamError = {
- type: Error,
- code: 'ERR_HTTP2_INVALID_STREAM',
- message: 'The stream has been destroyed'
- };
- common.expectsError(() => stream.additionalHeaders(), invalidStreamError);
- common.expectsError(() => stream.priority(), invalidStreamError);
- common.expectsError(
- () => stream.pushStream({}, common.mustNotCall()),
- invalidStreamError
- );
- common.expectsError(() => stream.respond(), invalidStreamError);
- common.expectsError(() => stream.write('data'), invalidStreamError);
-
- // Test that ERR_HTTP2_INVALID_SESSION is thrown while calling
- // session operations after the stream session has been destroyed
- const invalidSessionError = {
- type: Error,
- code: 'ERR_HTTP2_INVALID_SESSION',
- message: 'The session has been destroyed'
- };
- common.expectsError(() => stream.session.settings(), invalidSessionError);
- common.expectsError(() => stream.session.shutdown(), invalidSessionError);
-
- // Wait for setImmediate call from destroy() to complete
- // so that state.destroyed is set to true
- setImmediate((session) => {
- common.expectsError(() => session.settings(), invalidSessionError);
- common.expectsError(() => session.shutdown(), invalidSessionError);
- }, stream.session);
- })
-);
-
-server.listen(
- 0,
- common.mustCall(() => {
- const client = h2.connect(`http://localhost:${server.address().port}`);
- const req = client.request();
- req.resume();
- req.on('end', common.mustCall(() => server.close()));
- })
-);
+server.on('stream', common.mustCall((stream) => {
+ assert(stream.session);
+ stream.session.destroy();
+ assert.strictEqual(stream.session, undefined);
+
+ // Test that stream.state getter returns an empty object
+ // when the stream session has been destroyed
+ assert.deepStrictEqual({}, stream.state);
+
+ // Test that ERR_HTTP2_INVALID_STREAM is thrown while calling
+ // stream operations after the stream session has been destroyed
+ const invalidStreamError = {
+ type: Error,
+ code: 'ERR_HTTP2_INVALID_STREAM',
+ message: 'The stream has been destroyed'
+ };
+ common.expectsError(() => stream.additionalHeaders(), invalidStreamError);
+ common.expectsError(() => stream.priority(), invalidStreamError);
+ common.expectsError(() => stream.respond(), invalidStreamError);
+ common.expectsError(
+ () => stream.pushStream({}, common.mustNotCall()),
+ {
+ code: 'ERR_HTTP2_PUSH_DISABLED',
+ type: Error
+ }
+ );
+ assert.strictEqual(stream.write('data'), false);
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+ client.on('error', () => {});
+ const req = client.request();
+ req.resume();
+ req.on('end', common.mustCall());
+ req.on('close', common.mustCall(() => server.close()));
+ req.on('error', () => {});
+}));
diff --git a/test/parallel/test-http2-server-timeout.js b/test/parallel/test-http2-server-timeout.js
index 28ab6efb87f6c1..581a409ce9171d 100755
--- a/test/parallel/test-http2-server-timeout.js
+++ b/test/parallel/test-http2-server-timeout.js
@@ -9,7 +9,7 @@ const server = http2.createServer();
server.setTimeout(common.platformTimeout(1));
const onServerTimeout = common.mustCall((session) => {
- session.destroy();
+ session.close(() => session.destroy());
});
server.on('stream', common.mustNotCall());
@@ -18,10 +18,14 @@ server.once('timeout', onServerTimeout);
server.listen(0, common.mustCall(() => {
const url = `http://localhost:${server.address().port}`;
const client = http2.connect(url);
+ // Because of the timeout, an ECONRESET error may or may not happen here.
+ // Keep this as a non-op and do not use common.mustCall()
+ client.on('error', () => {});
client.on('close', common.mustCall(() => {
-
const client2 = http2.connect(url);
+ // Because of the timeout, an ECONRESET error may or may not happen here.
+ // Keep this as a non-op and do not use common.mustCall()
+ client2.on('error', () => {});
client2.on('close', common.mustCall(() => server.close()));
-
}));
}));
diff --git a/test/parallel/test-http2-session-gc-while-write-scheduled.js b/test/parallel/test-http2-session-gc-while-write-scheduled.js
new file mode 100644
index 00000000000000..bb23760cebf967
--- /dev/null
+++ b/test/parallel/test-http2-session-gc-while-write-scheduled.js
@@ -0,0 +1,32 @@
+// Flags: --expose-gc
+
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const http2 = require('http2');
+const makeDuplexPair = require('../common/duplexpair');
+
+// This tests that running garbage collection while an Http2Session has
+// a write *scheduled*, it will survive that garbage collection.
+
+{
+ // This creates a session and schedules a write (for the settings frame).
+ let client = http2.connect('http://localhost:80', {
+ createConnection: common.mustCall(() => makeDuplexPair().clientSide)
+ });
+
+ // First, wait for any nextTicks() and their responses
+ // from the `connect()` call to run.
+ tick(10, () => {
+ // This schedules a write.
+ client.settings(http2.getDefaultSettings());
+ client = null;
+ global.gc();
+ });
+}
+
+function tick(n, cb) {
+ if (n--) setImmediate(tick, n, cb);
+ else cb();
+}
diff --git a/test/parallel/test-http2-session-settings.js b/test/parallel/test-http2-session-settings.js
index 53ff44dd9ec2fe..75fcc1942104ac 100644
--- a/test/parallel/test-http2-session-settings.js
+++ b/test/parallel/test-http2-session-settings.js
@@ -68,71 +68,60 @@ server.listen(
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);
- })
- );
+ req.on('ready', 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
- });
- });
+ req.on('ready', common.mustCall(() => {
+ assert.doesNotThrow(() => {
+ client.settings({ maxHeaderListSize: 1 }, common.mustCall());
+ });
- // 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];
- common.expectsError(
- () => client.settings(settings),
- {
- type: RangeError,
- code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
- message: `Invalid value for setting "${i[0]}": ${i[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];
+ common.expectsError(
+ () => client.settings(settings),
+ {
+ type: RangeError,
+ code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
+ message: `Invalid value for setting "${i[0]}": ${i[1]}`
+ }
+ );
+ });
- // error checks for enablePush
- [1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
- common.expectsError(
- () => client.settings({ enablePush: i }),
- {
- type: TypeError,
- code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
- message: `Invalid value for setting "enablePush": ${i}`
- }
- );
- });
- })
- );
+ // error checks for enablePush
+ [1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
+ common.expectsError(
+ () => client.settings({ enablePush: i }),
+ {
+ type: TypeError,
+ code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
+ message: `Invalid value for setting "enablePush": ${i}`
+ }
+ );
+ });
+ }));
req.on('response', common.mustCall());
req.resume();
- req.on(
- 'end',
- common.mustCall(() => {
- server.close();
- client.destroy();
- })
- );
+ req.on('end', common.mustCall(() => {
+ server.close();
+ client.close();
+ }));
req.end();
})
);
diff --git a/test/parallel/test-http2-session-stream-state.js b/test/parallel/test-http2-session-stream-state.js
index 9bbac3f482cbcf..612feb8cf1e2ca 100644
--- a/test/parallel/test-http2-session-stream-state.js
+++ b/test/parallel/test-http2-session-stream-state.js
@@ -57,7 +57,7 @@ server.on('listening', common.mustCall(() => {
const req = client.request(headers);
// State will only be valid after connect event is emitted
- req.on('connect', common.mustCall(() => {
+ req.on('ready', common.mustCall(() => {
// Test Stream State.
{
@@ -91,7 +91,7 @@ server.on('listening', common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
diff --git a/test/parallel/test-http2-session-unref.js b/test/parallel/test-http2-session-unref.js
new file mode 100644
index 00000000000000..e765352cdc615d
--- /dev/null
+++ b/test/parallel/test-http2-session-unref.js
@@ -0,0 +1,53 @@
+'use strict';
+// Flags: --expose-internals
+
+// Tests that calling unref() on Http2Session:
+// (1) Prevents it from keeping the process alive
+// (2) Doesn't crash
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const http2 = require('http2');
+const makeDuplexPair = require('../common/duplexpair');
+
+const server = http2.createServer();
+const { clientSide, serverSide } = makeDuplexPair();
+
+// 'session' event should be emitted 3 times:
+// - the vanilla client
+// - the destroyed client
+// - manual 'connection' event emission with generic Duplex stream
+server.on('session', common.mustCallAtLeast((session) => {
+ session.unref();
+}, 3));
+
+server.listen(0, common.mustCall(() => {
+ const port = server.address().port;
+
+ // unref new client
+ {
+ const client = http2.connect(`http://localhost:${port}`);
+ client.unref();
+ }
+
+ // unref destroyed client
+ {
+ const client = http2.connect(`http://localhost:${port}`);
+ client.destroy();
+ client.unref();
+ }
+
+ // unref destroyed client
+ {
+ const client = http2.connect(`http://localhost:${port}`, {
+ createConnection: common.mustCall(() => clientSide)
+ });
+ client.destroy();
+ client.unref();
+ }
+}));
+server.emit('connection', serverSide);
+server.unref();
+
+setTimeout(common.mustNotCall(() => {}), 1000).unref();
diff --git a/test/parallel/test-http2-settings-unsolicited-ack.js b/test/parallel/test-http2-settings-unsolicited-ack.js
new file mode 100644
index 00000000000000..fa63e9ee3f6425
--- /dev/null
+++ b/test/parallel/test-http2-settings-unsolicited-ack.js
@@ -0,0 +1,50 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const http2 = require('http2');
+const net = require('net');
+const http2util = require('../common/http2');
+const Countdown = require('../common/countdown');
+
+// Test that an unsolicited settings ack is ignored.
+
+const kSettings = new http2util.SettingsFrame();
+const kSettingsAck = new http2util.SettingsFrame(true);
+
+const server = http2.createServer();
+let client;
+
+const countdown = new Countdown(3, () => {
+ client.destroy();
+ server.close();
+});
+
+server.on('stream', common.mustNotCall());
+server.on('session', common.mustCall((session) => {
+ session.on('remoteSettings', common.mustCall(() => countdown.dec()));
+}));
+
+server.listen(0, common.mustCall(() => {
+ client = net.connect(server.address().port);
+
+ // Ensures that the clients settings frames are not sent until the
+ // servers are received, so that the first ack is actually expected.
+ client.once('data', (chunk) => {
+ // The very first chunk of data we get from the server should
+ // be a settings frame.
+ assert.deepStrictEqual(chunk.slice(0, 9), kSettings.data);
+ // The first ack is expected.
+ client.write(kSettingsAck.data, () => countdown.dec());
+ // The second one is not and will be ignored.
+ client.write(kSettingsAck.data, () => countdown.dec());
+ });
+
+ client.on('connect', common.mustCall(() => {
+ client.write(http2util.kClientMagic);
+ client.write(kSettings.data);
+ }));
+}));
diff --git a/test/parallel/test-http2-shutdown-errors.js b/test/parallel/test-http2-shutdown-errors.js
deleted file mode 100644
index 30bdb7a986d898..00000000000000
--- a/test/parallel/test-http2-shutdown-errors.js
+++ /dev/null
@@ -1,75 +0,0 @@
-'use strict';
-
-const common = require('../common');
-if (!common.hasCrypto)
- common.skip('missing crypto');
-const http2 = require('http2');
-const {
- constants,
- Http2Session,
- nghttp2ErrorString
-} = process.binding('http2');
-
-// tests error handling within shutdown
-// - should emit ERR_HTTP2_ERROR on session for all errors
-
-const tests = Object.getOwnPropertyNames(constants)
- .filter((key) => (
- key.indexOf('NGHTTP2_ERR') === 0
- ))
- .map((key) => ({
- ngError: constants[key],
- error: {
- code: 'ERR_HTTP2_ERROR',
- type: Error,
- message: nghttp2ErrorString(constants[key])
- }
- }));
-
-let currentError;
-
-// mock submitGoaway because we only care about testing error handling
-Http2Session.prototype.goaway = () => currentError.ngError;
-
-const server = http2.createServer();
-server.on('stream', common.mustCall((stream, headers) => {
- const errorMustCall = common.expectsError(currentError.error);
- const errorMustNotCall = common.mustNotCall(
- `${currentError.error.code} should emit on session`
- );
-
- stream.session.once('error', errorMustCall);
- stream.on('error', errorMustNotCall);
-
- stream.session.shutdown();
-}, tests.length));
-
-server.listen(0, common.mustCall(() => runTest(tests.shift())));
-
-function runTest(test) {
- const port = server.address().port;
- const url = `http://localhost:${port}`;
- const headers = {
- ':path': '/',
- ':method': 'POST',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- };
-
- const client = http2.connect(url);
- const req = client.request(headers);
-
- currentError = test;
- req.resume();
- req.end();
-
- req.on('end', common.mustCall(() => {
- client.destroy();
-
- if (!tests.length) {
- server.close();
- } else {
- runTest(tests.shift());
- }
- }));
-}
diff --git a/test/parallel/test-http2-single-headers.js b/test/parallel/test-http2-single-headers.js
index bb2f57cba1a939..c545b065015050 100644
--- a/test/parallel/test-http2-single-headers.js
+++ b/test/parallel/test-http2-single-headers.js
@@ -26,35 +26,26 @@ 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));
+ common.expectsError(
+ () => client.request({ [i]: 'abc', [i.toUpperCase()]: 'xyz' }),
+ {
+ code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
+ type: Error,
+ message: `Header field "${i}" must have only a single value`
+ }
+ );
+
+ common.expectsError(
+ () => client.request({ [i]: ['abc', 'xyz'] }),
+ {
+ code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
+ type: Error,
+ message: `Header field "${i}" must have only a single value`
+ }
+ );
});
+ server.close();
+ client.close();
}));
diff --git a/test/parallel/test-http2-socket-proxy.js b/test/parallel/test-http2-socket-proxy.js
index 3b15c18628d218..2d90ef7e952a55 100644
--- a/test/parallel/test-http2-socket-proxy.js
+++ b/test/parallel/test-http2-socket-proxy.js
@@ -57,7 +57,7 @@ server.on('stream', common.mustCall(function(stream, headers) {
assert.strictEqual(socket.writable, 0);
assert.strictEqual(socket.readable, 0);
- stream.session.destroy();
+ stream.end();
socket.setTimeout = undefined;
assert.strictEqual(session.setTimeout, undefined);
@@ -71,18 +71,11 @@ server.listen(0, common.mustCall(function() {
const port = server.address().port;
const url = `http://localhost:${port}`;
const client = h2.connect(url, common.mustCall(() => {
- const headers = {
- ':path': '/',
- ':method': 'GET',
- ':scheme': 'http',
- ':authority': `localhost:${port}`
- };
- const request = client.request(headers);
+ const request = client.request();
request.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
- request.end();
request.resume();
}));
}));
diff --git a/test/parallel/test-http2-status-code-invalid.js b/test/parallel/test-http2-status-code-invalid.js
index 3a0d882dea19da..3337aad32d7f70 100644
--- a/test/parallel/test-http2-status-code-invalid.js
+++ b/test/parallel/test-http2-status-code-invalid.js
@@ -36,6 +36,6 @@ server.listen(0, common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
}));
diff --git a/test/parallel/test-http2-status-code.js b/test/parallel/test-http2-status-code.js
index e8f64da368a5f5..d3642b4ff0217f 100644
--- a/test/parallel/test-http2-status-code.js
+++ b/test/parallel/test-http2-status-code.js
@@ -22,7 +22,7 @@ server.listen(0, common.mustCall(() => {
let remaining = codes.length;
function maybeClose() {
if (--remaining === 0) {
- client.destroy();
+ client.close();
server.close();
}
}
diff --git a/test/parallel/test-http2-stream-client.js b/test/parallel/test-http2-stream-client.js
index aa722c5ff2b6d9..3e6c6b2a8a1b5e 100644
--- a/test/parallel/test-http2-stream-client.js
+++ b/test/parallel/test-http2-stream-client.js
@@ -22,7 +22,7 @@ server.listen(0, common.mustCall(() => {
const req = client.request();
req.resume();
req.on('close', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-stream-destroy-event-order.js b/test/parallel/test-http2-stream-destroy-event-order.js
index c375bd80b38e22..e2732830efbd8a 100644
--- a/test/parallel/test-http2-stream-destroy-event-order.js
+++ b/test/parallel/test-http2-stream-destroy-event-order.js
@@ -3,26 +3,27 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
-const assert = require('assert');
const http2 = require('http2');
let client;
let req;
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
- stream.on('error', common.mustCall(() => {
- stream.on('close', common.mustCall((code) => {
- assert.strictEqual(code, 2);
- client.destroy();
+ stream.on('close', common.mustCall(() => {
+ stream.on('error', common.mustCall(() => {
server.close();
}));
}));
- req.rstStream(2);
+ req.close(2);
}));
server.listen(0, common.mustCall(() => {
client = http2.connect(`http://localhost:${server.address().port}`);
req = client.request();
req.resume();
- req.on('error', common.mustCall());
+ req.on('close', common.mustCall(() => {
+ req.on('error', common.mustCall(() => {
+ client.close();
+ }));
+ }));
}));
diff --git a/test/parallel/test-http2-timeouts.js b/test/parallel/test-http2-timeouts.js
index e3163a3dc8d927..083dcaf40c10ad 100644
--- a/test/parallel/test-http2-timeouts.js
+++ b/test/parallel/test-http2-timeouts.js
@@ -51,7 +51,7 @@ server.on('listening', common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end();
}));
diff --git a/test/parallel/test-http2-too-large-headers.js b/test/parallel/test-http2-too-large-headers.js
index f7ac25170b846f..7a7082736160f8 100644
--- a/test/parallel/test-http2-too-large-headers.js
+++ b/test/parallel/test-http2-too-large-headers.js
@@ -25,7 +25,7 @@ server.listen(0, common.mustCall(() => {
req.on('close', common.mustCall((code) => {
assert.strictEqual(code, NGHTTP2_ENHANCE_YOUR_CALM);
server.close();
- client.destroy();
+ client.close();
}));
});
diff --git a/test/parallel/test-http2-too-many-headers.js b/test/parallel/test-http2-too-many-headers.js
index eff0fa9c351c32..f05511cff657e0 100644
--- a/test/parallel/test-http2-too-many-headers.js
+++ b/test/parallel/test-http2-too-many-headers.js
@@ -28,7 +28,7 @@ server.listen(0, common.mustCall(() => {
req.on('close', common.mustCall((code) => {
assert.strictEqual(code, NGHTTP2_ENHANCE_YOUR_CALM);
server.close();
- client.destroy();
+ client.close();
}));
}));
diff --git a/test/parallel/test-http2-too-many-settings.js b/test/parallel/test-http2-too-many-settings.js
index 9b685fa8ceb16f..0302fe623da07c 100644
--- a/test/parallel/test-http2-too-many-settings.js
+++ b/test/parallel/test-http2-too-many-settings.js
@@ -1,7 +1,7 @@
'use strict';
// Tests that attempting to send too many non-acknowledged
-// settings frames will result in a throw.
+// settings frames will result in an error
const common = require('../common');
if (!common.hasCrypto)
@@ -9,53 +9,41 @@ if (!common.hasCrypto)
const assert = require('assert');
const h2 = require('http2');
-const maxPendingAck = 2;
-const server = h2.createServer({ maxPendingAck: maxPendingAck + 1 });
-
-let clients = 2;
+const maxOutstandingSettings = 2;
function doTest(session) {
- for (let n = 0; n < maxPendingAck; n++)
- assert.doesNotThrow(() => session.settings({ enablePush: false }));
- common.expectsError(() => session.settings({ enablePush: false }),
- {
- code: 'ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
- type: Error
- });
+ session.on('error', common.expectsError({
+ code: 'ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
+ type: Error
+ }));
+ for (let n = 0; n < maxOutstandingSettings; n++) {
+ session.settings({ enablePush: false });
+ assert.strictEqual(session.pendingSettingsAck, true);
+ }
}
-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)));
-}));
+{
+ const server = h2.createServer({ maxOutstandingSettings });
+ server.on('stream', common.mustNotCall());
+ server.once('session', common.mustCall((session) => doTest(session)));
+
+ server.listen(0, common.mustCall(() => {
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+ // On some operating systems, an ECONNRESET error may be emitted.
+ // On others it won't be. Do not make this a mustCall
+ client.on('error', () => {});
+ client.on('close', common.mustCall(() => server.close()));
+ }));
+}
-// Setting maxPendingAck to 0, defaults it to 1
-server.on('listening', common.mustCall(() => {
- const client = h2.connect(`http://localhost:${server.address().port}`,
- { maxPendingAck: 0 });
+{
+ const server = h2.createServer();
+ server.on('stream', common.mustNotCall());
- client.on('close', closeServer);
- client.on('localSettings', common.mustCall(() => {
- client.destroy();
+ server.listen(0, common.mustCall(() => {
+ const client = h2.connect(`http://localhost:${server.address().port}`,
+ { maxOutstandingSettings });
+ client.on('connect', () => doTest(client));
+ client.on('close', () => server.close());
}));
-}));
+}
diff --git a/test/parallel/test-http2-too-many-streams.js b/test/parallel/test-http2-too-many-streams.js
new file mode 100644
index 00000000000000..a4a67befa0f50a
--- /dev/null
+++ b/test/parallel/test-http2-too-many-streams.js
@@ -0,0 +1,60 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const Countdown = require('../common/countdown');
+const http2 = require('http2');
+const assert = require('assert');
+
+// Test that the maxConcurrentStreams setting is strictly enforced
+
+const server = http2.createServer({ settings: { maxConcurrentStreams: 1 } });
+
+let c = 0;
+
+server.on('stream', common.mustCall((stream) => {
+ // Because we only allow one open stream at a time,
+ // c should never be greater than 1.
+ assert.strictEqual(++c, 1);
+ stream.respond();
+ // Force some asynchronos stuff.
+ setImmediate(() => {
+ stream.end('ok');
+ assert.strictEqual(--c, 0);
+ });
+}, 3));
+
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+
+ const countdown = new Countdown(3, common.mustCall(() => {
+ server.close();
+ client.destroy();
+ }));
+
+ client.on('remoteSettings', common.mustCall(() => {
+ assert.strictEqual(client.remoteSettings.maxConcurrentStreams, 1);
+
+ {
+ const req = client.request();
+ req.resume();
+ req.on('close', () => {
+ countdown.dec();
+
+ setImmediate(() => {
+ const req = client.request();
+ req.resume();
+ req.on('close', () => countdown.dec());
+ });
+ });
+ }
+
+ {
+ const req = client.request();
+ req.resume();
+ req.on('close', () => countdown.dec());
+ }
+ }));
+}));
diff --git a/test/parallel/test-http2-trailers.js b/test/parallel/test-http2-trailers.js
index 5e0db6a30c3db0..1ca5bdf70d05b0 100644
--- a/test/parallel/test-http2-trailers.js
+++ b/test/parallel/test-http2-trailers.js
@@ -45,7 +45,7 @@ server.on('listening', common.mustCall(function() {
}));
req.on('end', common.mustCall(() => {
server.close();
- client.destroy();
+ client.close();
}));
req.end('data');
diff --git a/test/parallel/test-http2-util-update-options-buffer.js b/test/parallel/test-http2-util-update-options-buffer.js
index 14e4ba23d8fdca..6ab8bcff02866e 100644
--- a/test/parallel/test-http2-util-update-options-buffer.js
+++ b/test/parallel/test-http2-util-update-options-buffer.js
@@ -19,7 +19,9 @@ const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;
const IDX_OPTIONS_PADDING_STRATEGY = 4;
const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
-const IDX_OPTIONS_FLAGS = 7;
+const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
+const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
+const IDX_OPTIONS_FLAGS = 9;
{
updateOptionsBuffer({
@@ -29,7 +31,9 @@ const IDX_OPTIONS_FLAGS = 7;
peerMaxConcurrentStreams: 4,
paddingStrategy: 5,
maxHeaderListPairs: 6,
- maxOutstandingPings: 7
+ maxOutstandingPings: 7,
+ maxOutstandingSettings: 8,
+ maxSessionMemory: 9
});
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE], 1);
@@ -39,6 +43,8 @@ const IDX_OPTIONS_FLAGS = 7;
strictEqual(optionsBuffer[IDX_OPTIONS_PADDING_STRATEGY], 5);
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS], 6);
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS], 7);
+ strictEqual(optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS], 8);
+ strictEqual(optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY], 9);
const flags = optionsBuffer[IDX_OPTIONS_FLAGS];
@@ -49,6 +55,7 @@ const IDX_OPTIONS_FLAGS = 7;
ok(flags & (1 << IDX_OPTIONS_PADDING_STRATEGY));
ok(flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS));
ok(flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS));
+ ok(flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS));
}
{
diff --git a/test/parallel/test-http2-window-size.js b/test/parallel/test-http2-window-size.js
index 381416c0d23cc6..3d1c14de847e48 100644
--- a/test/parallel/test-http2-window-size.js
+++ b/test/parallel/test-http2-window-size.js
@@ -67,7 +67,7 @@ function run(buffers, initialWindowSize) {
const actualBuffer = Buffer.concat(responses);
assert.strictEqual(Buffer.compare(actualBuffer, expectedBuffer), 0);
// shut down
- client.destroy();
+ client.close();
server.close(() => {
resolve();
});
diff --git a/test/parallel/test-http2-write-callbacks.js b/test/parallel/test-http2-write-callbacks.js
index 44e33573a680b6..eca7f00ea7e292 100644
--- a/test/parallel/test-http2-write-callbacks.js
+++ b/test/parallel/test-http2-write-callbacks.js
@@ -31,7 +31,7 @@ server.listen(0, common.mustCall(() => {
req.on('data', (chunk) => actual += chunk);
req.on('end', common.mustCall(() => assert.strictEqual(actual, 'abcxyz')));
req.on('close', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/parallel/test-http2-write-empty-string.js b/test/parallel/test-http2-write-empty-string.js
index fea261917187da..6e6ce5254ddcfc 100644
--- a/test/parallel/test-http2-write-empty-string.js
+++ b/test/parallel/test-http2-write-empty-string.js
@@ -34,7 +34,7 @@ server.listen(0, common.mustCall(function() {
req.on('end', common.mustCall(function() {
assert.strictEqual('1\n2\n3\n', res);
- client.destroy();
+ client.close();
}));
req.end();
diff --git a/test/parallel/test-http2-zero-length-write.js b/test/parallel/test-http2-zero-length-write.js
index 899c28bace6f53..0b50715330a1c4 100644
--- a/test/parallel/test-http2-zero-length-write.js
+++ b/test/parallel/test-http2-zero-length-write.js
@@ -41,11 +41,12 @@ server.listen(0, common.mustCall(() => {
let actual = '';
const req = client.request({ ':method': 'POST' });
req.on('response', common.mustCall());
+ req.setEncoding('utf8');
req.on('data', (chunk) => actual += chunk);
req.on('end', common.mustCall(() => {
assert.strictEqual(actual, expect);
server.close();
- client.destroy();
+ client.close();
}));
getSrc().pipe(req);
}));
diff --git a/test/parallel/test-performance-gc.js b/test/parallel/test-performance-gc.js
index 89a9041c1c1159..ec0714ea50034d 100644
--- a/test/parallel/test-performance-gc.js
+++ b/test/parallel/test-performance-gc.js
@@ -51,3 +51,13 @@ const kinds = [
// Keep the event loop alive to witness the GC async callback happen.
setImmediate(() => setImmediate(() => 0));
}
+
+// GC should not keep the event loop alive
+{
+ let didCall = false;
+ process.on('beforeExit', () => {
+ assert(!didCall);
+ didCall = true;
+ global.gc();
+ });
+}
diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status
index cf4763b00129cc..b95db2a111ea67 100644
--- a/test/sequential/sequential.status
+++ b/test/sequential/sequential.status
@@ -11,6 +11,8 @@ test-inspector-async-call-stack : PASS, FLAKY
test-inspector-bindings : PASS, FLAKY
test-inspector-debug-end : PASS, FLAKY
test-inspector-async-hook-setup-at-signal: PASS, FLAKY
+test-http2-ping-flood : PASS, FLAKY
+test-http2-settings-flood : PASS, FLAKY
[$system==linux]
diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js
index bd2b3254f06594..5a6d4e0758adc1 100644
--- a/test/sequential/test-async-wrap-getasyncid.js
+++ b/test/sequential/test-async-wrap-getasyncid.js
@@ -25,6 +25,7 @@ const fixtures = require('../common/fixtures');
delete providers.HTTP2SESSION;
delete providers.HTTP2STREAM;
delete providers.HTTP2PING;
+ delete providers.HTTP2SETTINGS;
const obj_keys = Object.keys(providers);
if (obj_keys.length > 0)
diff --git a/test/sequential/test-http2-max-session-memory.js b/test/sequential/test-http2-max-session-memory.js
new file mode 100644
index 00000000000000..e16000d1261ab0
--- /dev/null
+++ b/test/sequential/test-http2-max-session-memory.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const http2 = require('http2');
+
+// Test that maxSessionMemory Caps work
+
+const largeBuffer = Buffer.alloc(1e6);
+
+const server = http2.createServer({ maxSessionMemory: 1 });
+
+server.on('stream', common.mustCall((stream) => {
+ stream.respond();
+ stream.end(largeBuffer);
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+
+ {
+ const req = client.request();
+
+ req.on('response', () => {
+ // This one should be rejected because the server is over budget
+ // on the current memory allocation
+ const req = client.request();
+ req.on('error', common.expectsError({
+ code: 'ERR_HTTP2_STREAM_ERROR',
+ type: Error,
+ message: 'Stream closed with error code 11'
+ }));
+ req.on('close', common.mustCall(() => {
+ server.close();
+ client.destroy();
+ }));
+ });
+
+ req.resume();
+ req.on('close', common.mustCall());
+ }
+}));
diff --git a/test/sequential/test-http2-ping-flood.js b/test/sequential/test-http2-ping-flood.js
new file mode 100644
index 00000000000000..5b47d51be9c5a8
--- /dev/null
+++ b/test/sequential/test-http2-ping-flood.js
@@ -0,0 +1,56 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const http2 = require('http2');
+const net = require('net');
+const http2util = require('../common/http2');
+
+// Test that ping flooding causes the session to be torn down
+
+const kSettings = new http2util.SettingsFrame();
+const kPing = new http2util.PingFrame();
+
+const server = http2.createServer();
+
+server.on('stream', common.mustNotCall());
+server.on('session', common.mustCall((session) => {
+ session.on('error', common.expectsError({
+ code: 'ERR_HTTP2_ERROR',
+ message:
+ 'Flooding was detected in this HTTP/2 session, and it must be closed'
+ }));
+ session.on('close', common.mustCall(() => {
+ server.close();
+ }));
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = net.connect(server.address().port);
+
+ // nghttp2 uses a limit of 10000 items in it's outbound queue.
+ // If this number is exceeded, a flooding error is raised. Set
+ // this lim higher to account for the ones that nghttp2 is
+ // successfully able to respond to.
+ // TODO(jasnell): Unfortunately, this test is inherently flaky because
+ // it is entirely dependent on how quickly the server is able to handle
+ // the inbound frames and whether those just happen to overflow nghttp2's
+ // outbound queue. The threshold at which the flood error occurs can vary
+ // from one system to another, and from one test run to another.
+ client.on('connect', common.mustCall(() => {
+ client.write(http2util.kClientMagic, () => {
+ client.write(kSettings.data, () => {
+ for (let n = 0; n < 35000; n++)
+ client.write(kPing.data);
+ });
+ });
+ }));
+
+ // An error event may or may not be emitted, depending on operating system
+ // and timing. We do not really care if one is emitted here or not, as the
+ // error on the server side is what we are testing for. Do not make this
+ // a common.mustCall() and there's no need to check the error details.
+ client.on('error', () => {});
+}));
diff --git a/test/sequential/test-http2-session-timeout.js b/test/sequential/test-http2-session-timeout.js
index 7a401e90ea4bbc..fce4570563c584 100644
--- a/test/sequential/test-http2-session-timeout.js
+++ b/test/sequential/test-http2-session-timeout.js
@@ -38,7 +38,7 @@ server.listen(0, common.mustCall(() => {
setTimeout(() => makeReq(attempts - 1), callTimeout);
} else {
server.removeListener('timeout', mustNotCall);
- client.destroy();
+ client.close();
server.close();
}
});
diff --git a/test/sequential/test-http2-settings-flood.js b/test/sequential/test-http2-settings-flood.js
new file mode 100644
index 00000000000000..bad4cec9a8d509
--- /dev/null
+++ b/test/sequential/test-http2-settings-flood.js
@@ -0,0 +1,53 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const http2 = require('http2');
+const net = require('net');
+const http2util = require('../common/http2');
+
+// Test that settings flooding causes the session to be torn down
+
+const kSettings = new http2util.SettingsFrame();
+
+const server = http2.createServer();
+
+server.on('stream', common.mustNotCall());
+server.on('session', common.mustCall((session) => {
+ session.on('error', common.expectsError({
+ code: 'ERR_HTTP2_ERROR',
+ message:
+ 'Flooding was detected in this HTTP/2 session, and it must be closed'
+ }));
+ session.on('close', common.mustCall(() => {
+ server.close();
+ }));
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = net.connect(server.address().port);
+
+ // nghttp2 uses a limit of 10000 items in it's outbound queue.
+ // If this number is exceeded, a flooding error is raised. Set
+ // this lim higher to account for the ones that nghttp2 is
+ // successfully able to respond to.
+ // TODO(jasnell): Unfortunately, this test is inherently flaky because
+ // it is entirely dependent on how quickly the server is able to handle
+ // the inbound frames and whether those just happen to overflow nghttp2's
+ // outbound queue. The threshold at which the flood error occurs can vary
+ // from one system to another, and from one test run to another.
+ client.on('connect', common.mustCall(() => {
+ client.write(http2util.kClientMagic, () => {
+ for (let n = 0; n < 35000; n++)
+ client.write(kSettings.data);
+ });
+ }));
+
+ // An error event may or may not be emitted, depending on operating system
+ // and timing. We do not really care if one is emitted here or not, as the
+ // error on the server side is what we are testing for. Do not make this
+ // a common.mustCall() and there's no need to check the error details.
+ client.on('error', () => {});
+}));
diff --git a/test/sequential/test-http2-timeout-large-write-file.js b/test/sequential/test-http2-timeout-large-write-file.js
index 922e5f99668da2..e32f6037eef9e3 100644
--- a/test/sequential/test-http2-timeout-large-write-file.js
+++ b/test/sequential/test-http2-timeout-large-write-file.js
@@ -79,7 +79,7 @@ server.listen(0, common.mustCall(() => {
}
}, 1));
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
}));
diff --git a/test/sequential/test-http2-timeout-large-write.js b/test/sequential/test-http2-timeout-large-write.js
index f0a11b2e44469e..a15fb46af6d28a 100644
--- a/test/sequential/test-http2-timeout-large-write.js
+++ b/test/sequential/test-http2-timeout-large-write.js
@@ -78,7 +78,7 @@ server.listen(0, common.mustCall(() => {
}
}, 1));
req.on('end', common.mustCall(() => {
- client.destroy();
+ client.close();
server.close();
}));
}));