Skip to content

Commit 1b7ce1e

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

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
@@ -605,6 +550,82 @@ The `http2session.type` will be equal to
605550
server, and `http2.constants.NGHTTP2_SESSION_CLIENT` if the instance is a
606551
client.
607552

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

1674-
Returns a HTTP/2 client `Http2Session` instance.
1695+
Returns a `ClientHttp2Session` instance.
16751696

16761697
```js
16771698
const http2 = require('http2');
@@ -2806,6 +2827,8 @@ if the stream is closed.
28062827
[`http2.createServer()`]: #http2_http2_createserver_options_onrequesthandler
28072828
[`http2stream.pushStream()`]: #http2_http2stream_pushstream_headers_options_callback
28082829
[`net.Socket`]: net.html#net_class_net_socket
2830+
[`net.Socket.prototype.ref`]: net.html#net_socket_ref
2831+
[`net.Socket.prototype.unref`]: net.html#net_socket_unref
28092832
[`net.connect()`]: net.html#net_net_connect
28102833
[`request.socket.getPeerCertificate()`]: tls.html#tls_tlssocket_getpeercertificate_detailed
28112834
[`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
@@ -1133,7 +1133,9 @@ Socket.prototype.ref = function() {
11331133
return this;
11341134
}
11351135

1136-
this._handle.ref();
1136+
if (typeof this._handle.ref === 'function') {
1137+
this._handle.ref();
1138+
}
11371139

11381140
return this;
11391141
};
@@ -1145,7 +1147,9 @@ Socket.prototype.unref = function() {
11451147
return this;
11461148
}
11471149

1148-
this._handle.unref();
1150+
if (typeof this._handle.unref === 'function') {
1151+
this._handle.unref();
1152+
}
11491153

11501154
return this;
11511155
};
+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)