http2: cleanup Http2Stream/Http2Session destroy

PR-URL: https://github.com/nodejs/node/pull/17406
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>

This is a significant cleanup and refactoring of the
cleanup/close/destroy logic for Http2Stream and Http2Session.
There are significant changes here in the timing and ordering
of cleanup logic, JS apis. and various related necessary edits.
This commit is contained in:
James M Snell 2017-12-12 11:34:17 -08:00
parent 18ca0b6442
commit 0babd181a0
146 changed files with 2637 additions and 2691 deletions

View File

@ -810,6 +810,11 @@ 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.
<a id="ERR_HTTP2_ALREADY_SHUTDOWN"></a>
### ERR_HTTP2_ALREADY_SHUTDOWN
Occurs with multiple attempts to shutdown an HTTP/2 session.
<a id="ERR_HTTP2_CONNECT_AUTHORITY"></a>
### ERR_HTTP2_CONNECT_AUTHORITY
@ -833,6 +838,12 @@ forbidden.
A failure occurred sending an individual frame on the HTTP/2 session.
<a id="ERR_HTTP2_GOAWAY_SESSION"></a>
### ERR_HTTP2_GOAWAY_SESSION
New HTTP/2 Streams may not be opened after the `Http2Session` has received a
`GOAWAY` frame from the connected peer.
<a id="ERR_HTTP2_HEADER_REQUIRED"></a>
### ERR_HTTP2_HEADER_REQUIRED
@ -972,6 +983,11 @@ client.
An attempt was made to use the `Http2Stream.prototype.responseWithFile()` API to
send something other than a regular file.
<a id="ERR_HTTP2_SESSION_ERROR"></a>
### ERR_HTTP2_SESSION_ERROR
The `Http2Session` closed with a non-zero error code.
<a id="ERR_HTTP2_SOCKET_BOUND"></a>
### ERR_HTTP2_SOCKET_BOUND
@ -989,10 +1005,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).
<a id="ERR_HTTP2_STREAM_CLOSED"></a>
### ERR_HTTP2_STREAM_CLOSED
<a id="ERR_HTTP2_STREAM_CANCEL"></a>
### 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.
<a id="ERR_HTTP2_STREAM_ERROR"></a>
### ERR_HTTP2_STREAM_ERROR

View File

