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>
This commit is contained in:
parent
b25b1efa0a
commit
060babd665
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user