Skip to content

Commit 1c313e0

Browse files
jasnellMylesBorins
authored andcommitted
http2: add initial support for originSet
Add new properties to `Http2Session` to identify alpnProtocol, and indicator about whether the session is TLS or not, and initial support for origin set (preparinng for `ORIGIN` frame support and the client-side `Pool` implementation. The `originSet` is the set of origins for which an `Http2Session` may be considered authoritative. Per the `ORIGIN` frame spec, the originSet is only valid on TLS connections, so this is only exposed when using a `TLSSocket`. Backport-PR-URL: #18050 Backport-PR-URL: #20456 PR-URL: #17935 Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Sebastiaan Deckers <sebdeckers83@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
1 parent 95c1e2d commit 1c313e0

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-2
lines changed

doc/api/http2.md

+35
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,18 @@ session.setTimeout(2000);
283283
session.on('timeout', () => { /** .. **/ });
284284
```
285285

286+
#### http2session.alpnProtocol
287+
<!-- YAML
288+
added: REPLACEME
289+
-->
290+
291+
* Value: {string|undefined}
292+
293+
Value will be `undefined` if the `Http2Session` is not yet connected to a
294+
socket, `h2c` if the `Http2Session` is not connected to a `TLSSocket`, or
295+
will return the value of the connected `TLSSocket`'s own `alpnProtocol`
296+
property.
297+
286298
#### http2session.close([callback])
287299
<!-- YAML
288300
added: REPLACEME
@@ -340,6 +352,18 @@ added: v8.4.0
340352
Will be `true` if this `Http2Session` instance has been destroyed and must no
341353
longer be used, otherwise `false`.
342354

355+
#### http2session.encrypted
356+
<!-- YAML
357+
added: REPLACEME
358+
-->
359+
360+
* Value: {boolean|undefined}
361+
362+
Value is `undefined` if the `Http2Session` session socket has not yet been
363+
connected, `true` if the `Http2Session` is connected with a `TLSSocket`,
364+
and `false` if the `Http2Session` is connected to any other kind of socket
365+
or stream.
366+
343367
#### http2session.goaway([code, [lastStreamID, [opaqueData]]])
344368
<!-- YAML
345369
added: REPLACEME
@@ -363,6 +387,17 @@ added: v8.4.0
363387
A prototype-less object describing the current local settings of this
364388
`Http2Session`. The local settings are local to *this* `Http2Session` instance.
365389

390+
#### http2session.originSet
391+
<!-- YAML
392+
added: REPLACEME
393+
-->
394+
395+
* Value: {string[]|undefined}
396+
397+
If the `Http2Session` is connected to a `TLSSocket`, the `originSet` property
398+
will return an Array of origins for which the `Http2Session` may be
399+
considered authoritative.
400+
366401
#### http2session.pendingSettingsAck
367402
<!-- YAML
368403
added: v8.4.0

lib/internal/http2/core.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ const TLSServer = tls.Server;
6868

6969
const kInspect = require('internal/util').customInspectSymbol;
7070

71+
const kAlpnProtocol = Symbol('alpnProtocol');
7172
const kAuthority = Symbol('authority');
73+
const kEncrypted = Symbol('encrypted');
7274
const kHandle = Symbol('handle');
7375
const kID = Symbol('id');
7476
const kInit = Symbol('init');
@@ -729,6 +731,17 @@ function setupHandle(socket, type, options) {
729731

730732
this[kHandle] = handle;
731733

734+
if (socket.encrypted) {
735+
this[kAlpnProtocol] = socket.alpnProtocol;
736+
this[kEncrypted] = true;
737+
} else {
738+
// 'h2c' is the protocol identifier for HTTP/2 over plain-text. We use
739+
// it here to identify any session that is not explicitly using an
740+
// encrypted socket.
741+
this[kAlpnProtocol] = 'h2c';
742+
this[kEncrypted] = false;
743+
}
744+
732745
const settings = typeof options.settings === 'object' ?
733746
options.settings : {};
734747

@@ -803,9 +816,12 @@ class Http2Session extends EventEmitter {
803816
streams: new Map(),
804817
pendingStreams: new Set(),
805818
pendingAck: 0,
806-
writeQueueSize: 0
819+
writeQueueSize: 0,
820+
originSet: undefined
807821
};
808822

823+
this[kEncrypted] = undefined;
824+
this[kAlpnProtocol] = undefined;
809825
this[kType] = type;
810826
this[kProxySocket] = null;
811827
this[kSocket] = socket;
@@ -830,6 +846,46 @@ class Http2Session extends EventEmitter {
830846
debug(`Http2Session ${sessionName(type)}: created`);
831847
}
832848

849+
// Returns undefined if the socket is not yet connected, true if the
850+
// socket is a TLSSocket, and false if it is not.
851+
get encrypted() {
852+
return this[kEncrypted];
853+
}
854+
855+
// Returns undefined if the socket is not yet connected, `h2` if the
856+
// socket is a TLSSocket and the alpnProtocol is `h2`, or `h2c` if the
857+
// socket is not a TLSSocket.
858+
get alpnProtocol() {
859+
return this[kAlpnProtocol];
860+
}
861+
862+
// TODO(jasnell): originSet is being added in preparation for ORIGIN frame
863+
// support. At the current time, the ORIGIN frame specification is awaiting
864+
// publication as an RFC and is awaiting implementation in nghttp2. Once
865+
// added, an ORIGIN frame will add to the origins included in the origin
866+
// set. 421 responses will remove origins from the set.
867+
get originSet() {
868+
if (!this.encrypted || this.destroyed)
869+
return undefined;
870+
871+
let originSet = this[kState].originSet;
872+
if (originSet === undefined) {
873+
const socket = this[kSocket];
874+
this[kState].originSet = originSet = new Set();
875+
if (socket.servername != null) {
876+
let originString = `https://${socket.servername}`;
877+
if (socket.remotePort != null)
878+
originString += `:${socket.remotePort}`;
879+
// We have to ensure that it is a properly serialized
880+
// ASCII origin string. The socket.servername might not
881+
// be properly ASCII encoded.
882+
originSet.add((new URL(originString)).origin);
883+
}
884+
}
885+
886+
return Array.from(originSet);
887+
}
888+
833889
// True if the Http2Session is still waiting for the socket to connect
834890
get connecting() {
835891
return (this[kState].flags & SESSION_FLAGS_READY) === 0;

test/parallel/test-http2-create-client-secure-session.js

+19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ function loadKey(keyname) {
1919

2020
function onStream(stream, headers) {
2121
const socket = stream.session[kSocket];
22+
23+
assert(stream.session.encrypted);
24+
assert(stream.session.alpnProtocol, 'h2');
25+
const originSet = stream.session.originSet;
26+
assert(Array.isArray(originSet));
27+
assert.strictEqual(originSet[0],
28+
`https://${socket.servername}:${socket.remotePort}`);
29+
2230
assert(headers[':authority'].startsWith(socket.servername));
2331
stream.respond({ 'content-type': 'application/json' });
2432
stream.end(JSON.stringify({
@@ -39,6 +47,17 @@ function verifySecureSession(key, cert, ca, opts) {
3947
assert.strictEqual(client.socket.listenerCount('secureConnect'), 1);
4048
const req = client.request();
4149

50+
client.on('connect', common.mustCall(() => {
51+
assert(client.encrypted);
52+
assert.strictEqual(client.alpnProtocol, 'h2');
53+
const originSet = client.originSet;
54+
assert(Array.isArray(originSet));
55+
assert.strictEqual(originSet.length, 1);
56+
assert.strictEqual(
57+
originSet[0],
58+
`https://${opts.servername || 'localhost'}:${server.address().port}`);
59+
}));
60+
4261
req.on('response', common.mustCall((headers) => {
4362
assert.strictEqual(headers[':status'], 200);
4463
assert.strictEqual(headers['content-type'], 'application/json');

test/parallel/test-http2-create-client-session.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,16 @@ server.listen(0);
3434
server.on('listening', common.mustCall(() => {
3535

3636
const client = h2.connect(`http://localhost:${server.address().port}`);
37-
client.setMaxListeners(100);
37+
client.setMaxListeners(101);
3838

3939
client.on('goaway', console.log);
4040

41+
client.on('connect', common.mustCall(() => {
42+
assert(!client.encrypted);
43+
assert(!client.originSet);
44+
assert.strictEqual(client.alpnProtocol, 'h2c');
45+
}));
46+
4147
const countdown = new Countdown(count, () => {
4248
client.close();
4349
server.close();

0 commit comments

Comments
 (0)