@ -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'
<!-- YAML
@ -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'
<!-- YAML
added: v8.4.0
-->
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'
<!-- YAML
added: v8.4.0
@ -302,16 +283,53 @@ session.setTimeout(2000);
session.on('timeout', () => { /** .. **/ });
```
#### http2session.destroy()
#### http2session.close([callback])
<!-- YAML
added: REPLACEME
-->
* `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
<!-- YAML
added: REPLACEME
-->
* Value: {boolean}
Will be `true` if this `Http2Session` instance has been closed, otherwise
`false`.
#### http2session.destroy([error,][code])
<!-- YAML
added: v8.4.0
-->
* `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` associatd with the
`Http2Session`, those will also be destroyed.
#### http2session.destroyed
<!-- YAML
added: v8.4.0
@ -322,6 +340,19 @@ added: v8.4.0
Will be `true` if this `Http2Session` instance has been destroyed and must no
longer be used, otherwise `false`.
#### http2session.goaway([code, [lastStreamID, [opaqueData]]])
<!-- YAML
added: REPLACEME
-->
* `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
<!-- YAML
added: v8.4.0
@ -449,6 +480,12 @@ the trailing header fields to send to the peer.
will be emitted if the `getTrailers` callback attempts to set such header
fields.
The the `:method` and `:path` pseudoheaders are not specified within `headers`,
they respectively default to:
* `:method` = `'GET'`
* `:path` = `/`
#### http2session.setTimeout(msecs, callback)
<!-- YAML
added: v8.4.0
@ -462,19 +499,18 @@ Used to set a callback function that is called when there is no activity on
the `Http2Session` after `msecs` milliseconds. The given `callback` is
registered as a listener on the `'timeout'` event.
#### http2session.shutdown(options[, callback])
#### http2session.close(options[, callback])
<!-- YAML
added: v8.4.0
-->
* `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,
@ -487,19 +523,16 @@ Attempts to shutdown 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 2<sup>32</sup>-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 2<sup>31</sup>-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());
```
@ -627,7 +660,7 @@ is not yet ready for use.
All [`Http2Stream`][] instances are destroyed either when:
* An `RST_STREAM` frame for the stream is received by the connected peer.
* The `http2stream.rstStream()` methods is called.
* The `http2stream.close()` method is called.
* The `http2stream.destroy()` or `http2session.destroy()` methods are called.
When an `Http2Stream` instance is destroyed, an attempt will be made to send an
@ -720,6 +753,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])
<!-- YAML
added: v8.4.0
-->
* 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
<!-- YAML
added: REPLACEME
-->
* Value: {boolean}
Set to `true` if the `Http2Stream` instance has been closed.
#### http2stream.destroyed
<!-- YAML
added: v8.4.0
@ -730,6 +786,16 @@ added: v8.4.0
Set to `true` if the `Http2Stream` instance has been destroyed and is no longer
usable.
#### http2stream.pending
<!-- YAML
added: REPLACEME
-->
* Value: {boolean}
Set to `true` if the `Http2Stream` instance has not yet been assigned a
numeric stream identifier.
#### http2stream.priority(options)
<!-- YAML
added: v8.4.0
@ -760,66 +826,9 @@ added: v8.4.0
Set to the `RST_STREAM` [error code][] reported when the `Http2Stream` is
destroyed after either receiving an `RST_STREAM` frame from the connected peer,
calling `http2stream.rstStream()`, or `http2stream.destroy()`. Will be
calling `http2stream.close()`, or `http2stream.destroy()`. Will be
`undefined` if the `Http2Stream` has not been closed.
#### http2stream.rstStream(code)
<!-- YAML
added: v8.4.0
-->
* 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()
<!-- YAML
added: v8.4.0
-->
* Returns: {undefined}
Shortcut for `http2stream.rstStream()` using error code `0x00` (No Error).
#### http2stream.rstWithProtocolError()
<!-- YAML
added: v8.4.0
-->
* Returns: {undefined}
Shortcut for `http2stream.rstStream()` using error code `0x01` (Protocol Error).
#### http2stream.rstWithCancel()
<!-- YAML
added: v8.4.0
-->
* Returns: {undefined}
Shortcut for `http2stream.rstStream()` using error code `0x08` (Cancel).
#### http2stream.rstWithRefuse()
<!-- YAML
added: v8.4.0
-->
* Returns: {undefined}
Shortcut for `http2stream.rstStream()` using error code `0x07` (Refused Stream).
#### http2stream.rstWithInternalError()
<!-- YAML
added: v8.4.0
-->
* Returns: {undefined}
Shortcut for `http2stream.rstStream()` using error code `0x02` (Internal Error).
#### http2stream.session
<!-- YAML
added: v8.4.0
@ -842,11 +851,11 @@ added: v8.4.0
```js
const http2 = require('http2');
const client = http2.connect('http://example.org:8000');
const { NGHTTP2_CANCEL } = http2.constants;
const req = client.request({ ':path': '/' });
// Cancel the stream if there's no activity after 5 seconds
req.setTimeout(5000, () => req.rstWithCancel());
req.setTimeout(5000, () => req.rstStream(NGHTTP2_CANCEL));
```
#### http2stream.state
@ -1264,16 +1273,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: 'socketError'
#### Event: 'checkContinue'
<!-- YAML
added: v8.5.0
-->
* `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.
#### Event: 'request'
<!-- YAML
added: v8.4.0
-->
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'
<!-- YAML
added: v8.4.0
-->
The `'session'` event is emitted when a new `Http2Session` is created by the
`Http2Server`.
#### Event: 'sessionError'
<!-- YAML
@ -1281,16 +1323,13 @@ added: v8.4.0
-->
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'
<!-- YAML
added: v8.5.0
-->
* `socket` {http2.ServerHttp2Stream}
If an `ServerHttp2Stream` emits an `'error'` event, it will be forwarded here.
The stream will already be destroyed when this event is triggered.
@ -1325,17 +1364,6 @@ server.on('stream', (stream, headers, flags) => {
});
```
#### Event: 'request'
<!-- YAML
added: v8.4.0
-->
* `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'
<!-- YAML
added: v8.4.0
@ -1344,28 +1372,6 @@ added: v8.4.0
The `'timeout'` event is emitted when there is no activity on the Server for
a given number of milliseconds set using `http2server.setTimeout()`.
#### Event: 'checkContinue'
<!-- YAML
added: v8.5.0
-->
* `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
<!-- YAML
added: v8.4.0
@ -1379,16 +1385,7 @@ added: v8.4.0
-->
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'
<!-- YAML
added: v8.4.0
-->
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'
<!-- YAML
@ -1682,7 +1679,7 @@ const client = http2.connect('https://localhost:1234');
/** use the client **/
client.destroy();
client.close();
```
### http2.constants
@ -1938,6 +1935,7 @@ An HTTP/2 CONNECT proxy:
```js
const http2 = require('http2');
const { NGHTTP2_REFUSED_STREAM } = http2.constants;
const net = require('net');
const { URL } = require('url');
@ -1945,7 +1943,7 @@ const proxy = http2.createServer();
proxy.on('stream', (stream, headers) => {
if (headers[':method'] !== 'CONNECT') {
// Only accept CONNECT requests
stream.rstWithRefused();
stream.close(NGHTTP2_REFUSED_STREAM);
return;
}
const auth = new URL(`tcp://${headers[':authority']}`);
@ -1957,7 +1955,7 @@ proxy.on('stream', (stream, headers) => {
stream.pipe(socket);
});
socket.on('error', (error) => {
stream.rstStream(http2.constants.NGHTTP2_CONNECT_ERROR);
stream.close(http2.constants.NGHTTP2_CONNECT_ERROR);
});
});
@ -1986,7 +1984,7 @@ req.setEncoding('utf8');
req.on('data', (chunk) => data += chunk);
req.on('end', () => {
console.log(`The server says: ${data}`);
client.destroy();
client.close();
});
req.end('Jane');
```

View File

@ -307,6 +307,8 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA',
'The encoded data was not valid for encoding %s');
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported');
E('ERR_FALSY_VALUE_REJECTION', 'Promise was rejected with falsy value');
E('ERR_HTTP2_ALREADY_SHUTDOWN',
'Http2Session is already shutdown or destroyed');
E('ERR_HTTP2_CONNECT_AUTHORITY',
':authority header is required for CONNECT requests');
E('ERR_HTTP2_CONNECT_PATH',
@ -321,6 +323,8 @@ E('ERR_HTTP2_FRAME_ERROR',
msg += ` with code ${code}`;
return msg;
});
E('ERR_HTTP2_GOAWAY_SESSION',
'New streams cannot be created after receiving a GOAWAY');
E('ERR_HTTP2_HEADERS_AFTER_RESPOND',
'Cannot specify additional headers after response initiated');
E('ERR_HTTP2_HEADERS_OBJECT', 'Headers must be an object');
@ -358,12 +362,13 @@ E('ERR_HTTP2_PING_LENGTH', 'HTTP2 ping payload must be 8 bytes');
E('ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED', 'Cannot set HTTP/2 pseudo-headers');
E('ERR_HTTP2_PUSH_DISABLED', 'HTTP/2 client has disabled push streams');
E('ERR_HTTP2_SEND_FILE', 'Only regular files can be sent');
E('ERR_HTTP2_SESSION_ERROR', 'Session closed with error code %s');
E('ERR_HTTP2_SOCKET_BOUND',
'The socket is already bound to an Http2Session');
E('ERR_HTTP2_STATUS_101',
'HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2');
E('ERR_HTTP2_STATUS_INVALID', 'Invalid status code: %s');
E('ERR_HTTP2_STREAM_CLOSED', 'The stream is already closed');
E('ERR_HTTP2_STREAM_CANCEL', 'The pending stream has been canceled');
E('ERR_HTTP2_STREAM_ERROR', 'Stream closed with error code %s');
E('ERR_HTTP2_STREAM_SELF_DEPENDENCY', 'A stream cannot depend on itself');
E('ERR_HTTP2_UNSUPPORTED_PROTOCOL', 'protocol "%s" is unsupported.');

View File

@ -126,14 +126,11 @@ function onStreamAbortedRequest() {
const request = this[kRequest];
if (request !== undefined && request[kState].closed === false) {
request.emit('aborted');
request.emit('close');
}
}
function onStreamAbortedResponse() {
const response = this[kResponse];
if (response !== undefined && response[kState].closed === false)
response.emit('close');
// non-op for now
}
function resumeStream(stream) {
@ -234,9 +231,7 @@ class Http2ServerRequest extends Readable {
stream.on('end', onStreamEnd);
stream.on('error', onStreamError);
stream.on('aborted', onStreamAbortedRequest);
const onfinish = this[kFinish].bind(this);
stream.on('close', onfinish);
stream.on('finish', onfinish);
stream.on('close', this[kFinish].bind(this));
this.on('pause', onRequestPause);
this.on('resume', onRequestResume);
}
@ -297,7 +292,7 @@ class Http2ServerRequest extends Readable {
state.didRead = true;
process.nextTick(resumeStream, this[kStream]);
} else {
this.emit('error', new errors.Error('ERR_HTTP2_STREAM_CLOSED'));
this.emit('error', new errors.Error('ERR_HTTP2_INVALID_STREAM'));
}
}
@ -345,6 +340,7 @@ class Http2ServerRequest extends Readable {
// dump it for compatibility with http1
if (!state.didRead && !this._readableState.resumeScheduled)
this.resume();
this.emit('close');
}
}
@ -366,9 +362,7 @@ class Http2ServerResponse extends Stream {
this.writable = true;
stream.on('drain', onStreamDrain);
stream.on('aborted', onStreamAbortedResponse);
const onfinish = this[kFinish].bind(this);
stream.on('close', onfinish);
stream.on('finish', onfinish);
stream.on('close', this[kFinish].bind(this));
}
// User land modules such as finalhandler just check truthiness of this
@ -520,7 +514,7 @@ class Http2ServerResponse extends Stream {
const state = this[kState];
if (state.closed)
throw new errors.Error('ERR_HTTP2_STREAM_CLOSED');
throw new errors.Error('ERR_HTTP2_INVALID_STREAM');
if (this[kStream].headersSent)
throw new errors.Error('ERR_HTTP2_HEADERS_SENT');
@ -550,7 +544,7 @@ class Http2ServerResponse extends Stream {
}
if (this[kState].closed) {
const err = new errors.Error('ERR_HTTP2_STREAM_CLOSED');
const err = new errors.Error('ERR_HTTP2_INVALID_STREAM');
if (typeof cb === 'function')
process.nextTick(cb, err);
else
@ -620,12 +614,15 @@ class Http2ServerResponse extends Stream {
if (typeof callback !== 'function')
throw new errors.TypeError('ERR_INVALID_CALLBACK');
if (this[kState].closed) {
process.nextTick(callback, new errors.Error('ERR_HTTP2_STREAM_CLOSED'));
process.nextTick(callback, new errors.Error('ERR_HTTP2_INVALID_STREAM'));
return;
}
this[kStream].pushStream(headers, {}, function(stream, headers, options) {
const response = new Http2ServerResponse(stream);
callback(null, response);
this[kStream].pushStream(headers, {}, (err, stream, headers, options) => {
if (err) {
callback(err);
return;
}
callback(null, new Http2ServerResponse(stream));
});
}
@ -649,6 +646,7 @@ class Http2ServerResponse extends Stream {
this[kProxySocket] = null;
stream[kResponse] = undefined;
this.emit('finish');
this.emit('close');
}
// TODO doesn't support callbacks

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,13 @@ void inline debug_vfprintf(const char* format, ...) {
#define DEBUG_HTTP2STREAM2(...) do {} while (0)
#endif
// We strictly limit the number of outstanding unacknowledged PINGS a user
// may send in order to prevent abuse. The current default cap is 10. The
// user may set a different limit using a per Http2Session configuration
// option.
#define DEFAULT_MAX_PINGS 10
// These are the standard HTTP/2 defaults as specified by the RFC
#define DEFAULT_SETTINGS_HEADER_TABLE_SIZE 4096
#define DEFAULT_SETTINGS_ENABLE_PUSH 1
#define DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE 65535
@ -83,10 +89,10 @@ void inline debug_vfprintf(const char* format, ...) {
#define MAX_MAX_HEADER_LIST_SIZE 16777215u
#define DEFAULT_MAX_HEADER_LIST_PAIRS 128u
struct nghttp2_stream_write_t;
#define MAX_BUFFER_COUNT 16
struct nghttp2_stream_write_t;
enum nghttp2_session_type {
NGHTTP2_SESSION_SERVER,
NGHTTP2_SESSION_CLIENT
@ -109,11 +115,15 @@ enum nghttp2_stream_flags {
// Stream is destroyed
NGHTTP2_STREAM_FLAG_DESTROYED = 0x10,
// Stream has trailers
NGHTTP2_STREAM_FLAG_TRAILERS = 0x20
NGHTTP2_STREAM_FLAG_TRAILERS = 0x20,
// Stream has received all the data it can
NGHTTP2_STREAM_FLAG_EOS = 0x40
};
enum nghttp2_stream_options {
// Stream is not going to have any DATA frames
STREAM_OPTION_EMPTY_PAYLOAD = 0x1,
// Stream might have trailing headers
STREAM_OPTION_GET_TRAILERS = 0x2,
};
@ -136,7 +146,6 @@ struct nghttp2_header {
};
struct nghttp2_stream_write_t {
void* data;
int status;
@ -417,9 +426,10 @@ const char* nghttp2_errname(int rv) {
enum session_state_flags {
SESSION_STATE_NONE = 0x0,
SESSION_STATE_DESTROYING = 0x1,
SESSION_STATE_HAS_SCOPE = 0x2,
SESSION_STATE_WRITE_SCHEDULED = 0x4
SESSION_STATE_HAS_SCOPE = 0x1,
SESSION_STATE_WRITE_SCHEDULED = 0x2,
SESSION_STATE_CLOSED = 0x4,
SESSION_STATE_SENDING = 0x8,
};
// This allows for 4 default-sized frames with their frame headers
@ -555,6 +565,8 @@ class Http2Stream : public AsyncWrap,
unsigned int nbufs,
nghttp2_stream_write_cb cb);
inline bool HasDataChunks(bool ignore_eos = false);
inline void AddChunk(const uint8_t* data, size_t len);
inline void FlushDataChunks();
@ -592,7 +604,7 @@ class Http2Stream : public AsyncWrap,
bool silent = false);
// Submits an RST_STREAM frame using the given code
inline int SubmitRstStream(const uint32_t code);
inline void SubmitRstStream(const uint32_t code);
// Submits a PUSH_PROMISE frame with this stream as the parent.
inline Http2Stream* SubmitPushPromise(
@ -799,9 +811,11 @@ class Http2Session : public AsyncWrap {
void Start();
void Stop();
void Close();
void Close(uint32_t code = NGHTTP2_NO_ERROR,
bool socket_closed = false);
void Consume(Local<External> external);
void Unconsume();
void Goaway(uint32_t code, int32_t lastStreamID, uint8_t* data, size_t len);
bool Ping(v8::Local<v8::Function> function);
@ -827,8 +841,9 @@ class Http2Session : public AsyncWrap {
inline const char* TypeName();
inline void MarkDestroying() { flags_ |= SESSION_STATE_DESTROYING; }
inline bool IsDestroying() { return flags_ & SESSION_STATE_DESTROYING; }
inline bool IsDestroyed() {
return (flags_ & SESSION_STATE_CLOSED) || session_ == nullptr;
}
// Schedule a write if nghttp2 indicates it wants to write to the socket.
void MaybeScheduleWrite();
@ -842,9 +857,6 @@ class Http2Session : public AsyncWrap {
// Removes a stream instance from this session
inline void RemoveStream(int32_t id);
// Sends a notice to the connected peer that the session is shutting down.
inline void SubmitShutdownNotice();
// Submits a SETTINGS frame to the connected peer.
inline void Settings(const nghttp2_settings_entry iv[], size_t niv);
@ -868,6 +880,7 @@ class Http2Session : public AsyncWrap {
const uv_buf_t* bufs,
uv_handle_type pending,
void* ctx);
static void OnStreamDestructImpl(void* ctx);
// The JavaScript API
static void New(const FunctionCallbackInfo<Value>& args);
@ -878,7 +891,6 @@ class Http2Session : public AsyncWrap {
static void Settings(const FunctionCallbackInfo<Value>& args);
static void Request(const FunctionCallbackInfo<Value>& args);
static void SetNextStreamID(const FunctionCallbackInfo<Value>& args);
static void ShutdownNotice(const FunctionCallbackInfo<Value>& args);
static void Goaway(const FunctionCallbackInfo<Value>& args);
static void UpdateChunksSent(const FunctionCallbackInfo<Value>& args);
static void RefreshState(const FunctionCallbackInfo<Value>& args);

View File

@ -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());
}));

View File

@ -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()
];
server.listen(0, common.mustCall(() => {
const destroyCallbacks = [
(client) => client.destroy(),
(client) => client[kSocket].destroy()
];
let remaining = destroyCallbacks.length;
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];
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();
}
})
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();
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);
}));
req.on('response', common.mustNotCall());
req.resume();
const sessionError = {
type: Error,
code: 'ERR_HTTP2_INVALID_SESSION',
message: 'The session has been 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);
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);
});
req.on(
'end',
common.mustCall(() => {
server.close();
})
);
req.end();
})
);
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();
}));
client.on(
'goaway',
common.mustCall(() => {
// We ought to be able to destroy the client in here without an error
server.close();
client.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.request();
})
);
client.on('close', () => {
server.close();
// calling destroy in here should not matter
client.destroy();
});
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', () => {});
}));
}

View File

@ -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()));
}));

View File

@ -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) {

View File

@ -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();

View File

@ -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();
}));

View File

@ -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();
}));
}));

View File

@ -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;
}
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}"`
}
);
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();
}));

