http: support generic Duplex
streams
Support generic `Duplex` streams through more duck typing on the server and client sides. Since HTTP is, as a protocol, independent of its underlying transport layer, Node.js should not enforce any restrictions on what streams its HTTP parser may use. Ref: https://github.com/nodejs/node/issues/16256 PR-URL: https://github.com/nodejs/node/pull/16267 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
This commit is contained in:
parent
ab16eec436
commit
3e25e4d00f
@ -797,11 +797,14 @@ added: v0.1.0
|
|||||||
|
|
||||||
* `socket` {net.Socket}
|
* `socket` {net.Socket}
|
||||||
|
|
||||||
When a new TCP stream is established. `socket` is an object of type
|
This event is emitted when a new TCP stream is established. `socket` is
|
||||||
[`net.Socket`][]. Usually users will not want to access this event. In
|
typically an object of type [`net.Socket`][]. Usually users will not want to
|
||||||
particular, the socket will not emit `'readable'` events because of how
|
access this event. In particular, the socket will not emit `'readable'` events
|
||||||
the protocol parser attaches to the socket. The `socket` can also be
|
because of how the protocol parser attaches to the socket. The `socket` can
|
||||||
accessed at `request.connection`.
|
also be accessed at `request.connection`.
|
||||||
|
|
||||||
|
*Note*: This event can also be explicitly emitted by users to inject connections
|
||||||
|
into the HTTP server. In that case, any [`Duplex`][] stream can be passed.
|
||||||
|
|
||||||
### Event: 'request'
|
### Event: 'request'
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
@ -1769,7 +1772,7 @@ changes:
|
|||||||
use for the request when the `agent` option is not used. This can be used to
|
use for the request when the `agent` option is not used. This can be used to
|
||||||
avoid creating a custom `Agent` class just to override the default
|
avoid creating a custom `Agent` class just to override the default
|
||||||
`createConnection` function. See [`agent.createConnection()`][] for more
|
`createConnection` function. See [`agent.createConnection()`][] for more
|
||||||
details.
|
details. Any [`Duplex`][] stream is a valid return value.
|
||||||
* `timeout` {number}: A number specifying the socket timeout in milliseconds.
|
* `timeout` {number}: A number specifying the socket timeout in milliseconds.
|
||||||
This will set the timeout before the socket is connected.
|
This will set the timeout before the socket is connected.
|
||||||
* `callback` {Function}
|
* `callback` {Function}
|
||||||
@ -1869,6 +1872,7 @@ const req = http.request(options, (res) => {
|
|||||||
[`'request'`]: #http_event_request
|
[`'request'`]: #http_event_request
|
||||||
[`'response'`]: #http_event_response
|
[`'response'`]: #http_event_response
|
||||||
[`Agent`]: #http_class_http_agent
|
[`Agent`]: #http_class_http_agent
|
||||||
|
[`Duplex`]: stream.html#stream_class_stream_duplex
|
||||||
[`EventEmitter`]: events.html#events_class_eventemitter
|
[`EventEmitter`]: events.html#events_class_eventemitter
|
||||||
[`TypeError`]: errors.html#errors_class_typeerror
|
[`TypeError`]: errors.html#errors_class_typeerror
|
||||||
[`URL`]: url.html#url_the_whatwg_url_api
|
[`URL`]: url.html#url_the_whatwg_url_api
|
||||||
|
@ -565,7 +565,10 @@ function responseKeepAlive(res, req) {
|
|||||||
if (!req.shouldKeepAlive) {
|
if (!req.shouldKeepAlive) {
|
||||||
if (socket.writable) {
|
if (socket.writable) {
|
||||||
debug('AGENT socket.destroySoon()');
|
debug('AGENT socket.destroySoon()');
|
||||||
socket.destroySoon();
|
if (typeof socket.destroySoon === 'function')
|
||||||
|
socket.destroySoon();
|
||||||
|
else
|
||||||
|
socket.end();
|
||||||
}
|
}
|
||||||
assert(!socket.writable);
|
assert(!socket.writable);
|
||||||
} else {
|
} else {
|
||||||
|
@ -303,7 +303,7 @@ function connectionListener(socket) {
|
|||||||
// If the user has added a listener to the server,
|
// If the user has added a listener to the server,
|
||||||
// request, or response, then it's their responsibility.
|
// request, or response, then it's their responsibility.
|
||||||
// otherwise, destroy on timeout by default
|
// otherwise, destroy on timeout by default
|
||||||
if (this.timeout)
|
if (this.timeout && typeof socket.setTimeout === 'function')
|
||||||
socket.setTimeout(this.timeout);
|
socket.setTimeout(this.timeout);
|
||||||
socket.on('timeout', socketOnTimeout);
|
socket.on('timeout', socketOnTimeout);
|
||||||
|
|
||||||
@ -354,11 +354,13 @@ function connectionListener(socket) {
|
|||||||
socket.on = socketOnWrap;
|
socket.on = socketOnWrap;
|
||||||
|
|
||||||
// We only consume the socket if it has never been consumed before.
|
// We only consume the socket if it has never been consumed before.
|
||||||
var external = socket._handle._externalStream;
|
if (socket._handle) {
|
||||||
if (!socket._handle._consumed && external) {
|
var external = socket._handle._externalStream;
|
||||||
parser._consumed = true;
|
if (!socket._handle._consumed && external) {
|
||||||
socket._handle._consumed = true;
|
parser._consumed = true;
|
||||||
parser.consume(external);
|
socket._handle._consumed = true;
|
||||||
|
parser.consume(external);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
parser[kOnExecute] =
|
parser[kOnExecute] =
|
||||||
onParserExecute.bind(undefined, this, socket, parser, state);
|
onParserExecute.bind(undefined, this, socket, parser, state);
|
||||||
@ -533,9 +535,13 @@ function resOnFinish(req, res, socket, state, server) {
|
|||||||
res.detachSocket(socket);
|
res.detachSocket(socket);
|
||||||
|
|
||||||
if (res._last) {
|
if (res._last) {
|
||||||
socket.destroySoon();
|
if (typeof socket.destroySoon === 'function') {
|
||||||
|
socket.destroySoon();
|
||||||
|
} else {
|
||||||
|
socket.end();
|
||||||
|
}
|
||||||
} else if (state.outgoing.length === 0) {
|
} else if (state.outgoing.length === 0) {
|
||||||
if (server.keepAliveTimeout) {
|
if (server.keepAliveTimeout && typeof socket.setTimeout === 'function') {
|
||||||
socket.setTimeout(0);
|
socket.setTimeout(0);
|
||||||
socket.setTimeout(server.keepAliveTimeout);
|
socket.setTimeout(server.keepAliveTimeout);
|
||||||
state.keepAliveTimeoutSet = true;
|
state.keepAliveTimeoutSet = true;
|
||||||
|
60
test/parallel/test-http-generic-streams.js
Normal file
60
test/parallel/test-http-generic-streams.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http = require('http');
|
||||||
|
const MakeDuplexPair = require('../common/duplexpair');
|
||||||
|
|
||||||
|
// Test 1: Simple HTTP test, no keep-alive.
|
||||||
|
{
|
||||||
|
const testData = 'Hello, World!\n';
|
||||||
|
const server = http.createServer(common.mustCall((req, res) => {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.end(testData);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { clientSide, serverSide } = MakeDuplexPair();
|
||||||
|
server.emit('connection', serverSide);
|
||||||
|
|
||||||
|
const req = http.request({
|
||||||
|
createConnection: common.mustCall(() => clientSide)
|
||||||
|
}, common.mustCall((res) => {
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', common.mustCall((data) => {
|
||||||
|
assert.strictEqual(data, testData);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Keep-alive for 2 requests.
|
||||||
|
{
|
||||||
|
const testData = 'Hello, World!\n';
|
||||||
|
const server = http.createServer(common.mustCall((req, res) => {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.end(testData);
|
||||||
|
}, 2));
|
||||||
|
|
||||||
|
const { clientSide, serverSide } = MakeDuplexPair();
|
||||||
|
server.emit('connection', serverSide);
|
||||||
|
|
||||||
|
function doRequest(cb) {
|
||||||
|
const req = http.request({
|
||||||
|
createConnection: common.mustCall(() => clientSide),
|
||||||
|
headers: { Connection: 'keep-alive' }
|
||||||
|
}, common.mustCall((res) => {
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', common.mustCall((data) => {
|
||||||
|
assert.strictEqual(data, testData);
|
||||||
|
}));
|
||||||
|
res.on('end', common.mustCall(cb));
|
||||||
|
}));
|
||||||
|
req.shouldKeepAlive = true;
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
doRequest(() => {
|
||||||
|
doRequest();
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user