-
-
Notifications
You must be signed in to change notification settings - Fork 34.2k
http2: support generic Duplex streams
#16269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ const tls = require('tls'); | |
| const util = require('util'); | ||
| const fs = require('fs'); | ||
| const errors = require('internal/errors'); | ||
| const { StreamWrap } = require('_stream_wrap'); | ||
| const { Duplex } = require('stream'); | ||
| const { URL } = require('url'); | ||
| const { onServerStream, | ||
|
|
@@ -695,10 +696,14 @@ class Http2Session extends EventEmitter { | |
|
|
||
| // type { number } either NGHTTP2_SESSION_SERVER or NGHTTP2_SESSION_CLIENT | ||
| // options { Object } | ||
| // socket { net.Socket | tls.TLSSocket } | ||
| // socket { net.Socket | tls.TLSSocket | stream.Duplex } | ||
| constructor(type, options, socket) { | ||
| super(); | ||
|
|
||
| if (!socket._handle || !socket._handle._externalStream) { | ||
| socket = new StreamWrap(socket); | ||
| } | ||
|
|
||
| // No validation is performed on the input parameters because this | ||
| // constructor is not exported directly for users. | ||
|
|
||
|
|
@@ -723,7 +728,8 @@ class Http2Session extends EventEmitter { | |
| this[kSocket] = socket; | ||
|
|
||
| // Do not use nagle's algorithm | ||
| socket.setNoDelay(); | ||
| if (typeof socket.setNoDelay === 'function') | ||
| socket.setNoDelay(); | ||
|
|
||
| // Disable TLS renegotiation on the socket | ||
|
||
| if (typeof socket.disableRenegotiation === 'function') | ||
|
|
@@ -2429,15 +2435,19 @@ function connect(authority, options, listener) { | |
| const host = authority.hostname || authority.host || 'localhost'; | ||
|
|
||
| let socket; | ||
| switch (protocol) { | ||
| case 'http:': | ||
| socket = net.connect(port, host); | ||
| break; | ||
| case 'https:': | ||
| socket = tls.connect(port, host, initializeTLSOptions(options, host)); | ||
| break; | ||
| default: | ||
| throw new errors.Error('ERR_HTTP2_UNSUPPORTED_PROTOCOL', protocol); | ||
| if (typeof options.createConnection === 'function') { | ||
| socket = options.createConnection(authority, options); | ||
|
||
| } else { | ||
| switch (protocol) { | ||
| case 'http:': | ||
| socket = net.connect(port, host); | ||
| break; | ||
| case 'https:': | ||
| socket = tls.connect(port, host, initializeTLSOptions(options, host)); | ||
| break; | ||
| default: | ||
| throw new errors.Error('ERR_HTTP2_UNSUPPORTED_PROTOCOL', protocol); | ||
| } | ||
| } | ||
|
|
||
| socket.on('error', socketOnError); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* eslint-disable required-modules */ | ||
| 'use strict'; | ||
| const { Duplex } = require('stream'); | ||
| const assert = require('assert'); | ||
|
|
||
| const kCallback = Symbol('Callback'); | ||
| const kOtherSide = Symbol('Other'); | ||
|
|
||
| class DuplexSocket extends Duplex { | ||
| constructor() { | ||
| super(); | ||
| this[kCallback] = null; | ||
| this[kOtherSide] = null; | ||
| } | ||
|
|
||
| _read() { | ||
| const callback = this[kCallback]; | ||
| if (callback) { | ||
| this[kCallback] = null; | ||
| callback(); | ||
| } | ||
| } | ||
|
|
||
| _write(chunk, encoding, callback) { | ||
| assert.notStrictEqual(this[kOtherSide], null); | ||
| assert.strictEqual(this[kOtherSide][kCallback], null); | ||
| this[kOtherSide][kCallback] = callback; | ||
| this[kOtherSide].push(chunk); | ||
| } | ||
|
|
||
| _final(callback) { | ||
| this[kOtherSide].on('end', callback); | ||
| this[kOtherSide].push(null); | ||
| } | ||
| } | ||
|
|
||
| function makeDuplexPair() { | ||
| const clientSide = new DuplexSocket(); | ||
| const serverSide = new DuplexSocket(); | ||
| clientSide[kOtherSide] = serverSide; | ||
| serverSide[kOtherSide] = clientSide; | ||
| return { clientSide, serverSide }; | ||
| } | ||
|
|
||
| module.exports = makeDuplexPair; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| 'use strict'; | ||
| const common = require('../common'); | ||
| if (!common.hasCrypto) | ||
| common.skip('missing crypto'); | ||
| const assert = require('assert'); | ||
| const http2 = require('http2'); | ||
| const fs = require('fs'); | ||
| const makeDuplexPair = require('../common/duplexpair'); | ||
|
|
||
| { | ||
| const server = http2.createServer(); | ||
| server.on('stream', common.mustCall((stream, headers) => { | ||
| stream.respondWithFile(__filename); | ||
| })); | ||
|
|
||
| const { clientSide, serverSide } = makeDuplexPair(); | ||
| server.emit('connection', serverSide); | ||
|
|
||
| const client = http2.connect('http://localhost:80', { | ||
| createConnection: common.mustCall(() => clientSide) | ||
| }); | ||
|
|
||
| const req = client.request({ ':path': '/' }); | ||
|
|
||
| req.on('response', common.mustCall((headers) => { | ||
| assert.strictEqual(headers[':status'], 200); | ||
| })); | ||
|
|
||
| req.setEncoding('utf8'); | ||
| let data = ''; | ||
| req.on('data', (chunk) => { | ||
|
||
| data += chunk; | ||
| }); | ||
| req.on('end', common.mustCall(() => { | ||
| assert.strictEqual(data, fs.readFileSync(__filename, 'utf8')); | ||
| clientSide.destroy(); | ||
| clientSide.end(); | ||
| })); | ||
| req.end(); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is super nit picky but could we compare to
undefined? Anytime we're dealing with an object, because of the existence ofdocument.all(at least as far as V8 is concerned), these truthy/falsey checks are unusually expensive.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some things in Node’s source explicitly set
._handleon sockets tonull(e.g. after disconnecting)… so I’d like to be consistent with TLS here, just to be on the safe side.I doubt this makes a noticeable difference compared to the overall cost of setting up the HTTP2 session objects anyway, V8 seems to optimize a function containing only this line reasonable well to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason I thought we were unsetting
_handlebut if we donullit then this is fine.