Skip to content

Commit 060babd

Browse files
committed
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`. PR-URL: #17935 Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Sebastiaan Deckers <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]>
1 parent b25b1ef commit 060babd

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-2
lines changed

doc/api/http2.md

Lines changed: 35 additions & 0 deletions
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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ const TLSServer = tls.Server;
7070

7171
const kInspect = require('internal/util').customInspectSymbol;
7272

73+
const kAlpnProtocol = Symbol('alpnProtocol');
7374
const kAuthority = Symbol('authority');
75+
const kEncrypted = Symbol('encrypted');
7476
const kHandle = Symbol('handle');
7577
const kID = Symbol('id');
7678
const kInit = Symbol('init');
@@ -731,6 +733,17 @@ function setupHandle(socket, type, options) {
731733

732734
this[kHandle] = handle;
733735

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

@@ -805,9 +818,12 @@ class Http2Session extends EventEmitter {
805818
streams: new Map(),
806819
pendingStreams: new Set(),
807820
pendingAck: 0,
808-
writeQueueSize: 0
821+
writeQueueSize: 0,
822+
originSet: undefined
809823
};
810824

825+
this[kEncrypted] = undefined;
826+
this[kAlpnProtocol] = undefined;
811827
this[kType] = type;
812828
this[kProxySocket] = null;
813829
this[kSocket] = socket;
@@ -833,6 +849,46 @@ class Http2Session extends EventEmitter {
833849
debug(`Http2Session ${sessionName(type)}: created`);
834850
}
835851

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

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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 7 additions & 1 deletion
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)