Skip to content

Commit

Permalink
http2: add initial support for originSet
Browse files Browse the repository at this point in the history
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
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>
  • Loading branch information
jasnell authored and MylesBorins committed Jan 10, 2018
1 parent 3997617 commit 2b6a5d9
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 2 deletions.
35 changes: 35 additions & 0 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ session.setTimeout(2000);
session.on('timeout', () => { /** .. **/ });
```

#### http2session.alpnProtocol
<!-- YAML
added: REPLACEME
-->

* Value: {string|undefined}

Value will be `undefined` if the `Http2Session` is not yet connected to a
socket, `h2c` if the `Http2Session` is not connected to a `TLSSocket`, or
will return the value of the connected `TLSSocket`'s own `alpnProtocol`
property.

#### http2session.close([callback])
<!-- YAML
added: REPLACEME
Expand Down Expand Up @@ -340,6 +352,18 @@ added: v8.4.0
Will be `true` if this `Http2Session` instance has been destroyed and must no
longer be used, otherwise `false`.

#### http2session.encrypted
<!-- YAML
added: REPLACEME
-->

* Value: {boolean|undefined}

Value is `undefined` if the `Http2Session` session socket has not yet been
connected, `true` if the `Http2Session` is connected with a `TLSSocket`,
and `false` if the `Http2Session` is connected to any other kind of socket
or stream.

#### http2session.goaway([code, [lastStreamID, [opaqueData]]])
<!-- YAML
added: REPLACEME
Expand All @@ -363,6 +387,17 @@ added: v8.4.0
A prototype-less object describing the current local settings of this
`Http2Session`. The local settings are local to *this* `Http2Session` instance.

#### http2session.originSet
<!-- YAML
added: REPLACEME
-->

* Value: {string[]|undefined}

If the `Http2Session` is connected to a `TLSSocket`, the `originSet` property
will return an Array of origins for which the `Http2Session` may be
considered authoritative.

#### http2session.pendingSettingsAck
<!-- YAML
added: v8.4.0
Expand Down
58 changes: 57 additions & 1 deletion lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ const TLSServer = tls.Server;

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

const kAlpnProtocol = Symbol('alpnProtocol');
const kAuthority = Symbol('authority');
const kEncrypted = Symbol('encrypted');
const kHandle = Symbol('handle');
const kID = Symbol('id');
const kInit = Symbol('init');
Expand Down Expand Up @@ -729,6 +731,17 @@ function setupHandle(socket, type, options) {

this[kHandle] = handle;

if (socket.encrypted) {
this[kAlpnProtocol] = socket.alpnProtocol;
this[kEncrypted] = true;
} else {
// 'h2c' is the protocol identifier for HTTP/2 over plain-text. We use
// it here to identify any session that is not explicitly using an
// encrypted socket.
this[kAlpnProtocol] = 'h2c';
this[kEncrypted] = false;
}

const settings = typeof options.settings === 'object' ?
options.settings : {};

Expand Down Expand Up @@ -803,9 +816,12 @@ class Http2Session extends EventEmitter {
streams: new Map(),
pendingStreams: new Set(),
pendingAck: 0,
writeQueueSize: 0
writeQueueSize: 0,
originSet: undefined
};

this[kEncrypted] = undefined;
this[kAlpnProtocol] = undefined;
this[kType] = type;
this[kProxySocket] = null;
this[kSocket] = socket;
Expand All @@ -830,6 +846,46 @@ class Http2Session extends EventEmitter {
debug(`Http2Session ${sessionName(type)}: created`);
}

// Returns undefined if the socket is not yet connected, true if the
// socket is a TLSSocket, and false if it is not.
get encrypted() {
return this[kEncrypted];
}

// Returns undefined if the socket is not yet connected, `h2` if the
// socket is a TLSSocket and the alpnProtocol is `h2`, or `h2c` if the
// socket is not a TLSSocket.
get alpnProtocol() {
return this[kAlpnProtocol];
}

// TODO(jasnell): originSet is being added in preparation for ORIGIN frame
// support. At the current time, the ORIGIN frame specification is awaiting
// publication as an RFC and is awaiting implementation in nghttp2. Once
// added, an ORIGIN frame will add to the origins included in the origin
// set. 421 responses will remove origins from the set.
get originSet() {
if (!this.encrypted || this.destroyed)
return undefined;

let originSet = this[kState].originSet;
if (originSet === undefined) {
const socket = this[kSocket];
this[kState].originSet = originSet = new Set();
if (socket.servername != null) {
let originString = `https://${socket.servername}`;
if (socket.remotePort != null)
originString += `:${socket.remotePort}`;
// We have to ensure that it is a properly serialized
// ASCII origin string. The socket.servername might not
// be properly ASCII encoded.
originSet.add((new URL(originString)).origin);
}
}

return Array.from(originSet);
}

// True if the Http2Session is still waiting for the socket to connect
get connecting() {
return (this[kState].flags & SESSION_FLAGS_READY) === 0;
Expand Down
19 changes: 19 additions & 0 deletions test/parallel/test-http2-create-client-secure-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ function loadKey(keyname) {

function onStream(stream, headers) {
const socket = stream.session[kSocket];

assert(stream.session.encrypted);
assert(stream.session.alpnProtocol, 'h2');
const originSet = stream.session.originSet;
assert(Array.isArray(originSet));
assert.strictEqual(originSet[0],
`https://${socket.servername}:${socket.remotePort}`);

assert(headers[':authority'].startsWith(socket.servername));
stream.respond({ 'content-type': 'application/json' });
stream.end(JSON.stringify({
Expand All @@ -39,6 +47,17 @@ function verifySecureSession(key, cert, ca, opts) {
assert.strictEqual(client.socket.listenerCount('secureConnect'), 1);
const req = client.request();

client.on('connect', common.mustCall(() => {
assert(client.encrypted);
assert.strictEqual(client.alpnProtocol, 'h2');
const originSet = client.originSet;
assert(Array.isArray(originSet));
assert.strictEqual(originSet.length, 1);
assert.strictEqual(
originSet[0],
`https://${opts.servername || 'localhost'}:${server.address().port}`);
}));

req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
assert.strictEqual(headers['content-type'], 'application/json');
Expand Down
8 changes: 7 additions & 1 deletion test/parallel/test-http2-create-client-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@ server.listen(0);
server.on('listening', common.mustCall(() => {

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

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

client.on('connect', common.mustCall(() => {
assert(!client.encrypted);
assert(!client.originSet);
assert.strictEqual(client.alpnProtocol, 'h2c');
}));

const countdown = new Countdown(count, () => {
client.close();
server.close();
Expand Down

0 comments on commit 2b6a5d9

Please sign in to comment.