Skip to content

Commit 0c116a9

Browse files
committed
stream: Expose DuplexPair API
1 parent 6213fce commit 0c116a9

27 files changed

+180
-122
lines changed

doc/api/stream.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ There are four fundamental stream types within Node.js:
4545
is written and read (for example, [`zlib.createDeflate()`][]).
4646

4747
Additionally, this module includes the utility functions
48-
[`stream.pipeline()`][], [`stream.finished()`][] and
48+
[`DuplexPair`][],
49+
[`stream.pipeline()`][],
50+
[`stream.finished()`][], and
4951
[`stream.Readable.from()`][].
5052

5153
### Object mode
@@ -1508,6 +1510,25 @@ unless `emitClose` is set in false.
15081510
Once `destroy()` has been called any further calls will be a noop and no
15091511
further errors except from `_destroy` may be emitted as `'error'`.
15101512

1513+
#### Class: `stream.DuplexPair`
1514+
<!-- YAML
1515+
added: REPLACEME
1516+
-->
1517+
1518+
The utility class `DuplexPair` returns an iterable object with two `Duplex`
1519+
streams on properties `0` and `1`, each connected to the other side:
1520+
1521+
```js
1522+
const [ clientSide, serverSide ] = new DuplexPair();
1523+
```
1524+
1525+
Whatever is written to one stream is made readable on the other. It provides
1526+
behavior analagous to a network connection, where the data which a client writes
1527+
to its socket becomes readable on the server's socket.
1528+
1529+
The Duplex streams are symmetrical; one or the other may be used without any
1530+
difference in behavior.
1531+
15111532
### `stream.finished(stream[, options], callback)`
15121533
<!-- YAML
15131534
added: v10.0.0
@@ -3031,6 +3052,7 @@ contain multi-byte characters.
30313052
[`'finish'`]: #stream_event_finish
30323053
[`'readable'`]: #stream_event_readable
30333054
[`Duplex`]: #stream_class_stream_duplex
3055+
[`DuplexPair`]: #stream_class_stream_duplexpair
30343056
[`EventEmitter`]: events.html#events_class_eventemitter
30353057
[`Readable`]: #stream_class_stream_readable
30363058
[`Symbol.hasInstance`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance

lib/internal/streams/duplexpair.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
'use strict';
2-
3-
const {
4-
Symbol,
5-
} = primordials;
6-
72
const { Duplex } = require('stream');
3+
const assert = require('internal/assert');
84

5+
const { Symbol, Array } = primordials;
96
const kCallback = Symbol('Callback');
107
const kOtherSide = Symbol('Other');
118

12-
class DuplexSocket extends Duplex {
9+
class DuplexSide extends Duplex {
1310
constructor() {
1411
super();
1512
this[kCallback] = null;
@@ -25,6 +22,8 @@ class DuplexSocket extends Duplex {
2522
}
2623

2724
_write(chunk, encoding, callback) {
25+
assert(this[kOtherSide] !== null);
26+
assert(this[kOtherSide][kCallback] === null);
2827
if (chunk.length === 0) {
2928
process.nextTick(callback);
3029
} else {
@@ -39,13 +38,13 @@ class DuplexSocket extends Duplex {
3938
}
4039
}
4140

42-
class DuplexPair {
41+
class DuplexPair extends Array {
4342
constructor() {
44-
this.socket1 = new DuplexSocket();
45-
this.socket2 = new DuplexSocket();
46-
this.socket1[kOtherSide] = this.socket2;
47-
this.socket2[kOtherSide] = this.socket1;
43+
super();
44+
const side0 = this[0] = new DuplexSide();
45+
const side1 = this[1] = new DuplexSide();
46+
side0[kOtherSide] = side1;
47+
side1[kOtherSide] = side0;
4848
}
4949
}
50-
5150
module.exports = DuplexPair;

lib/stream.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Stream.Writable = require('_stream_writable');
3434
Stream.Duplex = require('_stream_duplex');
3535
Stream.Transform = require('_stream_transform');
3636
Stream.PassThrough = require('_stream_passthrough');
37+
Stream.DuplexPair = require('internal/streams/duplexpair');
3738

3839
Stream.pipeline = pipeline;
3940
Stream.finished = eos;

lib/tls.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const { getRootCertificates, getSSLCiphers } = internalBinding('crypto');
4444
const { Buffer } = require('buffer');
4545
const EventEmitter = require('events');
4646
const { URL } = require('internal/url');
47-
const DuplexPair = require('internal/streams/duplexpair');
47+
const { DuplexPair } = require('stream');
4848
const { canonicalizeIP } = internalBinding('cares_wrap');
4949
const _tls_common = require('_tls_common');
5050
const _tls_wrap = require('_tls_wrap');
@@ -296,7 +296,7 @@ class SecurePair extends EventEmitter {
296296
rejectUnauthorized = false,
297297
options = {}) {
298298
super();
299-
const { socket1, socket2 } = new DuplexPair();
299+
const [ socket1, socket2 ] = new DuplexPair();
300300

301301
this.server = options.server;
302302
this.credentials = secureContext;

node.gyp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
'lib/_stream_duplex.js',
8484
'lib/_stream_transform.js',
8585
'lib/_stream_passthrough.js',
86+
'lib/_stream_duplexpair.js',
8687
'lib/_stream_wrap.js',
8788
'lib/string_decoder.js',
8889
'lib/sys.js',
@@ -231,7 +232,6 @@
231232
'lib/internal/streams/lazy_transform.js',
232233
'lib/internal/streams/async_iterator.js',
233234
'lib/internal/streams/buffer_list.js',
234-
'lib/internal/streams/duplexpair.js',
235235
'lib/internal/streams/from.js',
236236
'lib/internal/streams/legacy.js',
237237
'lib/internal/streams/destroy.js',

test/common/README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ This directory contains modules used to test the Node.js implementation.
1010
* [Countdown module](#countdown-module)
1111
* [CPU Profiler module](#cpu-profiler-module)
1212
* [DNS module](#dns-module)
13-
* [Duplex pair helper](#duplex-pair-helper)
1413
* [Environment variables](#environment-variables)
1514
* [Fixtures module](#fixtures-module)
1615
* [Heap dump checker module](#heap-dump-checker-module)
@@ -564,14 +563,6 @@ Reads a Domain String and returns a Buffer containing the domain.
564563
Takes in a parsed Object and writes its fields to a DNS packet as a Buffer
565564
object.
566565

567-
## Duplex pair helper
568-
569-
The `common/duplexpair` module exports a single function `makeDuplexPair`,
570-
which returns an object `{ clientSide, serverSide }` where each side is a
571-
`Duplex` stream connected to the other side.
572-
573-
There is no difference between client or server side beyond their names.
574-
575566
## Environment variables
576567

577568
The behavior of the Node.js test suite can be altered using the following

test/common/duplexpair.js

Lines changed: 0 additions & 49 deletions
This file was deleted.

test/parallel/test-gc-tls-external-memory.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const common = require('../common');
88
if (!common.hasCrypto)
99
common.skip('missing crypto');
1010

11-
const makeDuplexPair = require('../common/duplexpair');
11+
const { DuplexPair } = require('stream');
1212
const onGC = require('../common/ongc');
1313
const assert = require('assert');
1414
const tls = require('tls');
@@ -37,7 +37,7 @@ function connect() {
3737
return;
3838
}
3939

40-
const { clientSide, serverSide } = makeDuplexPair();
40+
const [ clientSide, serverSide ] = new DuplexPair();
4141

4242
const tlsSocket = tls.connect({ socket: clientSide });
4343
tlsSocket.on('error', common.mustCall(connect));

test/parallel/test-http-agent-domain-reused-gc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const common = require('../common');
44
const http = require('http');
55
const async_hooks = require('async_hooks');
6-
const makeDuplexPair = require('../common/duplexpair');
6+
const { DuplexPair } = require('stream');
77

88
// Regression test for https://github.com/nodejs/node/issues/30122
99
// When a domain is attached to an http Agent’s ReusedHandle object, that
@@ -36,7 +36,7 @@ async_hooks.createHook({
3636
// attached to too many objects that use strong references (timers, the network
3737
// socket handle, etc.) and wrap the client side in a JSStreamSocket so we don’t
3838
// have to implement the whole _handle API ourselves.
39-
const { serverSide, clientSide } = makeDuplexPair();
39+
const [ serverSide, clientSide ] = new DuplexPair();
4040
const JSStreamSocket = require('internal/js_stream_socket');
4141
const wrappedClientSide = new JSStreamSocket(clientSide);
4242

test/parallel/test-http-generic-streams.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const common = require('../common');
33
const assert = require('assert');
44
const http = require('http');
5-
const MakeDuplexPair = require('../common/duplexpair');
5+
const { DuplexPair } = require('stream');
66

77
// Test 1: Simple HTTP test, no keep-alive.
88
{
@@ -13,7 +13,7 @@ const MakeDuplexPair = require('../common/duplexpair');
1313
res.end(testData);
1414
}));
1515

16-
const { clientSide, serverSide } = MakeDuplexPair();
16+
const [ clientSide, serverSide ] = new DuplexPair();
1717
server.emit('connection', serverSide);
1818

1919
const req = http.request({
@@ -37,7 +37,7 @@ const MakeDuplexPair = require('../common/duplexpair');
3737
res.end(testData);
3838
}, 2));
3939

40-
const { clientSide, serverSide } = MakeDuplexPair();
40+
const [ clientSide, serverSide ] = new DuplexPair();
4141
server.emit('connection', serverSide);
4242

4343
function doRequest(cb) {
@@ -77,7 +77,7 @@ const MakeDuplexPair = require('../common/duplexpair');
7777
});
7878
}));
7979

80-
const { clientSide, serverSide } = MakeDuplexPair();
80+
const [ clientSide, serverSide ] = new DuplexPair();
8181
server.emit('connection', serverSide);
8282
clientSide.on('end', common.mustCall());
8383
serverSide.on('end', common.mustCall());
@@ -117,7 +117,7 @@ const MakeDuplexPair = require('../common/duplexpair');
117117

118118
}));
119119

120-
const { clientSide, serverSide } = MakeDuplexPair();
120+
const [ clientSide, serverSide ] = new DuplexPair();
121121
server.emit('connection', serverSide);
122122
clientSide.on('end', common.mustCall());
123123
serverSide.on('end', common.mustCall());
@@ -143,7 +143,7 @@ const MakeDuplexPair = require('../common/duplexpair');
143143
{
144144
const server = http.createServer(common.mustNotCall());
145145

146-
const { clientSide, serverSide } = MakeDuplexPair();
146+
const [ clientSide, serverSide ] = new DuplexPair();
147147
server.emit('connection', serverSide);
148148

149149
server.on('clientError', common.mustCall());

0 commit comments

Comments
 (0)