Skip to content

Commit 3e25e4d

Browse files
committed
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: #16256 PR-URL: #16267 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]>
1 parent ab16eec commit 3e25e4d

File tree

4 files changed

+88
-15
lines changed

4 files changed

+88
-15
lines changed

doc/api/http.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -797,11 +797,14 @@ added: v0.1.0
797797

798798
* `socket` {net.Socket}
799799

800-
When a new TCP stream is established. `socket` is an object of type
801-
[`net.Socket`][]. Usually users will not want to access this event. In
802-
particular, the socket will not emit `'readable'` events because of how
803-
the protocol parser attaches to the socket. The `socket` can also be
804-
accessed at `request.connection`.
800+
This event is emitted when a new TCP stream is established. `socket` is
801+
typically an object of type [`net.Socket`][]. Usually users will not want to
802+
access this event. In particular, the socket will not emit `'readable'` events
803+
because of how the protocol parser attaches to the socket. The `socket` can
804+
also be accessed at `request.connection`.
805+
806+
*Note*: This event can also be explicitly emitted by users to inject connections
807+
into the HTTP server. In that case, any [`Duplex`][] stream can be passed.
805808

806809
### Event: 'request'
807810
<!-- YAML
@@ -1769,7 +1772,7 @@ changes:
17691772
use for the request when the `agent` option is not used. This can be used to
17701773
avoid creating a custom `Agent` class just to override the default
17711774
`createConnection` function. See [`agent.createConnection()`][] for more
1772-
details.
1775+
details. Any [`Duplex`][] stream is a valid return value.
17731776
* `timeout` {number}: A number specifying the socket timeout in milliseconds.
17741777
This will set the timeout before the socket is connected.
17751778
* `callback` {Function}
@@ -1869,6 +1872,7 @@ const req = http.request(options, (res) => {
18691872
[`'request'`]: #http_event_request
18701873
[`'response'`]: #http_event_response
18711874
[`Agent`]: #http_class_http_agent
1875+
[`Duplex`]: stream.html#stream_class_stream_duplex
18721876
[`EventEmitter`]: events.html#events_class_eventemitter
18731877
[`TypeError`]: errors.html#errors_class_typeerror
18741878
[`URL`]: url.html#url_the_whatwg_url_api

lib/_http_client.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,10 @@ function responseKeepAlive(res, req) {
565565
if (!req.shouldKeepAlive) {
566566
if (socket.writable) {
567567
debug('AGENT socket.destroySoon()');
568-
socket.destroySoon();
568+
if (typeof socket.destroySoon === 'function')
569+
socket.destroySoon();
570+
else
571+
socket.end();
569572
}
570573
assert(!socket.writable);
571574
} else {

lib/_http_server.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ function connectionListener(socket) {
303303
// If the user has added a listener to the server,
304304
// request, or response, then it's their responsibility.
305305
// otherwise, destroy on timeout by default
306-
if (this.timeout)
306+
if (this.timeout && typeof socket.setTimeout === 'function')
307307
socket.setTimeout(this.timeout);
308308
socket.on('timeout', socketOnTimeout);
309309

@@ -354,11 +354,13 @@ function connectionListener(socket) {
354354
socket.on = socketOnWrap;
355355

356356
// We only consume the socket if it has never been consumed before.
357-
var external = socket._handle._externalStream;
358-
if (!socket._handle._consumed && external) {
359-
parser._consumed = true;
360-
socket._handle._consumed = true;
361-
parser.consume(external);
357+
if (socket._handle) {
358+
var external = socket._handle._externalStream;
359+
if (!socket._handle._consumed && external) {
360+
parser._consumed = true;
361+
socket._handle._consumed = true;
362+
parser.consume(external);
363+
}
362364
}
363365
parser[kOnExecute] =
364366
onParserExecute.bind(undefined, this, socket, parser, state);
@@ -533,9 +535,13 @@ function resOnFinish(req, res, socket, state, server) {
533535
res.detachSocket(socket);
534536

535537
if (res._last) {
536-
socket.destroySoon();
538+
if (typeof socket.destroySoon === 'function') {
539+
socket.destroySoon();
540+
} else {
541+
socket.end();
542+
}
537543
} else if (state.outgoing.length === 0) {
538-
if (server.keepAliveTimeout) {
544+
if (server.keepAliveTimeout && typeof socket.setTimeout === 'function') {
539545
socket.setTimeout(0);
540546
socket.setTimeout(server.keepAliveTimeout);
541547
state.keepAliveTimeoutSet = true;
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
const MakeDuplexPair = require('../common/duplexpair');
6+
7+
// Test 1: Simple HTTP test, no keep-alive.
8+
{
9+
const testData = 'Hello, World!\n';
10+
const server = http.createServer(common.mustCall((req, res) => {
11+
res.statusCode = 200;
12+
res.setHeader('Content-Type', 'text/plain');
13+
res.end(testData);
14+
}));
15+
16+
const { clientSide, serverSide } = MakeDuplexPair();
17+
server.emit('connection', serverSide);
18+
19+
const req = http.request({
20+
createConnection: common.mustCall(() => clientSide)
21+
}, common.mustCall((res) => {
22+
res.setEncoding('utf8');
23+
res.on('data', common.mustCall((data) => {
24+
assert.strictEqual(data, testData);
25+
}));
26+
}));
27+
req.end();
28+
}
29+
30+
// Test 2: Keep-alive for 2 requests.
31+
{
32+
const testData = 'Hello, World!\n';
33+
const server = http.createServer(common.mustCall((req, res) => {
34+
res.statusCode = 200;
35+
res.setHeader('Content-Type', 'text/plain');
36+
res.end(testData);
37+
}, 2));
38+
39+
const { clientSide, serverSide } = MakeDuplexPair();
40+
server.emit('connection', serverSide);
41+
42+
function doRequest(cb) {
43+
const req = http.request({
44+
createConnection: common.mustCall(() => clientSide),
45+
headers: { Connection: 'keep-alive' }
46+
}, common.mustCall((res) => {
47+
res.setEncoding('utf8');
48+
res.on('data', common.mustCall((data) => {
49+
assert.strictEqual(data, testData);
50+
}));
51+
res.on('end', common.mustCall(cb));
52+
}));
53+
req.shouldKeepAlive = true;
54+
req.end();
55+
}
56+
57+
doRequest(() => {
58+
doRequest();
59+
});
60+
}

0 commit comments

Comments
 (0)