View File

@ -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('response', common.mustNotCall());
req.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
type: Error,
message: 'Stream closed with error code 1'
}));
req.on('response', common.mustCall());
req.resume();
req.on('end', common.mustCall());
req.end();

View File

@ -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();
}));
};

View File

@ -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();
}));

View File

@ -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()));
}));

View File

@ -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);
}
// purposefully do not end()
}));
server.listen(0);
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();
}));

View File

@ -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());
}));

View File

@ -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)

View File

@ -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);
}));
}));

View File

@ -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();
}));

View File

@ -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();
}));
}));

View File

@ -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();
}));
}));

View File

@ -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();
}));
}));

View File

@ -39,7 +39,7 @@ function nextTest(testsToRun) {
}));
req.on('end', common.mustCall(() => {
client.destroy();
client.close();
nextTest(testsToRun - 1);
}));
}

View File

@ -33,7 +33,7 @@ function testMethodConnect(testsToRun) {
}));
req.resume();
req.on('end', common.mustCall(() => {
client.destroy();
client.close();
testMethodConnect(testsToRun - 1);
}));
req.end();

View File

@ -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();
}));
}));

View File

@ -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();

View File

@ -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();
}));
}));

View File

@ -35,7 +35,7 @@ server.listen(0, common.mustCall(() => {
function maybeClose() {
if (--remaining === 0) {
server.close();
client.destroy();
client.close();
}
}

View File

@ -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();

View File

@ -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');

View File

@ -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();

View File

@ -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();
}));
});

