Skip to content

Commit 72b42de

Browse files
kjinMylesBorins
authored andcommitted
http2: implement ref() and unref() on client sessions
Backport-PR-URL: #18050 Backport-PR-URL: #20456 PR-URL: #17620 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 55f6bdb commit 72b42de

File tree

4 files changed

+159
-67
lines changed

4 files changed

+159
-67
lines changed

doc/api/http2.md

+88-65
Original file line numberDiff line numberDiff line change
@@ -413,78 +413,23 @@ session.ping(Buffer.from('abcdefgh'), (err, duration, payload) => {
413413
If the `payload` argument is not specified, the default payload will be the
414414
64-bit timestamp (little endian) marking the start of the `PING` duration.
415415

416-
#### http2session.remoteSettings
416+
#### http2session.ref()
417417
<!-- YAML
418-
added: v8.4.0
418+
added: REPLACEME
419419
-->
420420

421-
* Value: {[Settings Object][]}
421+
Calls [`ref()`][`net.Socket.prototype.ref`] on this `Http2Session`
422+
instance's underlying [`net.Socket`].
422423

423-
A prototype-less object describing the current remote settings of this
424-
`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
425-
426-
#### http2session.request(headers[, options])
424+
#### http2session.remoteSettings
427425
<!-- YAML
428426
added: v8.4.0
429427
-->
430428

431-
* `headers` {[Headers Object][]}
432-
* `options` {Object}
433-
* `endStream` {boolean} `true` if the `Http2Stream` *writable* side should
434-
be closed initially, such as when sending a `GET` request that should not
435-
expect a payload body.
436-
* `exclusive` {boolean} When `true` and `parent` identifies a parent Stream,
437-
the created stream is made the sole direct dependency of the parent, with
438-
all other existing dependents made a dependent of the newly created stream.
439-
**Default:** `false`
440-
* `parent` {number} Specifies the numeric identifier of a stream the newly
441-
created stream is dependent on.
442-
* `weight` {number} Specifies the relative dependency of a stream in relation
443-
to other streams with the same `parent`. The value is a number between `1`
444-
and `256` (inclusive).
445-
* `getTrailers` {Function} Callback function invoked to collect trailer
446-
headers.
447-
448-
* Returns: {ClientHttp2Stream}
449-
450-
For HTTP/2 Client `Http2Session` instances only, the `http2session.request()`
451-
creates and returns an `Http2Stream` instance that can be used to send an
452-
HTTP/2 request to the connected server.
453-
454-
This method is only available if `http2session.type` is equal to
455-
`http2.constants.NGHTTP2_SESSION_CLIENT`.
456-
457-
```js
458-
const http2 = require('http2');
459-
const clientSession = http2.connect('https://localhost:1234');
460-
const {
461-
HTTP2_HEADER_PATH,
462-
HTTP2_HEADER_STATUS
463-
} = http2.constants;
464-
465-
const req = clientSession.request({ [HTTP2_HEADER_PATH]: '/' });
466-
req.on('response', (headers) => {
467-
console.log(headers[HTTP2_HEADER_STATUS]);
468-
req.on('data', (chunk) => { /** .. **/ });
469-
req.on('end', () => { /** .. **/ });
470-
});
471-
```
472-
473-
When set, the `options.getTrailers()` function is called immediately after
474-
queuing the last chunk of payload data to be sent. The callback is passed a
475-
single object (with a `null` prototype) that the listener may used to specify
476-
the trailing header fields to send to the peer.
477-
478-
*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
479-
"pseudo-header" fields (e.g. `':method'`, `':path'`, etc). An `'error'` event
480-
will be emitted if the `getTrailers` callback attempts to set such header
481-
fields.
482-
483-
The the `:method` and `:path` pseudoheaders are not specified within `headers`,
484-
they respectively default to:
429+
* Value: {[Settings Object][]}
485430

486-
* `:method` = `'GET'`
487-
* `:path` = `/`
431+
A prototype-less object describing the current remote settings of this
432+
`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
488433

489434
#### http2session.setTimeout(msecs, callback)
490435
<!-- YAML
@@ -617,6 +562,82 @@ The `http2session.type` will be equal to
617562
server, and `http2.constants.NGHTTP2_SESSION_CLIENT` if the instance is a
618563
client.
619564

565+
#### http2session.unref()
566+
<!-- YAML
567+
added: REPLACEME
568+
-->
569+
570+
Calls [`unref()`][`net.Socket.prototype.unref`] on this `Http2Session`
571+
instance's underlying [`net.Socket`].
572+
573+
### Class: ClientHttp2Session
574+
<!-- YAML
575+
added: v8.4.0
576+
-->
577+
578+
#### clienthttp2session.request(headers[, options])
579+
<!-- YAML
580+
added: v8.4.0
581+
-->
582+
583+
* `headers` {[Headers Object][]}
584+
* `options` {Object}
585+
* `endStream` {boolean} `true` if the `Http2Stream` *writable* side should
586+
be closed initially, such as when sending a `GET` request that should not
587+
expect a payload body.
588+
* `exclusive` {boolean} When `true` and `parent` identifies a parent Stream,
589+
the created stream is made the sole direct dependency of the parent, with
590+
all other existing dependents made a dependent of the newly created stream.
591+
**Default:** `false`
592+
* `parent` {number} Specifies the numeric identifier of a stream the newly
593+
created stream is dependent on.
594+
* `weight` {number} Specifies the relative dependency of a stream in relation
595+
to other streams with the same `parent`. The value is a number between `1`
596+
and `256` (inclusive).
597+
* `getTrailers` {Function} Callback function invoked to collect trailer
598+
headers.
599+
600+
* Returns: {ClientHttp2Stream}
601+
602+
For HTTP/2 Client `Http2Session` instances only, the `http2session.request()`
603+
creates and returns an `Http2Stream` instance that can be used to send an
604+
HTTP/2 request to the connected server.
605+
606+
This method is only available if `http2session.type` is equal to
607+
`http2.constants.NGHTTP2_SESSION_CLIENT`.
608+
609+
```js
610+
const http2 = require('http2');
611+
const clientSession = http2.connect('https://localhost:1234');
612+
const {
613+
HTTP2_HEADER_PATH,
614+
HTTP2_HEADER_STATUS
615+
} = http2.constants;
616+
617+
const req = clientSession.request({ [HTTP2_HEADER_PATH]: '/' });
618+
req.on('response', (headers) => {
619+
console.log(headers[HTTP2_HEADER_STATUS]);
620+
req.on('data', (chunk) => { /** .. **/ });
621+
req.on('end', () => { /** .. **/ });
622+
});
623+
```
624+
625+
When set, the `options.getTrailers()` function is called immediately after
626+
queuing the last chunk of payload data to be sent. The callback is passed a
627+
single object (with a `null` prototype) that the listener may used to specify
628+
the trailing header fields to send to the peer.
629+
630+
*Note*: The HTTP/1 specification forbids trailers from containing HTTP/2
631+
"pseudo-header" fields (e.g. `':method'`, `':path'`, etc). An `'error'` event
632+
will be emitted if the `getTrailers` callback attempts to set such header
633+
fields.
634+
635+
The `:method` and `:path` pseudoheaders are not specified within `headers`,
636+
they respectively default to:
637+
638+
* `:method` = `'GET'`
639+
* `:path` = `/`
640+
620641
### Class: Http2Stream
621642
<!-- YAML
622643
added: v8.4.0
@@ -1688,9 +1709,9 @@ changes:
16881709
[`Duplex`][] stream that is to be used as the connection for this session.
16891710
* ...: Any [`net.connect()`][] or [`tls.connect()`][] options can be provided.
16901711
* `listener` {Function}
1691-
* Returns: {Http2Session}
1712+
* Returns {ClientHttp2Session}
16921713

1693-
Returns a HTTP/2 client `Http2Session` instance.
1714+
Returns a `ClientHttp2Session` instance.
16941715

16951716
```js
16961717
const http2 = require('http2');
@@ -2825,6 +2846,8 @@ if the stream is closed.
28252846
[`http2.createServer()`]: #http2_http2_createserver_options_onrequesthandler
28262847
[`http2stream.pushStream()`]: #http2_http2stream_pushstream_headers_options_callback
28272848
[`net.Socket`]: net.html#net_class_net_socket
2849+
[`net.Socket.prototype.ref`]: net.html#net_socket_ref
2850+
[`net.Socket.prototype.unref`]: net.html#net_socket_unref
28282851
[`net.connect()`]: net.html#net_net_connect
28292852
[`request.socket.getPeerCertificate()`]: tls.html#tls_tlssocket_getpeercertificate_detailed
28302853
[`response.end()`]: #http2_response_end_data_encoding_callback

lib/internal/http2/core.js

+12
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,18 @@ class Http2Session extends EventEmitter {
11241124

11251125
process.nextTick(emit, this, 'timeout');
11261126
}
1127+
1128+
ref() {
1129+
if (this[kSocket]) {
1130+
this[kSocket].ref();
1131+
}
1132+
}
1133+
1134+
unref() {
1135+
if (this[kSocket]) {
1136+
this[kSocket].unref();
1137+
}
1138+
}
11271139
}
11281140

11291141
// ServerHttp2Session instances should never have to wait for the socket

lib/net.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,9 @@ Socket.prototype.ref = function() {
11251125
return this;
11261126
}
11271127

1128-
this._handle.ref();
1128+
if (typeof this._handle.ref === 'function') {
1129+
this._handle.ref();
1130+
}
11291131

11301132
return this;
11311133
};
@@ -1137,7 +1139,9 @@ Socket.prototype.unref = function() {
11371139
return this;
11381140
}
11391141

1140-
this._handle.unref();
1142+
if (typeof this._handle.unref === 'function') {
1143+
this._handle.unref();
1144+
}
11411145

11421146
return this;
11431147
};
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
// Flags: --expose-internals
3+
4+
// Tests that calling unref() on Http2Session:
5+
// (1) Prevents it from keeping the process alive
6+
// (2) Doesn't crash
7+
8+
const common = require('../common');
9+
if (!common.hasCrypto)
10+
common.skip('missing crypto');
11+
const http2 = require('http2');
12+
const makeDuplexPair = require('../common/duplexpair');
13+
14+
const server = http2.createServer();
15+
const { clientSide, serverSide } = makeDuplexPair();
16+
17+
// 'session' event should be emitted 3 times:
18+
// - the vanilla client
19+
// - the destroyed client
20+
// - manual 'connection' event emission with generic Duplex stream
21+
server.on('session', common.mustCallAtLeast((session) => {
22+
session.unref();
23+
}, 3));
24+
25+
server.listen(0, common.mustCall(() => {
26+
const port = server.address().port;
27+
28+
// unref new client
29+
{
30+
const client = http2.connect(`http://localhost:${port}`);
31+
client.unref();
32+
}
33+
34+
// unref destroyed client
35+
{
36+
const client = http2.connect(`http://localhost:${port}`);
37+
client.destroy();
38+
client.unref();
39+
}
40+
41+
// unref destroyed client
42+
{
43+
const client = http2.connect(`http://localhost:${port}`, {
44+
createConnection: common.mustCall(() => clientSide)
45+
});
46+
client.destroy();
47+
client.unref();
48+
}
49+
}));
50+
server.emit('connection', serverSide);
51+
server.unref();
52+
53+
setTimeout(common.mustNotCall(() => {}), 1000).unref();

0 commit comments

Comments
 (0)