summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2018-01-01 11:13:29 -0800
committerJames M Snell <jasnell@gmail.com>2018-01-03 11:29:43 -0800
commit060babd66524b6a3e4757bb2fe5b87ad567cdb40 (patch)
tree22a4d99a5f91fd9b976e8b2e293ecf7420f9a697
parentb25b1efa0a6801cdf3faea199a36e0e2cc489395 (diff)
downloadandroid-node-v8-060babd66524b6a3e4757bb2fe5b87ad567cdb40.tar.gz
android-node-v8-060babd66524b6a3e4757bb2fe5b87ad567cdb40.tar.bz2
android-node-v8-060babd66524b6a3e4757bb2fe5b87ad567cdb40.zip
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: https://github.com/nodejs/node/pull/17935 Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Sebastiaan Deckers <sebdeckers83@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
-rw-r--r--doc/api/http2.md35
-rw-r--r--lib/internal/http2/core.js58
-rw-r--r--test/parallel/test-http2-create-client-secure-session.js19
-rw-r--r--test/parallel/test-http2-create-client-session.js8
4 files changed, 118 insertions, 2 deletions
diff --git a/doc/api/http2.md b/doc/api/http2.md
index e9b06a427c..f613072324 100644
--- a/doc/api/http2.md
+++ b/doc/api/http2.md
@@ -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
@@ -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
@@ -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
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index cad5fcfb10..a7f3045c3e 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -70,7 +70,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');
@@ -731,6 +733,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 : {};
@@ -805,9 +818,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;
@@ -833,6 +849,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;
diff --git a/test/parallel/test-http2-create-client-secure-session.js b/test/parallel/test-http2-create-client-secure-session.js
index 811ef772d5..6120a58602 100644
--- a/test/parallel/test-http2-create-client-secure-session.js
+++ b/test/parallel/test-http2-create-client-secure-session.js
@@ -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({
@@ -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');
diff --git a/test/parallel/test-http2-create-client-session.js b/test/parallel/test-http2-create-client-session.js
index c5e9468f9b..b5be6bc858 100644
--- a/test/parallel/test-http2-create-client-session.js
+++ b/test/parallel/test-http2-create-client-session.js
@@ -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();