http2: support generic Duplex
streams
Support generic `Duplex` streams through using `StreamWrap` on the server and client sides, and adding a `createConnection` method option similar to what the HTTP/1 API provides. Since HTTP2 is, as a protocol, independent of its underlying transport layer, Node.js should not enforce any restrictions on what streams its internals may use. Ref: https://github.com/nodejs/node/issues/16256 PR-URL: https://github.com/nodejs/node/pull/16269 Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
e340a66cb1
commit
ab16eec436
@ -1598,6 +1598,9 @@ added: v8.4.0
|
|||||||
used to determine the padding. See [Using options.selectPadding][].
|
used to determine the padding. See [Using options.selectPadding][].
|
||||||
* `settings` {[Settings Object][]} The initial settings to send to the
|
* `settings` {[Settings Object][]} The initial settings to send to the
|
||||||
remote peer upon connection.
|
remote peer upon connection.
|
||||||
|
* `createConnection` {Function} An optional callback that receives the `URL`
|
||||||
|
instance passed to `connect` and the `options` object, and returns any
|
||||||
|
[`Duplex`][] stream that is to be used as the connection for this session.
|
||||||
* `listener` {Function}
|
* `listener` {Function}
|
||||||
* Returns {Http2Session}
|
* Returns {Http2Session}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ const tls = require('tls');
|
|||||||
const util = require('util');
|
const util = require('util');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const errors = require('internal/errors');
|
const errors = require('internal/errors');
|
||||||
|
const { StreamWrap } = require('_stream_wrap');
|
||||||
const { Duplex } = require('stream');
|
const { Duplex } = require('stream');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
const { onServerStream,
|
const { onServerStream,
|
||||||
@ -683,10 +684,14 @@ class Http2Session extends EventEmitter {
|
|||||||
|
|
||||||
// type { number } either NGHTTP2_SESSION_SERVER or NGHTTP2_SESSION_CLIENT
|
// type { number } either NGHTTP2_SESSION_SERVER or NGHTTP2_SESSION_CLIENT
|
||||||
// options { Object }
|
// options { Object }
|
||||||
// socket { net.Socket | tls.TLSSocket }
|
// socket { net.Socket | tls.TLSSocket | stream.Duplex }
|
||||||
constructor(type, options, socket) {
|
constructor(type, options, socket) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
if (!socket._handle || !socket._handle._externalStream) {
|
||||||
|
socket = new StreamWrap(socket);
|
||||||
|
}
|
||||||
|
|
||||||
// No validation is performed on the input parameters because this
|
// No validation is performed on the input parameters because this
|
||||||
// constructor is not exported directly for users.
|
// constructor is not exported directly for users.
|
||||||
|
|
||||||
@ -711,7 +716,8 @@ class Http2Session extends EventEmitter {
|
|||||||
this[kSocket] = socket;
|
this[kSocket] = socket;
|
||||||
|
|
||||||
// Do not use nagle's algorithm
|
// Do not use nagle's algorithm
|
||||||
socket.setNoDelay();
|
if (typeof socket.setNoDelay === 'function')
|
||||||
|
socket.setNoDelay();
|
||||||
|
|
||||||
// Disable TLS renegotiation on the socket
|
// Disable TLS renegotiation on the socket
|
||||||
if (typeof socket.disableRenegotiation === 'function')
|
if (typeof socket.disableRenegotiation === 'function')
|
||||||
@ -2417,15 +2423,19 @@ function connect(authority, options, listener) {
|
|||||||
const host = authority.hostname || authority.host || 'localhost';
|
const host = authority.hostname || authority.host || 'localhost';
|
||||||
|
|
||||||
let socket;
|
let socket;
|
||||||
switch (protocol) {
|
if (typeof options.createConnection === 'function') {
|
||||||
case 'http:':
|
socket = options.createConnection(authority, options);
|
||||||
socket = net.connect(port, host);
|
} else {
|
||||||
break;
|
switch (protocol) {
|
||||||
case 'https:':
|
case 'http:':
|
||||||
socket = tls.connect(port, host, initializeTLSOptions(options, host));
|
socket = net.connect(port, host);
|
||||||
break;
|
break;
|
||||||
default:
|
case 'https:':
|
||||||
throw new errors.Error('ERR_HTTP2_UNSUPPORTED_PROTOCOL', protocol);
|
socket = tls.connect(port, host, initializeTLSOptions(options, host));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new errors.Error('ERR_HTTP2_UNSUPPORTED_PROTOCOL', protocol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('error', socketOnError);
|
socket.on('error', socketOnError);
|
||||||
|
40
test/parallel/test-http2-generic-streams-sendfile.js
Normal file
40
test/parallel/test-http2-generic-streams-sendfile.js
Normal file
@ -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();
|
||||||
|
}
|
45
test/parallel/test-http2-generic-streams.js
Normal file
45
test/parallel/test-http2-generic-streams.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http2 = require('http2');
|
||||||
|
const makeDuplexPair = require('../common/duplexpair');
|
||||||
|
|
||||||
|
{
|
||||||
|
const testData = '<h1>Hello World</h1>';
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustCall((stream, headers) => {
|
||||||
|
stream.respond({
|
||||||
|
'content-type': 'text/html',
|
||||||
|
':status': 200
|
||||||
|
});
|
||||||
|
stream.end(testData);
|
||||||
|
}));
|
||||||
|
|
||||||
|
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');
|
||||||
|
// Note: This is checking that this small amount of data is passed through in
|
||||||
|
// a single chunk, which is unusual for our test suite but seems like a
|
||||||
|
// reasonable assumption here.
|
||||||
|
req.on('data', common.mustCall((data) => {
|
||||||
|
assert.strictEqual(data, testData);
|
||||||
|
}));
|
||||||
|
req.on('end', common.mustCall(() => {
|
||||||
|
clientSide.destroy();
|
||||||
|
clientSide.end();
|
||||||
|
}));
|
||||||
|
req.end();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user