View File

@ -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();
}
}

View File

@ -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());
req.resume();
req.end();
const req2 = client.request({
':path': '/error',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
});
req2.on('response', common.mustNotCall());
req2.on('error', common.mustNotCall());
req2.on('end', common.mustCall());
req2.resume();
req2.end();
const req3 = client.request({
':path': '/error',
':method': 'GET',
':scheme': 'http',
':authority': `localhost:${port}`
});
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(() => {
const countdown = new Countdown(3, () => {
server.close();
client.destroy();
}));
client.close();
});
req3.resume();
req3.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 req = client.request({ ':path': '/error' });
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()));
req.resume();
req.on('end', common.mustCall());
}
{
const req = client.request({ ':path': '/error' });
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()));
req.resume();
req.on('end', common.mustCall());
}
}));

View File

@ -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();
}));
}));

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -68,7 +68,7 @@ server.listen(0, common.mustCall(() => {
}));
request.resume();
request.on('end', common.mustCall(() => {
client.destroy();
client.close();
server.close();
}));
}));

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();
}));

View File

@ -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)

View File

@ -54,7 +54,7 @@ server.on('listening', common.mustCall(() => {
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
req.end();

View File

@ -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);

View File

@ -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 =
'<html><head></head><body><h1>this is some data</h2></body></html>';
@ -23,21 +25,26 @@ 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(() => {
server.on('listening', common.mustCall(function() {
const client = h2.connect(`http://localhost:${server.address().port}`);
client.setMaxListeners(100);
const client = h2.connect(`http://localhost:${this.address().port}`);
client.on('goaway', console.log);
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 +58,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()));
}
}));

View File

@ -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
)
);
);
});

View File

@ -54,7 +54,7 @@ server.listen(0, common.mustCall(function() {
req.resume();
req.on('end', common.mustCall(function() {
client.destroy();
client.close();
testsFinished++;
if (testsFinished === testsToRun) {

View File

@ -24,6 +24,6 @@ server.listen(0, common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
}));

View File

@ -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();
}));
});
}));
}));
});

View File

@ -44,6 +44,6 @@ server.listen(0, common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
}));

View File

@ -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();

View File

@ -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();
});

View File

@ -54,6 +54,6 @@ server.listen(0, () => {
req.on('data', common.mustNotCall());
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
});

View File

@ -52,7 +52,7 @@ function onSession(session) {
strictEqual(alpnProtocol, 'h2');
strictEqual(httpVersion, '2.0');
session.destroy();
session.close();
this.cleanup();
}));
request.end();

View File

@ -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();

View File

@ -88,7 +88,7 @@ server.on('listening', common.mustCall(() => {
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
req.end();

View File

@ -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();
}));
}));

View File

@ -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'
}));
}
}));

View File

@ -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();

View File

@ -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);

View File

@ -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);
});

View File

@ -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();
}));

View File

@ -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,

View File

@ -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();
}));
}));

View File

@ -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();
}));
}));

View File

@ -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() {

View File

@ -25,7 +25,7 @@ server.listen(0, common.mustCall(() => {
const countdown = new Countdown(2, common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
{

View File

@ -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();
}));

View File

@ -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()));
}));

View File

@ -45,7 +45,7 @@ server.on('listening', common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
req.end();
}));

View File

@ -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();
}));
}));

View File

@ -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);
}));

View File

@ -54,7 +54,7 @@ server.on('listening', common.mustCall(() => {
req.resume();
req.on('end', common.mustCall(() => {
server.close();
client.destroy();
client.close();
}));
req.end();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();
}

View File

@ -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();

View File

@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More