Browse Source

Merge branch 'streams2'

v0.9.4-release
isaacs 12 years ago
parent
commit
01db736c8d
  1. 2
      benchmark/net-pipe.js
  2. 51
      doc/api/crypto.markdown
  3. 489
      doc/api/stream.markdown
  4. 2
      lib/_debugger.js
  5. 63
      lib/_stream_duplex.js
  6. 31
      lib/_stream_passthrough.js
  7. 752
      lib/_stream_readable.js
  8. 231
      lib/_stream_transform.js
  9. 257
      lib/_stream_writable.js
  10. 3
      lib/child_process.js
  11. 79
      lib/crypto.js
  12. 423
      lib/fs.js
  13. 134
      lib/http.js
  14. 470
      lib/net.js
  15. 4
      lib/repl.js
  16. 21
      lib/stream.js
  17. 7
      lib/string_decoder.js
  18. 35
      lib/tty.js
  19. 215
      lib/zlib.js
  20. 5
      node.gyp
  21. 39
      src/node.js
  22. 13
      src/node_zlib.cc
  23. 1
      test/fixtures/x1024.txt
  24. 16
      test/message/max_tick_depth_trace.out
  25. 16
      test/message/stdin_messages.out
  26. 2
      test/simple/test-child-process-disconnect.js
  27. 58
      test/simple/test-child-process-fork-net2.js
  28. 3
      test/simple/test-child-process-ipc.js
  29. 1
      test/simple/test-cluster-http-pipe.js
  30. 2
      test/simple/test-cluster-message.js
  31. 77
      test/simple/test-crypto.js
  32. 12
      test/simple/test-domain-http-server.js
  33. 35
      test/simple/test-file-write-stream.js
  34. 40
      test/simple/test-file-write-stream2.js
  35. 6
      test/simple/test-fs-empty-readStream.js
  36. 40
      test/simple/test-fs-read-stream-err.js
  37. 4
      test/simple/test-fs-read-stream.js
  38. 3
      test/simple/test-fs-write-stream-end.js
  39. 56
      test/simple/test-fs-write-stream-err.js
  40. 4
      test/simple/test-http-1.0-keep-alive.js
  41. 10
      test/simple/test-http-abort-client.js
  42. 10
      test/simple/test-http-agent.js
  43. 1
      test/simple/test-http-client-agent.js
  44. 5
      test/simple/test-http-client-pipe-end.js
  45. 6
      test/simple/test-http-connect.js
  46. 1
      test/simple/test-http-date-header.js
  47. 1
      test/simple/test-http-default-encoding.js
  48. 1
      test/simple/test-http-header-read.js
  49. 21
      test/simple/test-http-header-response-splitting.js
  50. 6
      test/simple/test-http-host-headers.js
  51. 7
      test/simple/test-http-keep-alive-close-on-header.js
  52. 3
      test/simple/test-http-keep-alive.js
  53. 1
      test/simple/test-http-many-keep-alive-connections.js
  54. 1
      test/simple/test-http-parser-free.js
  55. 1
      test/simple/test-http-request-end-twice.js
  56. 1
      test/simple/test-http-request-end.js
  57. 6
      test/simple/test-http-res-write-end-dont-take-array.js
  58. 1
      test/simple/test-http-response-readable.js
  59. 1
      test/simple/test-http-set-trailers.js
  60. 1
      test/simple/test-http-status-code.js
  61. 2
      test/simple/test-http-timeout.js
  62. 1
      test/simple/test-https-agent.js
  63. 2
      test/simple/test-https-socket-options.js
  64. 1
      test/simple/test-https-strict.js
  65. 2
      test/simple/test-net-after-close.js
  66. 28
      test/simple/test-net-binary.js
  67. 15
      test/simple/test-net-bytes-stats.js
  68. 2
      test/simple/test-net-can-reset-timeout.js
  69. 13
      test/simple/test-net-connect-buffer.js
  70. 4
      test/simple/test-net-connect-options.js
  71. 9
      test/simple/test-net-pingpong.js
  72. 18
      test/simple/test-net-reconnect.js
  73. 1
      test/simple/test-net-remote-address-port.js
  74. 4
      test/simple/test-net-write-after-close.js
  75. 13
      test/simple/test-pipe-file-to-http.js
  76. 1
      test/simple/test-pipe.js
  77. 1
      test/simple/test-regress-GH-877.js
  78. 3
      test/simple/test-repl-autolibs.js
  79. 320
      test/simple/test-stream2-basic.js
  80. 76
      test/simple/test-stream2-fs.js
  81. 105
      test/simple/test-stream2-pipe-error-handling.js
  82. 109
      test/simple/test-stream2-readable-from-list.js
  83. 299
      test/simple/test-stream2-set-encoding.js
  84. 314
      test/simple/test-stream2-transform.js
  85. 246
      test/simple/test-stream2-writable.js
  86. 1
      test/simple/test-tcp-wrap-connect.js
  87. 15
      test/simple/test-tls-pause.js
  88. 7
      test/simple/test-zlib-invalid-input.js
  89. 17
      test/simple/test-zlib-random-byte-pipes.js

2
benchmark/net-pipe.js

@ -27,7 +27,7 @@ Writer.prototype.write = function(chunk, encoding, cb) {
// doesn't matter, never emits anything. // doesn't matter, never emits anything.
Writer.prototype.on = function() {}; Writer.prototype.on = function() {};
Writer.prototype.once = function() {};
Writer.prototype.emit = function() {}; Writer.prototype.emit = function() {};
var statCounter = 0; var statCounter = 0;

51
doc/api/crypto.markdown

@ -89,6 +89,11 @@ Example: this program that takes the sha1 sum of a file
The class for creating hash digests of data. The class for creating hash digests of data.
It is a [stream](stream.html) that is both readable and writable. The
written data is used to compute the hash. Once the writable side of
the stream is ended, use the `read()` method to get the computed hash
digest. The legacy `update` and `digest` methods are also supported.
Returned by `crypto.createHash`. Returned by `crypto.createHash`.
### hash.update(data, [input_encoding]) ### hash.update(data, [input_encoding])
@ -114,6 +119,11 @@ called.
Creates and returns a hmac object, a cryptographic hmac with the given Creates and returns a hmac object, a cryptographic hmac with the given
algorithm and key. algorithm and key.
It is a [stream](stream.html) that is both readable and writable. The
written data is used to compute the hmac. Once the writable side of
the stream is ended, use the `read()` method to get the computed
digest. The legacy `update` and `digest` methods are also supported.
`algorithm` is dependent on the available algorithms supported by `algorithm` is dependent on the available algorithms supported by
OpenSSL - see createHash above. `key` is the hmac key to be used. OpenSSL - see createHash above. `key` is the hmac key to be used.
@ -148,6 +158,11 @@ recent releases, `openssl list-cipher-algorithms` will display the
available cipher algorithms. `password` is used to derive key and IV, available cipher algorithms. `password` is used to derive key and IV,
which must be a `'binary'` encoded string or a [buffer](buffer.html). which must be a `'binary'` encoded string or a [buffer](buffer.html).
It is a [stream](stream.html) that is both readable and writable. The
written data is used to compute the hash. Once the writable side of
the stream is ended, use the `read()` method to get the computed hash
digest. The legacy `update` and `digest` methods are also supported.
## crypto.createCipheriv(algorithm, key, iv) ## crypto.createCipheriv(algorithm, key, iv)
Creates and returns a cipher object, with the given algorithm, key and Creates and returns a cipher object, with the given algorithm, key and
@ -166,6 +181,11 @@ Class for encrypting data.
Returned by `crypto.createCipher` and `crypto.createCipheriv`. Returned by `crypto.createCipher` and `crypto.createCipheriv`.
Cipher objects are [streams](stream.html) that are both readable and
writable. The written plain text data is used to produce the
encrypted data on the the readable side. The legacy `update` and
`final` methods are also supported.
### cipher.update(data, [input_encoding], [output_encoding]) ### cipher.update(data, [input_encoding], [output_encoding])
Updates the cipher with `data`, the encoding of which is given in Updates the cipher with `data`, the encoding of which is given in
@ -213,6 +233,11 @@ Class for decrypting data.
Returned by `crypto.createDecipher` and `crypto.createDecipheriv`. Returned by `crypto.createDecipher` and `crypto.createDecipheriv`.
Decipher objects are [streams](stream.html) that are both readable and
writable. The written enciphered data is used to produce the
plain-text data on the the readable side. The legacy `update` and
`final` methods are also supported.
### decipher.update(data, [input_encoding], [output_encoding]) ### decipher.update(data, [input_encoding], [output_encoding])
Updates the decipher with `data`, which is encoded in `'binary'`, Updates the decipher with `data`, which is encoded in `'binary'`,
@ -246,28 +271,33 @@ Creates and returns a signing object, with the given algorithm. On
recent OpenSSL releases, `openssl list-public-key-algorithms` will recent OpenSSL releases, `openssl list-public-key-algorithms` will
display the available signing algorithms. Examples are `'RSA-SHA256'`. display the available signing algorithms. Examples are `'RSA-SHA256'`.
## Class: Signer ## Class: Sign
Class for generating signatures. Class for generating signatures.
Returned by `crypto.createSign`. Returned by `crypto.createSign`.
### signer.update(data) Sign objects are writable [streams](stream.html). The written data is
used to generate the signature. Once all of the data has been
written, the `sign` method will return the signature. The legacy
`update` method is also supported.
### sign.update(data)
Updates the signer object with data. This can be called many times Updates the sign object with data. This can be called many times
with new data as it is streamed. with new data as it is streamed.
### signer.sign(private_key, [output_format]) ### sign.sign(private_key, [output_format])
Calculates the signature on all the updated data passed through the Calculates the signature on all the updated data passed through the
signer. `private_key` is a string containing the PEM encoded private sign. `private_key` is a string containing the PEM encoded private
key for signing. key for signing.
Returns the signature in `output_format` which can be `'binary'`, Returns the signature in `output_format` which can be `'binary'`,
`'hex'` or `'base64'`. If no encoding is provided, then a buffer is `'hex'` or `'base64'`. If no encoding is provided, then a buffer is
returned. returned.
Note: `signer` object can not be used after `sign()` method been Note: `sign` object can not be used after `sign()` method been
called. called.
## crypto.createVerify(algorithm) ## crypto.createVerify(algorithm)
@ -281,6 +311,12 @@ Class for verifying signatures.
Returned by `crypto.createVerify`. Returned by `crypto.createVerify`.
Verify objects are writable [streams](stream.html). The written data
is used to validate against the supplied signature. Once all of the
data has been written, the `verify` method will return true if the
supplied signature is valid. The legacy `update` method is also
supported.
### verifier.update(data) ### verifier.update(data)
Updates the verifier object with data. This can be called many times Updates the verifier object with data. This can be called many times
@ -469,9 +505,6 @@ default, set the `crypto.DEFAULT_ENCODING` field to 'binary'. Note
that new programs will probably expect buffers, so only use this as a that new programs will probably expect buffers, so only use this as a
temporary measure. temporary measure.
Also, a Streaming API will be provided, but this will be done in such
a way as to preserve the legacy API surface.
[createCipher()]: #crypto_crypto_createcipher_algorithm_password [createCipher()]: #crypto_crypto_createcipher_algorithm_password
[createCipheriv()]: #crypto_crypto_createcipheriv_algorithm_key_iv [createCipheriv()]: #crypto_crypto_createcipheriv_algorithm_key_iv

489
doc/api/stream.markdown

@ -7,186 +7,485 @@ Node. For example a request to an HTTP server is a stream, as is
stdout. Streams are readable, writable, or both. All streams are stdout. Streams are readable, writable, or both. All streams are
instances of [EventEmitter][] instances of [EventEmitter][]
You can load up the Stream base class by doing `require('stream')`. You can load the Stream base classes by doing `require('stream')`.
There are base classes provided for Readable streams, Writable
streams, Duplex streams, and Transform streams.
## Readable Stream ## Compatibility
In earlier versions of Node, the Readable stream interface was
simpler, but also less powerful and less useful.
* Rather than waiting for you to call the `read()` method, `'data'`
events would start emitting immediately. If you needed to do some
I/O to decide how to handle data, then you had to store the chunks
in some kind of buffer so that they would not be lost.
* The `pause()` method was advisory, rather than guaranteed. This
meant that you still had to be prepared to receive `'data'` events
even when the stream was in a paused state.
In Node v0.10, the Readable class described below was added. For
backwards compatibility with older Node programs, Readable streams
switch into "old mode" when a `'data'` event handler is added, or when
the `pause()` or `resume()` methods are called. The effect is that,
even if you are not using the new `read()` method and `'readable'`
event, you no longer have to worry about losing `'data'` chunks.
Most programs will continue to function normally. However, this
introduces an edge case in the following conditions:
* No `'data'` event handler is added.
* The `pause()` and `resume()` methods are never called.
For example, consider the following code:
```javascript
// WARNING! BROKEN!
net.createServer(function(socket) {
// we add an 'end' method, but never consume the data
socket.on('end', function() {
// It will never get here.
socket.end('I got your message (but didnt read it)\n');
});
}).listen(1337);
```
In versions of node prior to v0.10, the incoming message data would be
simply discarded. However, in Node v0.10 and beyond, the socket will
remain paused forever.
The workaround in this situation is to call the `resume()` method to
trigger "old mode" behavior:
```javascript
// Workaround
net.createServer(function(socket) {
socket.on('end', function() {
socket.end('I got your message (but didnt read it)\n');
});
// start the flow of data, discarding it.
socket.resume();
}).listen(1337);
```
In addition to new Readable streams switching into old-mode, pre-v0.10
style streams can be wrapped in a Readable class using the `wrap()`
method.
## Class: stream.Readable
<!--type=class--> <!--type=class-->
A `Readable Stream` has the following methods, members, and events. A `Readable Stream` has the following methods, members, and events.
### Event: 'data' Note that `stream.Readable` is an abstract class designed to be
extended with an underlying implementation of the `_read(size, cb)`
method. (See below.)
`function (data) { }` ### new stream.Readable([options])
The `'data'` event emits either a `Buffer` (by default) or a string if * `options` {Object}
`setEncoding()` was used. * `bufferSize` {Number} The size of the chunks to consume from the
underlying resource. Default=16kb
* `lowWaterMark` {Number} The minimum number of bytes to store in
the internal buffer before emitting `readable`. Default=0
* `highWaterMark` {Number} The maximum number of bytes to store in
the internal buffer before ceasing to read from the underlying
resource. Default=16kb
* `encoding` {String} If specified, then buffers will be decoded to
strings using the specified encoding. Default=null
Note that the __data will be lost__ if there is no listener when a In classes that extend the Readable class, make sure to call the
`Readable Stream` emits a `'data'` event. constructor so that the buffering settings can be properly
initialized.
### Event: 'end' ### readable.\_read(size, callback)
* `size` {Number} Number of bytes to read asynchronously
* `callback` {Function} Called with an error or with data
All Readable stream implementations must provide a `_read` method
to fetch data from the underlying resource.
**This function MUST NOT be called directly.** It should be
implemented by child classes, and called by the internal Readable
class methods only.
Call the callback using the standard `callback(error, data)` pattern.
When no more data can be fetched, call `callback(null, null)` to
signal the EOF.
`function () { }` This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### readable.wrap(stream)
* `stream` {Stream} An "old style" readable stream
If you are using an older Node library that emits `'data'` events and
has a `pause()` method that is advisory only, then you can use the
`wrap()` method to create a Readable stream that uses the old stream
as its data source.
For example:
```javascript
var OldReader = require('./old-api-module.js').OldReader;
var oreader = new OldReader;
var Readable = require('stream').Readable;
var myReader = new Readable().wrap(oreader);
myReader.on('readable', function() {
myReader.read(); // etc.
});
```
### Event: 'readable'
When there is data ready to be consumed, this event will fire. The
number of bytes that are required to be considered "readable" depends
on the `lowWaterMark` option set in the constructor.
When this event emits, call the `read()` method to consume the data.
### Event: 'end'
Emitted when the stream has received an EOF (FIN in TCP terminology). Emitted when the stream has received an EOF (FIN in TCP terminology).
Indicates that no more `'data'` events will happen. If the stream is Indicates that no more `'data'` events will happen. If the stream is
also writable, it may be possible to continue writing. also writable, it may be possible to continue writing.
### Event: 'error' ### Event: 'data'
The `'data'` event emits either a `Buffer` (by default) or a string if
`setEncoding()` was used.
Note that adding a `'data'` event listener will switch the Readable
stream into "old mode", where data is emitted as soon as it is
available, rather than waiting for you to call `read()` to consume it.
`function (exception) { }` ### Event: 'error'
Emitted if there was an error receiving data. Emitted if there was an error receiving data.
### Event: 'close' ### Event: 'close'
`function () { }`
Emitted when the underlying resource (for example, the backing file Emitted when the underlying resource (for example, the backing file
descriptor) has been closed. Not all streams will emit this. descriptor) has been closed. Not all streams will emit this.
### stream.readable ### readable.setEncoding(encoding)
A boolean that is `true` by default, but turns `false` after an
`'error'` occurred, the stream came to an `'end'`, or `destroy()` was
called.
### stream.setEncoding([encoding])
Makes the `'data'` event emit a string instead of a `Buffer`. `encoding` Makes the `'data'` event emit a string instead of a `Buffer`. `encoding`
can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`. Defaults can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`.
to `'utf8'`.
### stream.pause()
Issues an advisory signal to the underlying communication layer, The encoding can also be set by specifying an `encoding` field to the
requesting that no further data be sent until `resume()` is called. constructor.
Note that, due to the advisory nature, certain streams will not be ### readable.read([size])
paused immediately, and so `'data'` events may be emitted for some
indeterminate period of time even after `pause()` is called. You may
wish to buffer such `'data'` events.
### stream.resume() * `size` {Number | null} Optional number of bytes to read.
* Return: {Buffer | String | null}
Resumes the incoming `'data'` events after a `pause()`. Call this method to consume data once the `'readable'` event is
emitted.
### stream.destroy() The `size` argument will set a minimum number of bytes that you are
interested in. If not set, then the entire content of the internal
buffer is returned.
Closes the underlying file descriptor. Stream is no longer `writable` If there is no data to consume, or if there are fewer bytes in the
nor `readable`. The stream will not emit any more 'data', or 'end' internal buffer than the `size` argument, then `null` is returned, and
events. Any queued write data will not be sent. The stream should emit a future `'readable'` event will be emitted when more is available.
'close' event once its resources have been disposed of.
Note that calling `stream.read(0)` will always return `null`, and will
trigger a refresh of the internal buffer, but otherwise be a no-op.
### stream.pipe(destination, [options]) ### readable.pipe(destination, [options])
This is a `Stream.prototype` method available on all `Stream`s. * `destination` {Writable Stream}
* `options` {Object} Optional
* `end` {Boolean} Default=true
Connects this read stream to `destination` WriteStream. Incoming data on Connects this readable stream to `destination` WriteStream. Incoming
this stream gets written to `destination`. The destination and source data on this stream gets written to `destination`. Properly manages
streams are kept in sync by pausing and resuming as necessary. back-pressure so that a slow destination will not be overwhelmed by a
fast readable stream.
This function returns the `destination` stream. This function returns the `destination` stream.
Emulating the Unix `cat` command: For example, emulating the Unix `cat` command:
process.stdin.resume(); process.stdin.pipe(process.stdout);
process.stdin.pipe(process.stdout);
By default `end()` is called on the destination when the source stream By default `end()` is called on the destination when the source stream
emits `end`, so that `destination` is no longer writable. Pass `{ end: emits `end`, so that `destination` is no longer writable. Pass `{ end:
false }` as `options` to keep the destination stream open. false }` as `options` to keep the destination stream open.
This keeps `process.stdout` open so that "Goodbye" can be written at the This keeps `writer` open so that "Goodbye" can be written at the
end. end.
process.stdin.resume(); reader.pipe(writer, { end: false });
reader.on("end", function() {
writer.end("Goodbye\n");
});
Note that `process.stderr` and `process.stdout` are never closed until
the process exits, regardless of the specified options.
### readable.unpipe([destination])
* `destination` {Writable Stream} Optional
Undo a previously established `pipe()`. If no destination is
provided, then all previously established pipes are removed.
### readable.pause()
process.stdin.pipe(process.stdout, { end: false }); Switches the readable stream into "old mode", where data is emitted
using a `'data'` event rather than being buffered for consumption via
the `read()` method.
process.stdin.on("end", function() { Ceases the flow of data. No `'data'` events are emitted while the
process.stdout.write("Goodbye\n"); }); stream is in a paused state.
### readable.resume()
## Writable Stream Switches the readable stream into "old mode", where data is emitted
using a `'data'` event rather than being buffered for consumption via
the `read()` method.
Resumes the incoming `'data'` events after a `pause()`.
## Class: stream.Writable
<!--type=class--> <!--type=class-->
A `Writable Stream` has the following methods, members, and events. A `Writable` Stream has the following methods, members, and events.
### Event: 'drain' Note that `stream.Writable` is an abstract class designed to be
extended with an underlying implementation of the `_write(chunk, cb)`
method. (See below.)
`function () { }` ### new stream.Writable([options])
Emitted when the stream's write queue empties and it's safe to write without * `options` {Object}
buffering again. Listen for it when `stream.write()` returns `false`. * `highWaterMark` {Number} Buffer level when `write()` starts
returning false. Default=16kb
* `lowWaterMark` {Number} The buffer level when `'drain'` is
emitted. Default=0
* `decodeStrings` {Boolean} Whether or not to decode strings into
Buffers before passing them to `_write()`. Default=true
The `'drain'` event can happen at *any* time, regardless of whether or not In classes that extend the Writable class, make sure to call the
`stream.write()` has previously returned `false`. To avoid receiving unwanted constructor so that the buffering settings can be properly
`'drain'` events, listen using `stream.once()`. initialized.
### Event: 'error' ### writable.\_write(chunk, callback)
`function (exception) { }` * `chunk` {Buffer | Array} The data to be written
* `callback` {Function} Called with an error, or null when finished
Emitted on error with the exception `exception`. All Writable stream implementations must provide a `_write` method to
send data to the underlying resource.
### Event: 'close' **This function MUST NOT be called directly.** It should be
implemented by child classes, and called by the internal Writable
class methods only.
Call the callback using the standard `callback(error)` pattern to
signal that the write completed successfully or with an error.
If the `decodeStrings` flag is set in the constructor options, then
`chunk` will be an array rather than a Buffer. This is to support
implementations that have an optimized handling for certain string
data encodings.
This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### writable.write(chunk, [encoding], [callback])
* `chunk` {Buffer | String} Data to be written
* `encoding` {String} Optional. If `chunk` is a string, then encoding
defaults to `'utf8'`
* `callback` {Function} Optional. Called when this chunk is
successfully written.
* Returns {Boolean}
Writes `chunk` to the stream. Returns `true` if the data has been
flushed to the underlying resource. Returns `false` to indicate that
the buffer is full, and the data will be sent out in the future. The
`'drain'` event will indicate when the buffer is empty again.
The specifics of when `write()` will return false, and when a
subsequent `'drain'` event will be emitted, are determined by the
`highWaterMark` and `lowWaterMark` options provided to the
constructor.
### writable.end([chunk], [encoding])
* `chunk` {Buffer | String} Optional final data to be written
* `encoding` {String} Optional. If `chunk` is a string, then encoding
defaults to `'utf8'`
Call this method to signal the end of the data being written to the
stream.
`function () { }` ### Event: 'drain'
Emitted when the stream's write queue empties and it's safe to write
without buffering again. Listen for it when `stream.write()` returns
`false`.
### Event: 'close'
Emitted when the underlying file descriptor has been closed. Emitted when the underlying resource (for example, the backing file
descriptor) has been closed. Not all streams will emit this.
### Event: 'pipe' ### Event: 'pipe'
`function (src) { }` * `source` {Readable Stream}
Emitted when the stream is passed to a readable stream's pipe method. Emitted when the stream is passed to a readable stream's pipe method.
### stream.writable ### Event 'unpipe'
* `source` {Readable Stream}
Emitted when a previously established `pipe()` is removed using the
source Readable stream's `unpipe()` method.
## Class: stream.Duplex
<!--type=class-->
A "duplex" stream is one that is both Readable and Writable, such as a
TCP socket connection.
Note that `stream.Duplex` is an abstract class designed to be
extended with an underlying implementation of the `_read(size, cb)`
and `_write(chunk, callback)` methods as you would with a Readable or
Writable stream class.
Since JavaScript doesn't have multiple prototypal inheritance, this
class prototypally inherits from Readable, and then parasitically from
Writable. It is thus up to the user to implement both the lowlevel
`_read(n,cb)` method as well as the lowlevel `_write(chunk,cb)` method
on extension duplex classes.
### new stream.Duplex(options)
* `options` {Object} Passed to both Writable and Readable
constructors. Also has the following fields:
* `allowHalfOpen` {Boolean} Default=true. If set to `false`, then
the stream will automatically end the readable side when the
writable side ends and vice versa.
In classes that extend the Duplex class, make sure to call the
constructor so that the buffering settings can be properly
initialized.
## Class: stream.Transform
A "transform" stream is a duplex stream where the output is causally
connected in some way to the input, such as a zlib stream or a crypto
stream.
There is no requirement that the output be the same size as the input,
the same number of chunks, or arrive at the same time. For example, a
Hash stream will only ever have a single chunk of output which is
provided when the input is ended. A zlib stream will either produce
much smaller or much larger than its input.
Rather than implement the `_read()` and `_write()` methods, Transform
classes must implement the `_transform()` method, and may optionally
also implement the `_flush()` method. (See below.)
### new stream.Transform([options])
* `options` {Object} Passed to both Writable and Readable
constructors.
In classes that extend the Transform class, make sure to call the
constructor so that the buffering settings can be properly
initialized.
### transform.\_transform(chunk, outputFn, callback)
* `chunk` {Buffer} The chunk to be transformed.
* `outputFn` {Function} Call this function with any output data to be
passed to the readable interface.
* `callback` {Function} Call this function (optionally with an error
argument) when you are done processing the supplied chunk.
A boolean that is `true` by default, but turns `false` after an All Transform stream implementations must provide a `_transform`
`'error'` occurred or `end()` / `destroy()` was called. method to accept input and produce output.
### stream.write(string, [encoding]) **This function MUST NOT be called directly.** It should be
implemented by child classes, and called by the internal Transform
class methods only.
Writes `string` with the given `encoding` to the stream. Returns `true` `_transform` should do whatever has to be done in this specific
if the string has been flushed to the kernel buffer. Returns `false` to Transform class, to handle the bytes being written, and pass them off
indicate that the kernel buffer is full, and the data will be sent out to the readable portion of the interface. Do asynchronous I/O,
in the future. The `'drain'` event will indicate when the kernel buffer process things, and so on.
is empty again. The `encoding` defaults to `'utf8'`.
### stream.write(buffer) Call the callback function only when the current chunk is completely
consumed. Note that this may mean that you call the `outputFn` zero
or more times, depending on how much data you want to output as a
result of this chunk.
Same as the above except with a raw buffer. This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### stream.end() ### transform.\_flush(outputFn, callback)
Terminates the stream with EOF or FIN. This call will allow queued * `outputFn` {Function} Call this function with any output data to be
write data to be sent before closing the stream. passed to the readable interface.
* `callback` {Function} Call this function (optionally with an error
argument) when you are done flushing any remaining data.
### stream.end(string, encoding) **This function MUST NOT be called directly.** It MAY be implemented
by child classes, and if so, will be called by the internal Transform
class methods only.
Sends `string` with the given `encoding` and terminates the stream with In some cases, your transform operation may need to emit a bit more
EOF or FIN. This is useful to reduce the number of packets sent. data at the end of the stream. For example, a `Zlib` compression
stream will store up some internal state so that it can optimally
compress the output. At the end, however, it needs to do the best it
can with what is left, so that the data will be complete.
### stream.end(buffer) In those cases, you can implement a `_flush` method, which will be
called at the very end, after all the written data is consumed, but
before emitting `end` to signal the end of the readable side. Just
like with `_transform`, call `outputFn` zero or more times, as
appropriate, and call `callback` when the flush operation is complete.
Same as above but with a `buffer`. This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### stream.destroy()
Closes the underlying file descriptor. Stream is no longer `writable` ## Class: stream.PassThrough
nor `readable`. The stream will not emit any more 'data', or 'end'
events. Any queued write data will not be sent. The stream should emit
'close' event once its resources have been disposed of.
### stream.destroySoon() This is a trivial implementation of a `Transform` stream that simply
passes the input bytes across to the output. Its purpose is mainly
for examples and testing, but there are occasionally use cases where
it can come in handy.
After the write queue is drained, close the file descriptor.
`destroySoon()` can still destroy straight away, as long as there is no
data left in the queue for writes.
[EventEmitter]: events.html#events_class_events_eventemitter [EventEmitter]: events.html#events_class_events_eventemitter

2
lib/_debugger.js

@ -36,7 +36,7 @@ exports.start = function(argv, stdin, stdout) {
} }
// Setup input/output streams // Setup input/output streams
stdin = stdin || process.openStdin(); stdin = stdin || process.stdin;
stdout = stdout || process.stdout; stdout = stdout || process.stdout;
var args = ['--debug-brk'].concat(argv), var args = ['--debug-brk'].concat(argv),

63
lib/_stream_duplex.js

@ -0,0 +1,63 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// a duplex stream is just a stream that is both readable and writable.
// Since JS doesn't have multiple prototypal inheritance, this class
// prototypally inherits from Readable, and then parasitically from
// Writable.
module.exports = Duplex;
var util = require('util');
var Readable = require('_stream_readable');
var Writable = require('_stream_writable');
util.inherits(Duplex, Readable);
Object.keys(Writable.prototype).forEach(function(method) {
if (!Duplex.prototype[method])
Duplex.prototype[method] = Writable.prototype[method];
});
function Duplex(options) {
if (!(this instanceof Duplex))
return new Duplex(options);
Readable.call(this, options);
Writable.call(this, options);
this.allowHalfOpen = true;
if (options && options.allowHalfOpen === false)
this.allowHalfOpen = false;
this.once('end', onend);
}
// the no-half-open enforcer
function onend() {
// if we allow half-open state, or if the writable side ended,
// then we're ok.
if (this.allowHalfOpen || this._writableState.ended)
return;
// no more data can be written.
// But allow more writes to happen in this tick.
process.nextTick(this.end.bind(this));
}

31
test/simple/test-zlib-destroy.js → lib/_stream_passthrough.js

@ -19,18 +19,23 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common'); // a passthrough stream.
var assert = require('assert'); // basically just the most minimal sort of Transform stream.
var zlib = require('zlib'); // Every written chunk gets output as-is.
['Deflate', 'Inflate', 'Gzip', 'Gunzip', 'DeflateRaw', 'InflateRaw', 'Unzip'] module.exports = PassThrough;
.forEach(function (name) {
var a = false;
var zStream = new zlib[name]();
zStream.on('close', function () {
a = true;
});
zStream.destroy();
assert.equal(a, true, name+'#destroy() must emit \'close\''); var Transform = require('_stream_transform');
}); var util = require('util');
util.inherits(PassThrough, Transform);
function PassThrough(options) {
if (!(this instanceof PassThrough))
return new PassThrough(options);
Transform.call(this, options);
}
PassThrough.prototype._transform = function(chunk, output, cb) {
cb(null, chunk);
};

752
lib/_stream_readable.js

@ -0,0 +1,752 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
module.exports = Readable;
Readable.ReadableState = ReadableState;
var Stream = require('stream');
var util = require('util');
var assert = require('assert');
var StringDecoder;
util.inherits(Readable, Stream);
function ReadableState(options, stream) {
options = options || {};
// the argument passed to this._read(n,cb)
this.bufferSize = options.hasOwnProperty('bufferSize') ?
options.bufferSize : 16 * 1024;
// the point at which it stops calling _read() to fill the buffer
this.highWaterMark = options.hasOwnProperty('highWaterMark') ?
options.highWaterMark : 16 * 1024;
// the minimum number of bytes to buffer before emitting 'readable'
// default to pushing everything out as fast as possible.
this.lowWaterMark = options.hasOwnProperty('lowWaterMark') ?
options.lowWaterMark : 0;
// cast to ints.
assert(typeof this.bufferSize === 'number');
assert(typeof this.lowWaterMark === 'number');
assert(typeof this.highWaterMark === 'number');
this.bufferSize = ~~this.bufferSize;
this.lowWaterMark = ~~this.lowWaterMark;
this.highWaterMark = ~~this.highWaterMark;
assert(this.bufferSize >= 0);
assert(this.lowWaterMark >= 0);
assert(this.highWaterMark >= this.lowWaterMark,
this.highWaterMark + '>=' + this.lowWaterMark);
this.buffer = [];
this.length = 0;
this.pipes = null;
this.pipesCount = 0;
this.flowing = false;
this.ended = false;
this.endEmitted = false;
this.reading = false;
this.sync = false;
this.onread = function(er, data) {
onread(stream, er, data);
};
// whenever we return null, then we set a flag to say
// that we're awaiting a 'readable' event emission.
this.needReadable = false;
this.emittedReadable = false;
// when piping, we only care about 'readable' events that happen
// after read()ing all the bytes and not getting any pushback.
this.ranOut = false;
// the number of writers that are awaiting a drain event in .pipe()s
this.awaitDrain = 0;
this.pipeChunkSize = null;
this.decoder = null;
if (options.encoding) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this.decoder = new StringDecoder(options.encoding);
}
}
function Readable(options) {
if (!(this instanceof Readable))
return new Readable(options);
this._readableState = new ReadableState(options, this);
// legacy
this.readable = true;
Stream.apply(this);
}
// backwards compatibility.
Readable.prototype.setEncoding = function(enc) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this._readableState.decoder = new StringDecoder(enc);
};
function howMuchToRead(n, state) {
if (state.length === 0 && state.ended)
return 0;
if (isNaN(n) || n === null)
return state.length;
if (n <= 0)
return 0;
// don't have that much. return null, unless we've ended.
if (n > state.length) {
if (!state.ended) {
state.needReadable = true;
return 0;
} else
return state.length;
}
return n;
}
// you can override either this method, or _read(n, cb) below.
Readable.prototype.read = function(n) {
var state = this._readableState;
var nOrig = n;
if (typeof n !== 'number' || n > 0)
state.emittedReadable = false;
n = howMuchToRead(n, state);
// if we've ended, and we're now clear, then finish it up.
if (n === 0 && state.ended) {
endReadable(this);
return null;
}
// All the actual chunk generation logic needs to be
// *below* the call to _read. The reason is that in certain
// synthetic stream cases, such as passthrough streams, _read
// may be a completely synchronous operation which may change
// the state of the read buffer, providing enough data when
// before there was *not* enough.
//
// So, the steps are:
// 1. Figure out what the state of things will be after we do
// a read from the buffer.
//
// 2. If that resulting state will trigger a _read, then call _read.
// Note that this may be asynchronous, or synchronous. Yes, it is
// deeply ugly to write APIs this way, but that still doesn't mean
// that the Readable class should behave improperly, as streams are
// designed to be sync/async agnostic.
// Take note if the _read call is sync or async (ie, if the read call
// has returned yet), so that we know whether or not it's safe to emit
// 'readable' etc.
//
// 3. Actually pull the requested chunks out of the buffer and return.
// if we need a readable event, then we need to do some reading.
var doRead = state.needReadable;
// if we currently have less than the highWaterMark, then also read some
if (state.length - n <= state.highWaterMark)
doRead = true;
// however, if we've ended, then there's no point, and if we're already
// reading, then it's unnecessary.
if (state.ended || state.reading)
doRead = false;
if (doRead) {
state.reading = true;
state.sync = true;
// if the length is currently zero, then we *need* a readable event.
if (state.length === 0)
state.needReadable = true;
// call internal read method
this._read(state.bufferSize, state.onread);
state.sync = false;
}
// If _read called its callback synchronously, then `reading`
// will be false, and we need to re-evaluate how much data we
// can return to the user.
if (doRead && !state.reading)
n = howMuchToRead(nOrig, state);
var ret;
if (n > 0)
ret = fromList(n, state.buffer, state.length, !!state.decoder);
else
ret = null;
if (ret === null || ret.length === 0) {
state.needReadable = true;
n = 0;
}
state.length -= n;
// If we have nothing in the buffer, then we want to know
// as soon as we *do* get something into the buffer.
if (state.length === 0 && !state.ended)
state.needReadable = true;
return ret;
};
function onread(stream, er, chunk) {
var state = stream._readableState;
var sync = state.sync;
state.reading = false;
if (er)
return stream.emit('error', er);
if (!chunk || !chunk.length) {
// eof
state.ended = true;
if (state.decoder) {
chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
state.length += chunk.length;
}
}
// if we've ended and we have some data left, then emit
// 'readable' now to make sure it gets picked up.
if (!sync) {
if (state.length > 0) {
state.needReadable = false;
if (!state.emittedReadable) {
state.emittedReadable = true;
stream.emit('readable');
}
} else
endReadable(stream);
}
return;
}
if (state.decoder)
chunk = state.decoder.write(chunk);
// update the buffer info.
if (chunk) {
state.length += chunk.length;
state.buffer.push(chunk);
}
// if we haven't gotten enough to pass the lowWaterMark,
// and we haven't ended, then don't bother telling the user
// that it's time to read more data. Otherwise, emitting 'readable'
// probably will trigger another stream.read(), which can trigger
// another _read(n,cb) before this one returns!
if (state.length <= state.lowWaterMark) {
state.reading = true;
stream._read(state.bufferSize, state.onread);
return;
}
if (state.needReadable && !sync) {
state.needReadable = false;
if (!state.emittedReadable) {
state.emittedReadable = true;
stream.emit('readable');
}
}
}
// abstract method. to be overridden in specific implementation classes.
// call cb(er, data) where data is <= n in length.
// for virtual (non-string, non-buffer) streams, "length" is somewhat
// arbitrary, and perhaps not very meaningful.
Readable.prototype._read = function(n, cb) {
process.nextTick(function() {
cb(new Error('not implemented'));
});
};
Readable.prototype.pipe = function(dest, pipeOpts) {
var src = this;
var state = this._readableState;
switch (state.pipesCount) {
case 0:
state.pipes = dest;
break;
case 1:
state.pipes = [state.pipes, dest];
break;
default:
state.pipes.push(dest);
break;
}
state.pipesCount += 1;
if ((!pipeOpts || pipeOpts.end !== false) &&
dest !== process.stdout &&
dest !== process.stderr) {
src.once('end', onend);
dest.on('unpipe', function(readable) {
if (readable === src)
src.removeListener('end', onend);
});
}
if (pipeOpts && pipeOpts.chunkSize)
state.pipeChunkSize = pipeOpts.chunkSize;
function onend() {
dest.end();
}
// when the dest drains, it reduces the awaitDrain counter
// on the source. This would be more elegant with a .once()
// handler in flow(), but adding and removing repeatedly is
// too slow.
var ondrain = pipeOnDrain(src);
dest.on('drain', ondrain);
dest.on('unpipe', function(readable) {
if (readable === src)
dest.removeListener('drain', ondrain);
// if the reader is waiting for a drain event from this
// specific writer, then it would cause it to never start
// flowing again.
// So, if this is awaiting a drain, then we just call it now.
// If we don't know, then assume that we are waiting for one.
if (!dest._writableState || dest._writableState.needDrain)
ondrain();
});
// if the dest has an error, then stop piping into it.
// however, don't suppress the throwing behavior for this.
dest.once('error', function(er) {
unpipe();
if (dest.listeners('error').length === 0)
dest.emit('error', er);
});
// if the dest emits close, then presumably there's no point writing
// to it any more.
dest.on('close', unpipe);
dest.on('finish', function() {
dest.removeListener('close', unpipe);
});
function unpipe() {
src.unpipe(dest);
}
// tell the dest that it's being piped to
dest.emit('pipe', src);
// start the flow if it hasn't been started already.
if (!state.flowing) {
// the handler that waits for readable events after all
// the data gets sucked out in flow.
// This would be easier to follow with a .once() handler
// in flow(), but that is too slow.
this.on('readable', pipeOnReadable);
state.flowing = true;
process.nextTick(function() {
flow(src);
});
}
return dest;
};
function pipeOnDrain(src) {
return function() {
var dest = this;
var state = src._readableState;
state.awaitDrain--;
if (state.awaitDrain === 0)
flow(src);
};
}
function flow(src) {
var state = src._readableState;
var chunk;
state.awaitDrain = 0;
function write(dest, i, list) {
var written = dest.write(chunk);
if (false === written) {
state.awaitDrain++;
}
}
while (state.pipesCount &&
null !== (chunk = src.read(state.pipeChunkSize))) {
if (state.pipesCount === 1)
write(state.pipes, 0, null);
else
state.pipes.forEach(write);
src.emit('data', chunk);
// if anyone needs a drain, then we have to wait for that.
if (state.awaitDrain > 0)
return;
}
// if every destination was unpiped, either before entering this
// function, or in the while loop, then stop flowing.
//
// NB: This is a pretty rare edge case.
if (state.pipesCount === 0) {
state.flowing = false;
// if there were data event listeners added, then switch to old mode.
if (src.listeners('data').length)
emitDataEvents(src);
return;
}
// at this point, no one needed a drain, so we just ran out of data
// on the next readable event, start it over again.
state.ranOut = true;
}
function pipeOnReadable() {
if (this._readableState.ranOut) {
this._readableState.ranOut = false;
flow(this);
}
}
Readable.prototype.unpipe = function(dest) {
var state = this._readableState;
// if we're not piping anywhere, then do nothing.
if (state.pipesCount === 0)
return this;
// just one destination. most common case.
if (state.pipesCount === 1) {
// passed in one, but it's not the right one.
if (dest && dest !== state.pipes)
return this;
if (!dest)
dest = state.pipes;
// got a match.
state.pipes = null;
state.pipesCount = 0;
this.removeListener('readable', pipeOnReadable);
if (dest)
dest.emit('unpipe', this);
return this;
}
// slow case. multiple pipe destinations.
if (!dest) {
// remove all.
var dests = state.pipes;
var len = state.pipesCount;
state.pipes = null;
state.pipesCount = 0;
this.removeListener('readable', pipeOnReadable);
for (var i = 0; i < len; i++)
dests[i].emit('unpipe', this);
return this;
}
// try to find the right one.
var i = state.pipes.indexOf(dest);
if (i === -1)
return this;
state.pipes.splice(i, 1);
state.pipesCount -= 1;
if (state.pipesCount === 1)
state.pipes = state.pipes[0];
dest.emit('unpipe', this);
return this;
};
// kludge for on('data', fn) consumers. Sad.
// This is *not* part of the new readable stream interface.
// It is an ugly unfortunate mess of history.
Readable.prototype.on = function(ev, fn) {
// https://github.com/isaacs/readable-stream/issues/16
// if we're already flowing, then no need to set up data events.
if (ev === 'data' && !this._readableState.flowing)
emitDataEvents(this);
return Stream.prototype.on.call(this, ev, fn);
};
Readable.prototype.addListener = Readable.prototype.on;
// pause() and resume() are remnants of the legacy readable stream API
// If the user uses them, then switch into old mode.
Readable.prototype.resume = function() {
emitDataEvents(this);
this.read(0);
this.emit('resume');
};
Readable.prototype.pause = function() {
emitDataEvents(this, true);
this.emit('pause');
};
function emitDataEvents(stream, startPaused) {
var state = stream._readableState;
if (state.flowing) {
// https://github.com/isaacs/readable-stream/issues/16
throw new Error('Cannot switch to old mode now.');
}
var paused = startPaused || false;
var readable = false;
// convert to an old-style stream.
stream.readable = true;
stream.pipe = Stream.prototype.pipe;
stream.on = stream.addEventListener = Stream.prototype.on;
stream.on('readable', function() {
readable = true;
var c;
while (!paused && (null !== (c = stream.read())))
stream.emit('data', c);
if (c === null) {
readable = false;
stream._readableState.needReadable = true;
}
});
stream.pause = function() {
paused = true;
this.emit('pause');
};
stream.resume = function() {
paused = false;
if (readable)
process.nextTick(function() {
stream.emit('readable');
});
else
this.read(0);
this.emit('resume');
};
// now make it start, just in case it hadn't already.
stream.emit('readable');
}
// wrap an old-style stream as the async data source.
// This is *not* part of the readable stream interface.
// It is an ugly unfortunate mess of history.
Readable.prototype.wrap = function(stream) {
var state = this._readableState;
var paused = false;
var self = this;
stream.on('end', function() {
state.ended = true;
if (state.decoder) {
var chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
state.length += chunk.length;
}
}
if (state.length > 0)
self.emit('readable');
else
endReadable(self);
});
stream.on('data', function(chunk) {
if (state.decoder)
chunk = state.decoder.write(chunk);
if (!chunk || !chunk.length)
return;
state.buffer.push(chunk);
state.length += chunk.length;
self.emit('readable');
// if not consumed, then pause the stream.
if (state.length > state.lowWaterMark && !paused) {
paused = true;
stream.pause();
}
});
// proxy all the other methods.
// important when wrapping filters and duplexes.
for (var i in stream) {
if (typeof stream[i] === 'function' &&
typeof this[i] === 'undefined') {
this[i] = function(method) { return function() {
return stream[method].apply(stream, arguments);
}}(i);
}
}
// proxy certain important events.
var events = ['error', 'close', 'destroy', 'pause', 'resume'];
events.forEach(function(ev) {
stream.on(ev, self.emit.bind(self, ev));
});
// consume some bytes. if not all is consumed, then
// pause the underlying stream.
this.read = function(n) {
if (state.length === 0) {
state.needReadable = true;
return null;
}
if (isNaN(n) || n <= 0)
n = state.length;
if (n > state.length) {
if (!state.ended) {
state.needReadable = true;
return null;
} else
n = state.length;
}
var ret = fromList(n, state.buffer, state.length, !!state.decoder);
state.length -= n;
if (state.length === 0 && !state.ended)
state.needReadable = true;
if (state.length <= state.lowWaterMark && paused) {
stream.resume();
paused = false;
}
if (state.length === 0 && state.ended)
endReadable(this);
return ret;
};
};
// exposed for testing purposes only.
Readable._fromList = fromList;
// Pluck off n bytes from an array of buffers.
// Length is the combined lengths of all the buffers in the list.
function fromList(n, list, length, stringMode) {
var ret;
// nothing in the list, definitely empty.
if (list.length === 0) {
return null;
}
if (length === 0)
ret = null;
else if (!n || n >= length) {
// read it all, truncate the array.
if (stringMode)
ret = list.join('');
else
ret = Buffer.concat(list, length);
list.length = 0;
} else {
// read just some of it.
if (n < list[0].length) {
// just take a part of the first list item.
// slice is the same for buffers and strings.
var buf = list[0];
ret = buf.slice(0, n);
list[0] = buf.slice(n);
} else if (n === list[0].length) {
// first list is a perfect match
ret = list.shift();
} else {
// complex case.
// we have enough to cover it, but it spans past the first buffer.
if (stringMode)
ret = '';
else
ret = new Buffer(n);
var c = 0;
for (var i = 0, l = list.length; i < l && c < n; i++) {
var buf = list[0];
var cpy = Math.min(n - c, buf.length);
if (stringMode)
ret += buf.slice(0, cpy);
else
buf.copy(ret, c, 0, cpy);
if (cpy < buf.length)
list[0] = buf.slice(cpy);
else
list.shift();
c += cpy;
}
}
}
return ret;
}
function endReadable(stream) {
var state = stream._readableState;
if (state.endEmitted)
return;
state.ended = true;
state.endEmitted = true;
process.nextTick(function() {
stream.readable = false;
stream.emit('end');
});
}

231
lib/_stream_transform.js

@ -0,0 +1,231 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// a transform stream is a readable/writable stream where you do
// something with the data. Sometimes it's called a "filter",
// but that's not a great name for it, since that implies a thing where
// some bits pass through, and others are simply ignored. (That would
// be a valid example of a transform, of course.)
//
// While the output is causally related to the input, it's not a
// necessarily symmetric or synchronous transformation. For example,
// a zlib stream might take multiple plain-text writes(), and then
// emit a single compressed chunk some time in the future.
//
// Here's how this works:
//
// The Transform stream has all the aspects of the readable and writable
// stream classes. When you write(chunk), that calls _write(chunk,cb)
// internally, and returns false if there's a lot of pending writes
// buffered up. When you call read(), that calls _read(n,cb) until
// there's enough pending readable data buffered up.
//
// In a transform stream, the written data is placed in a buffer. When
// _read(n,cb) is called, it transforms the queued up data, calling the
// buffered _write cb's as it consumes chunks. If consuming a single
// written chunk would result in multiple output chunks, then the first
// outputted bit calls the readcb, and subsequent chunks just go into
// the read buffer, and will cause it to emit 'readable' if necessary.
//
// This way, back-pressure is actually determined by the reading side,
// since _read has to be called to start processing a new chunk. However,
// a pathological inflate type of transform can cause excessive buffering
// here. For example, imagine a stream where every byte of input is
// interpreted as an integer from 0-255, and then results in that many
// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in
// 1kb of data being output. In this case, you could write a very small
// amount of input, and end up with a very large amount of output. In
// such a pathological inflating mechanism, there'd be no way to tell
// the system to stop doing the transform. A single 4MB write could
// cause the system to run out of memory.
//
// However, even in such a pathological case, only a single written chunk
// would be consumed, and then the rest would wait (un-transformed) until
// the results of the previous transformed chunk were consumed. Because
// the transform happens on-demand, it will only transform as much as is
// necessary to fill the readable buffer to the specified lowWaterMark.
module.exports = Transform;
var Duplex = require('_stream_duplex');
var util = require('util');
util.inherits(Transform, Duplex);
function TransformState(stream) {
this.buffer = [];
this.transforming = false;
this.pendingReadCb = null;
this.output = function(chunk) {
stream._output(chunk);
};
}
function Transform(options) {
if (!(this instanceof Transform))
return new Transform(options);
Duplex.call(this, options);
// bind output so that it can be passed around as a regular function.
var stream = this;
// the queue of _write chunks that are pending being transformed
var ts = this._transformState = new TransformState(stream);
// when the writable side finishes, then flush out anything remaining.
this.once('finish', function() {
if ('function' === typeof this._flush)
this._flush(ts.output, function(er) {
done(stream, er);
});
else
done(stream);
});
}
// This is the part where you do stuff!
// override this function in implementation classes.
// 'chunk' is an input chunk.
//
// Call `output(newChunk)` to pass along transformed output
// to the readable side. You may call 'output' zero or more times.
//
// Call `cb(err)` when you are done with this chunk. If you pass
// an error, then that'll put the hurt on the whole operation. If you
// never call cb(), then you'll never get another chunk.
Transform.prototype._transform = function(chunk, output, cb) {
throw new Error('not implemented');
};
Transform.prototype._write = function(chunk, cb) {
var ts = this._transformState;
var rs = this._readableState;
ts.buffer.push([chunk, cb]);
// no need for auto-pull if already in the midst of one.
if (ts.transforming)
return;
// now we have something to transform, if we were waiting for it.
// kick off a _read to pull it in.
if (ts.pendingReadCb) {
var readcb = ts.pendingReadCb;
ts.pendingReadCb = null;
this._read(0, readcb);
}
// if we weren't waiting for it, but nothing is queued up, then
// still kick off a transform, just so it's there when the user asks.
var doRead = rs.needReadable || rs.length <= rs.highWaterMark;
if (doRead && !rs.reading) {
var ret = this.read(0);
if (ret !== null)
return cb(new Error('invalid stream transform state'));
}
};
Transform.prototype._read = function(n, readcb) {
var ws = this._writableState;
var rs = this._readableState;
var ts = this._transformState;
if (ts.pendingReadCb)
throw new Error('_read while _read already in progress');
ts.pendingReadCb = readcb;
// if there's nothing pending, then we just wait.
// if we're already transforming, then also just hold on a sec.
// we've already stashed the readcb, so we can come back later
// when we have something to transform
if (ts.buffer.length === 0 || ts.transforming)
return;
// go ahead and transform that thing, now that someone wants it
var req = ts.buffer.shift();
var chunk = req[0];
var writecb = req[1];
ts.transforming = true;
this._transform(chunk, ts.output, function(er, data) {
ts.transforming = false;
if (data)
ts.output(data);
writecb(er);
});
};
Transform.prototype._output = function(chunk) {
if (!chunk || !chunk.length)
return;
// if we've got a pending readcb, then just call that,
// and let Readable take care of it. If not, then we fill
// the readable buffer ourselves, and emit whatever's needed.
var ts = this._transformState;
var readcb = ts.pendingReadCb;
if (readcb) {
ts.pendingReadCb = null;
readcb(null, chunk);
return;
}
// otherwise, it's up to us to fill the rs buffer.
var rs = this._readableState;
var len = rs.length;
rs.buffer.push(chunk);
rs.length += chunk.length;
if (rs.needReadable) {
rs.needReadable = false;
this.emit('readable');
}
};
function done(stream, er) {
if (er)
return stream.emit('error', er);
// if there's nothing in the write buffer, then that means
// that nothing more will ever be provided
var ws = stream._writableState;
var rs = stream._readableState;
var ts = stream._transformState;
if (ws.length)
throw new Error('calling transform done when ws.length != 0');
if (ts.transforming)
throw new Error('calling transform done when still transforming');
// if we were waiting on a read, let them know that it isn't coming.
var readcb = ts.pendingReadCb;
if (readcb)
return readcb();
rs.ended = true;
// we may have gotten a 'null' read before, and since there is
// no more data coming from the writable side, we need to emit
// now so that the consumer knows to pick up the tail bits.
if (rs.length && rs.needReadable)
stream.emit('readable');
else if (rs.length === 0)
stream.emit('end');
}

257
lib/_stream_writable.js

@ -0,0 +1,257 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// A bit simpler than readable streams.
// Implement an async ._write(chunk, cb), and it'll handle all
// the drain event emission and buffering.
module.exports = Writable;
Writable.WritableState = WritableState;
var util = require('util');
var assert = require('assert');
var Stream = require('stream');
util.inherits(Writable, Stream);
function WritableState(options, stream) {
options = options || {};
// the point at which write() starts returning false
this.highWaterMark = options.hasOwnProperty('highWaterMark') ?
options.highWaterMark : 16 * 1024;
// the point that it has to get to before we call _write(chunk,cb)
// default to pushing everything out as fast as possible.
this.lowWaterMark = options.hasOwnProperty('lowWaterMark') ?
options.lowWaterMark : 0;
// cast to ints.
assert(typeof this.lowWaterMark === 'number');
assert(typeof this.highWaterMark === 'number');
this.lowWaterMark = ~~this.lowWaterMark;
this.highWaterMark = ~~this.highWaterMark;
assert(this.lowWaterMark >= 0);
assert(this.highWaterMark >= this.lowWaterMark,
this.highWaterMark + '>=' + this.lowWaterMark);
this.needDrain = false;
// at the start of calling end()
this.ending = false;
// when end() has been called, and returned
this.ended = false;
// when 'finish' has emitted
this.finished = false;
// when 'finish' is being emitted
this.finishing = false;
// should we decode strings into buffers before passing to _write?
// this is here so that some node-core streams can optimize string
// handling at a lower level.
this.decodeStrings = options.hasOwnProperty('decodeStrings') ?
options.decodeStrings : true;
// not an actual buffer we keep track of, but a measurement
// of how much we're waiting to get pushed to some underlying
// socket or file.
this.length = 0;
// a flag to see when we're in the middle of a write.
this.writing = false;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick.
this.sync = false;
// the callback that's passed to _write(chunk,cb)
this.onwrite = function(er) {
onwrite(stream, er);
};
// the callback that the user supplies to write(chunk,encoding,cb)
this.writecb = null;
// the amount that is being written when _write is called.
this.writelen = 0;
this.buffer = [];
}
function Writable(options) {
// Writable ctor is applied to Duplexes, though they're not
// instanceof Writable, they're instanceof Readable.
if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
return new Writable(options);
this._writableState = new WritableState(options, this);
// legacy.
this.writable = true;
Stream.call(this);
}
// Override this method or _write(chunk, cb)
Writable.prototype.write = function(chunk, encoding, cb) {
var state = this._writableState;
if (typeof encoding === 'function') {
cb = encoding;
encoding = null;
}
if (state.ended) {
var er = new Error('write after end');
if (typeof cb === 'function')
cb(er);
this.emit('error', er);
return;
}
var l = chunk.length;
if (false === state.decodeStrings)
chunk = [chunk, encoding || 'utf8'];
else if (typeof chunk === 'string' || encoding) {
chunk = new Buffer(chunk + '', encoding);
l = chunk.length;
}
state.length += l;
var ret = state.length < state.highWaterMark;
if (ret === false)
state.needDrain = true;
// if we're already writing something, then just put this
// in the queue, and wait our turn.
if (state.writing) {
state.buffer.push([chunk, cb]);
return ret;
}
state.writing = true;
state.sync = true;
state.writelen = l;
state.writecb = cb;
this._write(chunk, state.onwrite);
state.sync = false;
return ret;
};
function onwrite(stream, er) {
var state = stream._writableState;
var sync = state.sync;
var cb = state.writecb;
var l = state.writelen;
state.writing = false;
state.writelen = null;
state.writecb = null;
if (er) {
if (cb) {
if (sync)
process.nextTick(function() {
cb(er);
});
else
cb(er);
}
// backwards compatibility. still emit if there was a cb.
stream.emit('error', er);
return;
}
state.length -= l;
if (cb) {
// don't call the cb until the next tick if we're in sync mode.
// also, defer if we're about to write some more right now.
if (sync || state.buffer.length)
process.nextTick(cb);
else
cb();
}
if (state.length === 0 && (state.ended || state.ending) &&
!state.finished && !state.finishing) {
// emit 'finish' at the very end.
state.finishing = true;
stream.emit('finish');
state.finished = true;
return;
}
// if there's something in the buffer waiting, then do that, too.
if (state.buffer.length) {
var chunkCb = state.buffer.shift();
var chunk = chunkCb[0];
cb = chunkCb[1];
if (false === state.decodeStrings)
l = chunk[0].length;
else
l = chunk.length;
state.writelen = l;
state.writecb = cb;
state.writechunk = chunk;
state.writing = true;
stream._write(chunk, state.onwrite);
}
if (state.length <= state.lowWaterMark && state.needDrain) {
// Must force callback to be called on nextTick, so that we don't
// emit 'drain' before the write() consumer gets the 'false' return
// value, and has a chance to attach a 'drain' listener.
process.nextTick(function() {
if (!state.needDrain)
return;
state.needDrain = false;
stream.emit('drain');
});
}
}
Writable.prototype._write = function(chunk, cb) {
process.nextTick(function() {
cb(new Error('not implemented'));
});
};
Writable.prototype.end = function(chunk, encoding) {
var state = this._writableState;
// ignore unnecessary end() calls.
if (state.ending || state.ended || state.finished)
return;
state.ending = true;
if (chunk)
this.write(chunk, encoding);
else if (state.length === 0 && !state.finishing && !state.finished) {
state.finishing = true;
this.emit('finish');
state.finished = true;
}
state.ended = true;
};

3
lib/child_process.js

@ -110,7 +110,6 @@ var handleConversion = {
'net.Socket': { 'net.Socket': {
send: function(message, socket) { send: function(message, socket) {
// pause socket so no data is lost, will be resumed later // pause socket so no data is lost, will be resumed later
socket.pause();
// if the socket wsa created by net.Server // if the socket wsa created by net.Server
if (socket.server) { if (socket.server) {
@ -142,7 +141,6 @@ var handleConversion = {
got: function(message, handle, emit) { got: function(message, handle, emit) {
var socket = new net.Socket({handle: handle}); var socket = new net.Socket({handle: handle});
socket.readable = socket.writable = true; socket.readable = socket.writable = true;
socket.pause();
// if the socket was created by net.Server we will track the socket // if the socket was created by net.Server we will track the socket
if (message.key) { if (message.key) {
@ -153,7 +151,6 @@ var handleConversion = {
} }
emit(socket); emit(socket);
socket.resume();
} }
} }
}; };

79
lib/crypto.js

@ -37,6 +37,9 @@ try {
var crypto = false; var crypto = false;
} }
var stream = require('stream');
var util = require('util');
// This is here because many functions accepted binary strings without // This is here because many functions accepted binary strings without
// any explicit encoding in older versions of node, and we don't want // any explicit encoding in older versions of node, and we don't want
// to break them unnecessarily. // to break them unnecessarily.
@ -148,12 +151,24 @@ exports.createCredentials = function(options, context) {
exports.createHash = exports.Hash = Hash; exports.createHash = exports.Hash = Hash;
function Hash(algorithm) { function Hash(algorithm, options) {
if (!(this instanceof Hash)) if (!(this instanceof Hash))
return new Hash(algorithm); return new Hash(algorithm);
this._binding = new binding.Hash(algorithm); this._binding = new binding.Hash(algorithm);
stream.Transform.call(this, options);
} }
util.inherits(Hash, stream.Transform);
Hash.prototype._transform = function(chunk, output, callback) {
this._binding.update(chunk);
callback();
};
Hash.prototype._flush = function(output, callback) {
output(this._binding.digest());
callback();
};
Hash.prototype.update = function(data, encoding) { Hash.prototype.update = function(data, encoding) {
encoding = encoding || exports.DEFAULT_ENCODING; encoding = encoding || exports.DEFAULT_ENCODING;
@ -174,16 +189,20 @@ Hash.prototype.digest = function(outputEncoding) {
exports.createHmac = exports.Hmac = Hmac; exports.createHmac = exports.Hmac = Hmac;
function Hmac(hmac, key) { function Hmac(hmac, key, options) {
if (!(this instanceof Hmac)) if (!(this instanceof Hmac))
return new Hmac(hmac, key); return new Hmac(hmac, key);
this._binding = new binding.Hmac(); this._binding = new binding.Hmac();
this._binding.init(hmac, toBuf(key)); this._binding.init(hmac, toBuf(key));
stream.Transform.call(this, options);
} }
util.inherits(Hmac, stream.Transform);
Hmac.prototype.update = Hash.prototype.update; Hmac.prototype.update = Hash.prototype.update;
Hmac.prototype.digest = Hash.prototype.digest; Hmac.prototype.digest = Hash.prototype.digest;
Hmac.prototype._flush = Hash.prototype._flush;
Hmac.prototype._transform = Hash.prototype._transform;
function getDecoder(decoder, encoding) { function getDecoder(decoder, encoding) {
@ -194,15 +213,28 @@ function getDecoder(decoder, encoding) {
exports.createCipher = exports.Cipher = Cipher; exports.createCipher = exports.Cipher = Cipher;
function Cipher(cipher, password) { function Cipher(cipher, password, options) {
if (!(this instanceof Cipher)) if (!(this instanceof Cipher))
return new Cipher(cipher, password); return new Cipher(cipher, password);
this._binding = new binding.Cipher; this._binding = new binding.Cipher;
this._binding.init(cipher, toBuf(password)); this._binding.init(cipher, toBuf(password));
this._decoder = null; this._decoder = null;
stream.Transform.call(this, options);
} }
util.inherits(Cipher, stream.Transform);
Cipher.prototype._transform = function(chunk, output, callback) {
output(this._binding.update(chunk));
callback();
};
Cipher.prototype._flush = function(output, callback) {
output(this._binding.final());
callback();
};
Cipher.prototype.update = function(data, inputEncoding, outputEncoding) { Cipher.prototype.update = function(data, inputEncoding, outputEncoding) {
inputEncoding = inputEncoding || exports.DEFAULT_ENCODING; inputEncoding = inputEncoding || exports.DEFAULT_ENCODING;
@ -241,15 +273,20 @@ Cipher.prototype.setAutoPadding = function(ap) {
exports.createCipheriv = exports.Cipheriv = Cipheriv; exports.createCipheriv = exports.Cipheriv = Cipheriv;
function Cipheriv(cipher, key, iv) { function Cipheriv(cipher, key, iv, options) {
if (!(this instanceof Cipheriv)) if (!(this instanceof Cipheriv))
return new Cipheriv(cipher, key, iv); return new Cipheriv(cipher, key, iv);
this._binding = new binding.Cipher(); this._binding = new binding.Cipher();
this._binding.initiv(cipher, toBuf(key), toBuf(iv)); this._binding.initiv(cipher, toBuf(key), toBuf(iv));
this._decoder = null; this._decoder = null;
stream.Transform.call(this, options);
} }
util.inherits(Cipheriv, stream.Transform);
Cipheriv.prototype._transform = Cipher.prototype._transform;
Cipheriv.prototype._flush = Cipher.prototype._flush;
Cipheriv.prototype.update = Cipher.prototype.update; Cipheriv.prototype.update = Cipher.prototype.update;
Cipheriv.prototype.final = Cipher.prototype.final; Cipheriv.prototype.final = Cipher.prototype.final;
Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
@ -257,16 +294,21 @@ Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
exports.createDecipher = exports.Decipher = Decipher; exports.createDecipher = exports.Decipher = Decipher;
function Decipher(cipher, password) { function Decipher(cipher, password, options) {
if (!(this instanceof Decipher)) if (!(this instanceof Decipher))
return new Decipher(cipher, password); return new Decipher(cipher, password);
this._binding = new binding.Decipher; this._binding = new binding.Decipher;
this._binding.init(cipher, toBuf(password)); this._binding.init(cipher, toBuf(password));
this._decoder = null; this._decoder = null;
stream.Transform.call(this, options);
} }
util.inherits(Decipher, stream.Transform);
Decipher.prototype._transform = Cipher.prototype._transform;
Decipher.prototype._flush = Cipher.prototype._flush;
Decipher.prototype.update = Cipher.prototype.update; Decipher.prototype.update = Cipher.prototype.update;
Decipher.prototype.final = Cipher.prototype.final; Decipher.prototype.final = Cipher.prototype.final;
Decipher.prototype.finaltol = Cipher.prototype.final; Decipher.prototype.finaltol = Cipher.prototype.final;
@ -275,16 +317,21 @@ Decipher.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
exports.createDecipheriv = exports.Decipheriv = Decipheriv; exports.createDecipheriv = exports.Decipheriv = Decipheriv;
function Decipheriv(cipher, key, iv) { function Decipheriv(cipher, key, iv, options) {
if (!(this instanceof Decipheriv)) if (!(this instanceof Decipheriv))
return new Decipheriv(cipher, key, iv); return new Decipheriv(cipher, key, iv);
this._binding = new binding.Decipher; this._binding = new binding.Decipher;
this._binding.initiv(cipher, toBuf(key), toBuf(iv)); this._binding.initiv(cipher, toBuf(key), toBuf(iv));
this._decoder = null; this._decoder = null;
stream.Transform.call(this, options);
} }
util.inherits(Decipheriv, stream.Transform);
Decipheriv.prototype._transform = Cipher.prototype._transform;
Decipheriv.prototype._flush = Cipher.prototype._flush;
Decipheriv.prototype.update = Cipher.prototype.update; Decipheriv.prototype.update = Cipher.prototype.update;
Decipheriv.prototype.final = Cipher.prototype.final; Decipheriv.prototype.final = Cipher.prototype.final;
Decipheriv.prototype.finaltol = Cipher.prototype.final; Decipheriv.prototype.finaltol = Cipher.prototype.final;
@ -293,16 +340,23 @@ Decipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
exports.createSign = exports.Sign = Sign; exports.createSign = exports.Sign = Sign;
function Sign(algorithm) { function Sign(algorithm, options) {
if (!(this instanceof Sign)) if (!(this instanceof Sign))
return new Sign(algorithm); return new Sign(algorithm);
this._binding = new binding.Sign(); this._binding = new binding.Sign();
this._binding.init(algorithm); this._binding.init(algorithm);
stream.Writable.call(this, options);
} }
util.inherits(Sign, stream.Writable);
Sign.prototype.update = Hash.prototype.update; Sign.prototype._write = function(chunk, callback) {
this._binding.update(chunk);
callback();
};
Sign.prototype.update = Hash.prototype.update;
Sign.prototype.sign = function(key, encoding) { Sign.prototype.sign = function(key, encoding) {
encoding = encoding || exports.DEFAULT_ENCODING; encoding = encoding || exports.DEFAULT_ENCODING;
@ -317,17 +371,20 @@ Sign.prototype.sign = function(key, encoding) {
exports.createVerify = exports.Verify = Verify; exports.createVerify = exports.Verify = Verify;
function Verify(algorithm) { function Verify(algorithm, options) {
if (!(this instanceof Verify)) if (!(this instanceof Verify))
return new Verify(algorithm); return new Verify(algorithm);
this._binding = new binding.Verify; this._binding = new binding.Verify;
this._binding.init(algorithm); this._binding.init(algorithm);
}
stream.Writable.call(this, options);
}
Verify.prototype.update = Hash.prototype.update; util.inherits(Verify, stream.Writable);
Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update;
Verify.prototype.verify = function(object, signature, sigEncoding) { Verify.prototype.verify = function(object, signature, sigEncoding) {
sigEncoding = sigEncoding || exports.DEFAULT_ENCODING; sigEncoding = sigEncoding || exports.DEFAULT_ENCODING;

423
lib/fs.js

@ -34,6 +34,9 @@ var fs = exports;
var Stream = require('stream').Stream; var Stream = require('stream').Stream;
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var Readable = Stream.Readable;
var Writable = Stream.Writable;
var kMinPoolSpace = 128; var kMinPoolSpace = 128;
var kPoolSize = 40 * 1024; var kPoolSize = 40 * 1024;
@ -1386,34 +1389,30 @@ fs.createReadStream = function(path, options) {
return new ReadStream(path, options); return new ReadStream(path, options);
}; };
var ReadStream = fs.ReadStream = function(path, options) { util.inherits(ReadStream, Readable);
if (!(this instanceof ReadStream)) return new ReadStream(path, options); fs.ReadStream = ReadStream;
Stream.call(this);
var self = this;
this.path = path;
this.fd = null;
this.readable = true;
this.paused = false;
this.flags = 'r'; function ReadStream(path, options) {
this.mode = 438; /*=0666*/ if (!(this instanceof ReadStream))
this.bufferSize = 64 * 1024; return new ReadStream(path, options);
options = options || {}; // a little bit bigger buffer and water marks by default
options = util._extend({
bufferSize: 64 * 1024,
lowWaterMark: 16 * 1024,
highWaterMark: 64 * 1024
}, options || {});
// Mixin options into this Readable.call(this, options);
var keys = Object.keys(options);
for (var index = 0, length = keys.length; index < length; index++) {
var key = keys[index];
this[key] = options[key];
}
assertEncoding(this.encoding); this.path = path;
this.fd = options.hasOwnProperty('fd') ? options.fd : null;
this.flags = options.hasOwnProperty('flags') ? options.flags : 'r';
this.mode = options.hasOwnProperty('mode') ? options.mode : 438; /*=0666*/
if (this.encoding) this.setEncoding(this.encoding); this.start = options.hasOwnProperty('start') ? options.start : undefined;
this.end = options.hasOwnProperty('start') ? options.end : undefined;
this.pos = undefined;
if (this.start !== undefined) { if (this.start !== undefined) {
if ('number' !== typeof this.start) { if ('number' !== typeof this.start) {
@ -1432,41 +1431,40 @@ var ReadStream = fs.ReadStream = function(path, options) {
this.pos = this.start; this.pos = this.start;
} }
if (this.fd !== null) { if (typeof this.fd !== 'number')
process.nextTick(function() { this.open();
self._read();
});
return;
}
fs.open(this.path, this.flags, this.mode, function(err, fd) { this.on('end', function() {
if (err) { this.destroy();
self.emit('error', err); });
self.readable = false; }
fs.FileReadStream = fs.ReadStream; // support the legacy name
ReadStream.prototype.open = function() {
var self = this;
fs.open(this.path, this.flags, this.mode, function(er, fd) {
if (er) {
self.destroy();
self.emit('error', er);
return; return;
} }
self.fd = fd; self.fd = fd;
self.emit('open', fd); self.emit('open', fd);
self._read(); // start the flow of data.
self.read();
}); });
}; };
util.inherits(ReadStream, Stream);
fs.FileReadStream = fs.ReadStream; // support the legacy name
ReadStream.prototype.setEncoding = function(encoding) {
assertEncoding(encoding);
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
this._decoder = new StringDecoder(encoding);
};
ReadStream.prototype._read = function(n, cb) {
if (typeof this.fd !== 'number')
return this.once('open', function() {
this._read(n, cb);
});
ReadStream.prototype._read = function() { if (this.destroyed)
var self = this; return;
if (!this.readable || this.paused || this.reading) return;
this.reading = true;
if (!pool || pool.length - pool.used < kMinPoolSpace) { if (!pool || pool.length - pool.used < kMinPoolSpace) {
// discard the old pool. Can't add to the free list because // discard the old pool. Can't add to the free list because
@ -1475,149 +1473,110 @@ ReadStream.prototype._read = function() {
allocNewPool(); allocNewPool();
} }
// Grab another reference to the pool in the case that while we're in the // Grab another reference to the pool in the case that while we're
// thread pool another read() finishes up the pool, and allocates a new // in the thread pool another read() finishes up the pool, and
// one. // allocates a new one.
var thisPool = pool; var thisPool = pool;
var toRead = Math.min(pool.length - pool.used, ~~this.bufferSize); var toRead = Math.min(pool.length - pool.used, n);
var start = pool.used; var start = pool.used;
if (this.pos !== undefined) { if (this.pos !== undefined)
toRead = Math.min(this.end - this.pos + 1, toRead); toRead = Math.min(this.end - this.pos + 1, toRead);
}
function afterRead(err, bytesRead) { // already read everything we were supposed to read!
self.reading = false; // treat as EOF.
if (err) { if (toRead <= 0)
fs.close(self.fd, function() { return cb();
self.fd = null;
self.emit('error', err);
self.readable = false;
});
return;
}
if (bytesRead === 0) { // the actual read.
if (this._decoder) { var self = this;
var ret = this._decoder.end(); fs.read(this.fd, pool, pool.used, toRead, this.pos, onread);
if (ret)
this.emit('data', ret);
}
self.emit('end');
self.destroy();
return;
}
var b = thisPool.slice(start, start + bytesRead);
// Possible optimizition here? // move the pool positions, and internal position for reading.
// Reclaim some bytes if bytesRead < toRead? if (this.pos !== undefined)
// Would need to ensure that pool === thisPool. this.pos += toRead;
pool.used += toRead;
// do not emit events if the stream is paused function onread(er, bytesRead) {
if (self.paused) { if (er) {
self.buffer = b; self.destroy();
return; return cb(er);
} }
// do not emit events anymore after we declared the stream unreadable var b = null;
if (!self.readable) return; if (bytesRead > 0)
b = thisPool.slice(start, start + bytesRead);
self._emitData(b); cb(null, b);
self._read();
} }
fs.read(this.fd, pool, pool.used, toRead, this.pos, afterRead);
if (this.pos !== undefined) {
this.pos += toRead;
}
pool.used += toRead;
}; };
ReadStream.prototype._emitData = function(d) { ReadStream.prototype.destroy = function() {
if (this._decoder) { if (this.destroyed)
var string = this._decoder.write(d); return;
if (string.length) this.emit('data', string); this.destroyed = true;
} else {
this.emit('data', d); if ('number' === typeof this.fd)
} this.close();
}; };
ReadStream.prototype.destroy = function() { ReadStream.prototype.close = function(cb) {
if (cb)
this.once('close', cb);
if (this.closed || 'number' !== typeof this.fd) {
if ('number' !== typeof this.fd)
this.once('open', close);
return process.nextTick(this.emit.bind(this, 'close'));
}
this.closed = true;
var self = this; var self = this;
close();
if (!this.readable) return;
this.readable = false;
function close() { function close() {
fs.close(self.fd, function(err) { fs.close(self.fd, function(er) {
if (err) { if (er)
self.emit('error', err); self.emit('error', er);
} else { else
self.emit('close'); self.emit('close');
}
}); });
} self.fd = null;
if (this.fd === null) {
this.addListener('open', close);
} else {
close();
} }
}; };
ReadStream.prototype.pause = function() {
this.paused = true;
};
ReadStream.prototype.resume = function() {
this.paused = false;
if (this.buffer) {
var buffer = this.buffer;
this.buffer = null;
this._emitData(buffer);
}
// hasn't opened yet.
if (null == this.fd) return;
this._read();
};
fs.createWriteStream = function(path, options) { fs.createWriteStream = function(path, options) {
return new WriteStream(path, options); return new WriteStream(path, options);
}; };
var WriteStream = fs.WriteStream = function(path, options) { util.inherits(WriteStream, Writable);
if (!(this instanceof WriteStream)) return new WriteStream(path, options); fs.WriteStream = WriteStream;
function WriteStream(path, options) {
if (!(this instanceof WriteStream))
return new WriteStream(path, options);
Stream.call(this); // a little bit bigger buffer and water marks by default
options = util._extend({
bufferSize: 64 * 1024,
lowWaterMark: 16 * 1024,
highWaterMark: 64 * 1024
}, options || {});
Writable.call(this, options);
this.path = path; this.path = path;
this.fd = null; this.fd = null;
this.writable = true;
this.flags = 'w';
this.encoding = 'binary';
this.mode = 438; /*=0666*/
this.bytesWritten = 0;
options = options || {}; this.fd = options.hasOwnProperty('fd') ? options.fd : null;
this.flags = options.hasOwnProperty('flags') ? options.flags : 'w';
this.mode = options.hasOwnProperty('mode') ? options.mode : 438; /*=0666*/
// Mixin options into this this.start = options.hasOwnProperty('start') ? options.start : undefined;
var keys = Object.keys(options); this.pos = undefined;
for (var index = 0, length = keys.length; index < length; index++) { this.bytesWritten = 0;
var key = keys[index];
this[key] = options[key];
}
if (this.start !== undefined) { if (this.start !== undefined) {
if ('number' !== typeof this.start) { if ('number' !== typeof this.start) {
@ -1630,154 +1589,54 @@ var WriteStream = fs.WriteStream = function(path, options) {
this.pos = this.start; this.pos = this.start;
} }
this.busy = false; if ('number' !== typeof this.fd)
this._queue = []; this.open();
if (this.fd === null) { // dispose on finish.
this._open = fs.open; this.once('finish', this.close);
this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); }
this.flush();
}
};
util.inherits(WriteStream, Stream);
fs.FileWriteStream = fs.WriteStream; // support the legacy name fs.FileWriteStream = fs.WriteStream; // support the legacy name
WriteStream.prototype.flush = function() {
if (this.busy) return;
var self = this;
var args = this._queue.shift();
if (!args) {
if (this.drainable) { this.emit('drain'); }
return;
}
this.busy = true;
var method = args.shift(),
cb = args.pop();
args.push(function(err) {
self.busy = false;
if (err) {
self.writable = false;
function emit() {
self.fd = null;
if (cb) cb(err);
self.emit('error', err);
}
if (self.fd === null) {
emit();
} else {
fs.close(self.fd, emit);
}
return;
}
if (method == fs.write) { WriteStream.prototype.open = function() {
self.bytesWritten += arguments[1]; fs.open(this.path, this.flags, this.mode, function(er, fd) {
if (cb) { if (er) {
// write callback this.destroy();
cb(null, arguments[1]); this.emit('error', er);
}
} else if (method === self._open) {
// save reference for file pointer
self.fd = arguments[1];
self.emit('open', self.fd);
} else if (method === fs.close) {
// stop flushing after close
if (cb) {
cb(null);
}
self.emit('close');
return; return;
} }
self.flush(); this.fd = fd;
}); this.emit('open', fd);
}.bind(this));
// Inject the file pointer
if (method !== self._open) {
args.unshift(this.fd);
}
method.apply(this, args);
}; };
WriteStream.prototype.write = function(data) {
if (!this.writable) {
this.emit('error', new Error('stream not writable'));
return false;
}
this.drainable = true; WriteStream.prototype._write = function(data, cb) {
if (!Buffer.isBuffer(data))
var cb; return this.emit('error', new Error('Invalid data'));
if (typeof(arguments[arguments.length - 1]) == 'function') {
cb = arguments[arguments.length - 1];
}
if (!Buffer.isBuffer(data)) { if (typeof this.fd !== 'number')
var encoding = 'utf8'; return this.once('open', this._write.bind(this, data, cb));
if (typeof(arguments[1]) == 'string') encoding = arguments[1];
assertEncoding(encoding);
data = new Buffer('' + data, encoding);
}
this._queue.push([fs.write, data, 0, data.length, this.pos, cb]); var self = this;
fs.write(this.fd, data, 0, data.length, this.pos, function(er, bytes) {
if (er) {
self.destroy();
return cb(er);
}
self.bytesWritten += bytes;
cb();
});
if (this.pos !== undefined) { if (this.pos !== undefined)
this.pos += data.length; this.pos += data.length;
}
this.flush();
return false;
};
WriteStream.prototype.end = function(data, encoding, cb) {
if (typeof(data) === 'function') {
cb = data;
} else if (typeof(encoding) === 'function') {
cb = encoding;
this.write(data);
} else if (arguments.length > 0) {
this.write(data, encoding);
}
this.writable = false;
this._queue.push([fs.close, cb]);
this.flush();
}; };
WriteStream.prototype.destroy = function() {
var self = this;
if (!this.writable) return;
this.writable = false;
function close() {
fs.close(self.fd, function(err) {
if (err) {
self.emit('error', err);
} else {
self.emit('close');
}
});
}
if (this.fd === null) { WriteStream.prototype.destroy = ReadStream.prototype.destroy;
this.addListener('open', close); WriteStream.prototype.close = ReadStream.prototype.close;
} else {
close();
}
};
// There is no shutdown() for files. // There is no shutdown() for files.
WriteStream.prototype.destroySoon = WriteStream.prototype.end; WriteStream.prototype.destroySoon = WriteStream.prototype.end;

134
lib/http.js

@ -114,19 +114,30 @@ function parserOnHeadersComplete(info) {
return skipBody; return skipBody;
} }
// XXX This is a mess.
// TODO: http.Parser should be a Writable emits request/response events.
function parserOnBody(b, start, len) { function parserOnBody(b, start, len) {
var parser = this; var parser = this;
var slice = b.slice(start, start + len); var stream = parser.incoming;
if (parser.incoming._paused || parser.incoming._pendings.length) { var rs = stream._readableState;
parser.incoming._pendings.push(slice); var socket = stream.socket;
} else {
parser.incoming._emitData(slice); // pretend this was the result of a stream._read call.
if (len > 0) {
var slice = b.slice(start, start + len);
rs.onread(null, slice);
} }
if (rs.length >= rs.highWaterMark)
socket.pause();
} }
function parserOnMessageComplete() { function parserOnMessageComplete() {
var parser = this; var parser = this;
parser.incoming.complete = true; var stream = parser.incoming;
var socket = stream.socket;
stream.complete = true;
// Emit any trailing headers. // Emit any trailing headers.
var headers = parser._headers; var headers = parser._headers;
@ -140,19 +151,13 @@ function parserOnMessageComplete() {
parser._url = ''; parser._url = '';
} }
if (!parser.incoming.upgrade) { if (!stream.upgrade)
// For upgraded connections, also emit this after parser.execute // For upgraded connections, also emit this after parser.execute
if (parser.incoming._paused || parser.incoming._pendings.length) { stream._readableState.onread(null, null);
parser.incoming._pendings.push(END_OF_FILE);
} else {
parser.incoming.readable = false;
parser.incoming._emitEnd();
}
}
if (parser.socket.readable) { if (parser.socket.readable) {
// force to read the next incoming message // force to read the next incoming message
parser.socket.resume(); socket.resume();
} }
} }
@ -263,9 +268,13 @@ function utcDate() {
/* Abstract base class for ServerRequest and ClientResponse. */ /* Abstract base class for ServerRequest and ClientResponse. */
function IncomingMessage(socket) { function IncomingMessage(socket) {
Stream.call(this); Stream.Readable.call(this);
// XXX This implementation is kind of all over the place
// When the parser emits body chunks, they go in this list.
// _read() pulls them out, and when it finds EOF, it ends.
this._pendings = [];
// TODO Remove one of these eventually.
this.socket = socket; this.socket = socket;
this.connection = socket; this.connection = socket;
@ -276,77 +285,49 @@ function IncomingMessage(socket) {
this.readable = true; this.readable = true;
this._paused = false;
this._pendings = []; this._pendings = [];
this._pendingIndex = 0;
this._endEmitted = false;
// request (server) only // request (server) only
this.url = ''; this.url = '';
this.method = null; this.method = null;
// response (client) only // response (client) only
this.statusCode = null; this.statusCode = null;
this.client = this.socket; this.client = this.socket;
// flag for backwards compatibility grossness.
this._consuming = false;
} }
util.inherits(IncomingMessage, Stream); util.inherits(IncomingMessage, Stream.Readable);
exports.IncomingMessage = IncomingMessage; exports.IncomingMessage = IncomingMessage;
IncomingMessage.prototype.destroy = function(error) { IncomingMessage.prototype.read = function(n) {
this.socket.destroy(error); this._consuming = true;
return Stream.Readable.prototype.read.call(this, n);
}; };
IncomingMessage.prototype.setEncoding = function(encoding) { IncomingMessage.prototype._read = function(n, callback) {
var StringDecoder = require('string_decoder').StringDecoder; // lazy load // We actually do almost nothing here, because the parserOnBody
this._decoder = new StringDecoder(encoding); // function fills up our internal buffer directly. However, we
}; // do need to unpause the underlying socket so that it flows.
if (!this.socket.readable)
return callback(null, null);
IncomingMessage.prototype.pause = function() { else
this._paused = true; this.socket.resume();
this.socket.pause();
}; };
IncomingMessage.prototype.resume = function() { IncomingMessage.prototype.destroy = function(error) {
this._paused = false; this.socket.destroy(error);
if (this.socket) {
this.socket.resume();
}
this._emitPending();
}; };
IncomingMessage.prototype._emitPending = function(callback) {
if (this._pendings.length) {
var self = this;
process.nextTick(function() {
while (!self._paused && self._pendings.length) {
var chunk = self._pendings.shift();
if (chunk !== END_OF_FILE) {
assert(Buffer.isBuffer(chunk));
self._emitData(chunk);
} else {
assert(self._pendings.length === 0);
self.readable = false;
self._emitEnd();
}
}
if (callback) {
callback();
}
});
} else if (callback) {
callback();
}
};
IncomingMessage.prototype._emitData = function(d) { IncomingMessage.prototype._emitData = function(d) {
@ -1016,7 +997,7 @@ ServerResponse.prototype.writeHead = function(statusCode) {
// don't keep alive connections where the client expects 100 Continue // don't keep alive connections where the client expects 100 Continue
// but we sent a final status; they may put extra bytes on the wire. // but we sent a final status; they may put extra bytes on the wire.
if (this._expect_continue && ! this._sent100) { if (this._expect_continue && !this._sent100) {
this.shouldKeepAlive = false; this.shouldKeepAlive = false;
} }
@ -1321,11 +1302,10 @@ function socketCloseListener() {
// Socket closed before we emitted 'end' below. // Socket closed before we emitted 'end' below.
req.res.emit('aborted'); req.res.emit('aborted');
var res = req.res; var res = req.res;
req.res._emitPending(function() { res.on('end', function() {
res._emitEnd();
res.emit('close'); res.emit('close');
res = null;
}); });
res._readableState.onread(null, null);
} else if (!req.res && !req._hadError) { } else if (!req.res && !req._hadError) {
// This socket error fired before we started to // This socket error fired before we started to
// receive a response. The error needs to // receive a response. The error needs to
@ -1428,11 +1408,13 @@ function socketOnData(d, start, end) {
} }
// client
function parserOnIncomingClient(res, shouldKeepAlive) { function parserOnIncomingClient(res, shouldKeepAlive) {
var parser = this; var parser = this;
var socket = this.socket; var socket = this.socket;
var req = socket._httpMessage; var req = socket._httpMessage;
// propogate "domain" setting... // propogate "domain" setting...
if (req.domain && !res.domain) { if (req.domain && !res.domain) {
debug('setting "res.domain"'); debug('setting "res.domain"');
@ -1480,15 +1462,21 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
DTRACE_HTTP_CLIENT_RESPONSE(socket, req); DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
COUNTER_HTTP_CLIENT_RESPONSE(); COUNTER_HTTP_CLIENT_RESPONSE();
req.emit('response', res);
req.res = res; req.res = res;
res.req = req; res.req = req;
var handled = req.emit('response', res);
res.on('end', responseOnEnd); res.on('end', responseOnEnd);
// If the user did not listen for the 'response' event, then they
// can't possibly read the data, so we .resume() it into the void
// so that the socket doesn't hang there in a paused state.
if (!handled)
res.resume();
return isHeadResponse; return isHeadResponse;
} }
// client
function responseOnEnd() { function responseOnEnd() {
var res = this; var res = this;
var req = res.req; var req = res.req;
@ -1784,7 +1772,7 @@ function connectionListener(socket) {
incoming.push(req); incoming.push(req);
var res = new ServerResponse(req); var res = new ServerResponse(req);
debug('server response shouldKeepAlive: ' + shouldKeepAlive);
res.shouldKeepAlive = shouldKeepAlive; res.shouldKeepAlive = shouldKeepAlive;
DTRACE_HTTP_SERVER_REQUEST(req, socket); DTRACE_HTTP_SERVER_REQUEST(req, socket);
COUNTER_HTTP_SERVER_REQUEST(); COUNTER_HTTP_SERVER_REQUEST();
@ -1806,6 +1794,12 @@ function connectionListener(socket) {
incoming.shift(); incoming.shift();
// if the user never called req.read(), and didn't pipe() or
// .resume() or .on('data'), then we call req.resume() so that the
// bytes will be pulled off the wire.
if (!req._consuming)
req.resume();
res.detachSocket(socket); res.detachSocket(socket);
if (res._last) { if (res._last) {

470
lib/net.js

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
var events = require('events'); var events = require('events');
var Stream = require('stream'); var stream = require('stream');
var timers = require('timers'); var timers = require('timers');
var util = require('util'); var util = require('util');
var assert = require('assert'); var assert = require('assert');
@ -42,16 +42,16 @@ function createTCP() {
} }
/* Bit flags for socket._flags */
var FLAG_GOT_EOF = 1 << 0;
var FLAG_SHUTDOWN = 1 << 1;
var FLAG_DESTROY_SOON = 1 << 2;
var FLAG_SHUTDOWN_QUEUED = 1 << 3;
var debug; var debug;
if (process.env.NODE_DEBUG && /net/.test(process.env.NODE_DEBUG)) { if (process.env.NODE_DEBUG && /net/.test(process.env.NODE_DEBUG)) {
debug = function(x) { console.error('NET:', x); }; var pid = process.pid;
debug = function(x) {
// if console is not set up yet, then skip this.
if (!console.error)
return;
console.error('NET: %d', pid,
util.format.apply(util, arguments).slice(0, 500));
};
} else { } else {
debug = function() { }; debug = function() { };
} }
@ -110,12 +110,8 @@ function normalizeConnectArgs(args) {
exports._normalizeConnectArgs = normalizeConnectArgs; exports._normalizeConnectArgs = normalizeConnectArgs;
/* called when creating new Socket, or when re-using a closed Socket */ // called when creating new Socket, or when re-using a closed Socket
function initSocketHandle(self) { function initSocketHandle(self) {
self._pendingWriteReqs = 0;
self._flags = 0;
self._connectQueueSize = 0;
self.destroyed = false; self.destroyed = false;
self.errorEmitted = false; self.errorEmitted = false;
self.bytesRead = 0; self.bytesRead = 0;
@ -131,8 +127,6 @@ function initSocketHandle(self) {
function Socket(options) { function Socket(options) {
if (!(this instanceof Socket)) return new Socket(options); if (!(this instanceof Socket)) return new Socket(options);
Stream.call(this);
switch (typeof options) { switch (typeof options) {
case 'number': case 'number':
options = { fd: options }; // Legacy interface. options = { fd: options }; // Legacy interface.
@ -142,7 +136,10 @@ function Socket(options) {
break; break;
} }
if (typeof options.fd === 'undefined') { this.readable = this.writable = false;
if (options.handle) {
this._handle = options.handle; // private
} else if (typeof options.fd === 'undefined') {
this._handle = options && options.handle; // private this._handle = options && options.handle; // private
} else { } else {
this._handle = createPipe(); this._handle = createPipe();
@ -150,17 +147,105 @@ function Socket(options) {
this.readable = this.writable = true; this.readable = this.writable = true;
} }
this.onend = null;
// shut down the socket when we're finished with it.
this.on('finish', onSocketFinish);
this.on('_socketEnd', onSocketEnd);
initSocketHandle(this); initSocketHandle(this);
this.allowHalfOpen = options && options.allowHalfOpen;
this._pendingWrite = null;
stream.Duplex.call(this, options);
// handle strings directly
this._writableState.decodeStrings = false;
// default to *not* allowing half open sockets
this.allowHalfOpen = options && options.allowHalfOpen || false;
// if we have a handle, then start the flow of data into the
// buffer. if not, then this will happen when we connect
if (this._handle && (!options || options.readable !== false))
this.read(0);
}
util.inherits(Socket, stream.Duplex);
// the user has called .end(), and all the bytes have been
// sent out to the other side.
// If allowHalfOpen is false, or if the readable side has
// ended already, then destroy.
// If allowHalfOpen is true, then we need to do a shutdown,
// so that only the writable side will be cleaned up.
function onSocketFinish() {
debug('onSocketFinish');
if (this._readableState.ended) {
debug('oSF: ended, destroy', this._readableState);
return this.destroy();
}
debug('oSF: not ended, call shutdown()');
// otherwise, just shutdown, or destroy() if not possible
if (!this._handle.shutdown)
return this.destroy();
var shutdownReq = this._handle.shutdown();
if (!shutdownReq)
return this._destroy(errnoException(errno, 'shutdown'));
shutdownReq.oncomplete = afterShutdown;
}
function afterShutdown(status, handle, req) {
var self = handle.owner;
debug('afterShutdown destroyed=%j', self.destroyed,
self._readableState);
// callback may come after call to destroy.
if (self.destroyed)
return;
if (self._readableState.ended) {
debug('readableState ended, destroying');
self.destroy();
} else {
self.once('_socketEnd', self.destroy);
}
} }
util.inherits(Socket, Stream);
// the EOF has been received, and no more bytes are coming.
// if the writable side has ended already, then clean everything
// up.
function onSocketEnd() {
// XXX Should not have to do as much crap in this function.
// ended should already be true, since this is called *after*
// the EOF errno and onread has returned null to the _read cb.
debug('onSocketEnd', this._readableState);
this._readableState.ended = true;
if (this._readableState.endEmitted) {
this.readable = false;
} else {
this.once('end', function() {
this.readable = false;
});
this.read(0);
}
if (!this.allowHalfOpen)
this.destroySoon();
}
exports.Socket = Socket; exports.Socket = Socket;
exports.Stream = Socket; // Legacy naming. exports.Stream = Socket; // Legacy naming.
Socket.prototype.listen = function() { Socket.prototype.listen = function() {
debug('socket.listen');
var self = this; var self = this;
self.on('connection', arguments[0]); self.on('connection', arguments[0]);
listen(self, null, null, null); listen(self, null, null, null);
@ -230,96 +315,62 @@ Object.defineProperty(Socket.prototype, 'readyState', {
Object.defineProperty(Socket.prototype, 'bufferSize', { Object.defineProperty(Socket.prototype, 'bufferSize', {
get: function() { get: function() {
if (this._handle) { if (this._handle) {
return this._handle.writeQueueSize + this._connectQueueSize; return this._handle.writeQueueSize;
} }
} }
}); });
Socket.prototype.pause = function() { // Just call handle.readStart until we have enough in the buffer
this._paused = true; Socket.prototype._read = function(n, callback) {
if (this._handle && !this._connecting) { debug('_read');
this._handle.readStop(); if (this._connecting || !this._handle) {
debug('_read wait for connection');
this.once('connect', this._read.bind(this, n, callback));
return;
} }
};
assert(callback === this._readableState.onread);
assert(this._readableState.reading = true);
Socket.prototype.resume = function() { if (!this._handle.reading) {
this._paused = false; debug('Socket._read readStart');
if (this._handle && !this._connecting) { this._handle.reading = true;
this._handle.readStart(); var r = this._handle.readStart();
if (r)
this._destroy(errnoException(errno, 'read'));
} else {
debug('readStart already has been called.');
} }
}; };
Socket.prototype.end = function(data, encoding) { Socket.prototype.end = function(data, encoding) {
if (this._connecting && ((this._flags & FLAG_SHUTDOWN_QUEUED) == 0)) { stream.Duplex.prototype.end.call(this, data, encoding);
// still connecting, add data to buffer
if (data) this.write(data, encoding);
this.writable = false;
this._flags |= FLAG_SHUTDOWN_QUEUED;
}
if (!this.writable) return;
this.writable = false; this.writable = false;
if (data) this.write(data, encoding);
DTRACE_NET_STREAM_END(this); DTRACE_NET_STREAM_END(this);
if (!this.readable) { // just in case we're waiting for an EOF.
this.destroySoon(); if (!this._readableState.endEmitted)
} else { this.read(0);
this._flags |= FLAG_SHUTDOWN; return;
var shutdownReq = this._handle.shutdown();
if (!shutdownReq) {
this._destroy(errnoException(errno, 'shutdown'));
return false;
}
shutdownReq.oncomplete = afterShutdown;
}
return true;
}; };
function afterShutdown(status, handle, req) {
var self = handle.owner;
assert.ok(self._flags & FLAG_SHUTDOWN);
assert.ok(!self.writable);
// callback may come after call to destroy.
if (self.destroyed) {
return;
}
if (self._flags & FLAG_GOT_EOF || !self.readable) {
self._destroy();
} else {
}
}
Socket.prototype.destroySoon = function() { Socket.prototype.destroySoon = function() {
this.writable = false; if (this.writable)
this._flags |= FLAG_DESTROY_SOON; this.end();
if (this._pendingWriteReqs == 0) {
this._destroy();
}
};
Socket.prototype._connectQueueCleanUp = function(exception) { if (this._writableState.finishing || this._writableState.finished)
this._connecting = false; this.destroy();
this._connectQueueSize = 0; else
this._connectQueue = null; this.once('finish', this.destroy);
}; };
Socket.prototype._destroy = function(exception, cb) { Socket.prototype._destroy = function(exception, cb) {
debug('destroy');
var self = this; var self = this;
function fireErrorCallbacks() { function fireErrorCallbacks() {
@ -333,13 +384,12 @@ Socket.prototype._destroy = function(exception, cb) {
}; };
if (this.destroyed) { if (this.destroyed) {
debug('already destroyed, fire error callbacks');
fireErrorCallbacks(); fireErrorCallbacks();
return; return;
} }
self._connectQueueCleanUp(); self._connecting = false;
debug('destroy');
this.readable = this.writable = false; this.readable = this.writable = false;
@ -347,6 +397,8 @@ Socket.prototype._destroy = function(exception, cb) {
debug('close'); debug('close');
if (this._handle) { if (this._handle) {
if (this !== process.stderr)
debug('close handle');
this._handle.close(); this._handle.close();
this._handle.onread = noop; this._handle.onread = noop;
this._handle = null; this._handle = null;
@ -355,6 +407,7 @@ Socket.prototype._destroy = function(exception, cb) {
fireErrorCallbacks(); fireErrorCallbacks();
process.nextTick(function() { process.nextTick(function() {
debug('emit close');
self.emit('close', exception ? true : false); self.emit('close', exception ? true : false);
}); });
@ -362,6 +415,7 @@ Socket.prototype._destroy = function(exception, cb) {
if (this.server) { if (this.server) {
COUNTER_NET_SERVER_CONNECTION_CLOSE(this); COUNTER_NET_SERVER_CONNECTION_CLOSE(this);
debug('has server');
this.server._connections--; this.server._connections--;
if (this.server._emitCloseIfDrained) { if (this.server._emitCloseIfDrained) {
this.server._emitCloseIfDrained(); this.server._emitCloseIfDrained();
@ -371,10 +425,13 @@ Socket.prototype._destroy = function(exception, cb) {
Socket.prototype.destroy = function(exception) { Socket.prototype.destroy = function(exception) {
debug('destroy', exception);
this._destroy(exception); this._destroy(exception);
}; };
// This function is called whenever the handle gets a
// buffer, or when there's an error reading.
function onread(buffer, offset, length) { function onread(buffer, offset, length) {
var handle = this; var handle = this;
var self = handle.owner; var self = handle.owner;
@ -383,47 +440,56 @@ function onread(buffer, offset, length) {
timers.active(self); timers.active(self);
var end = offset + length; var end = offset + length;
debug('onread', global.errno, offset, length, end);
if (buffer) { if (buffer) {
// Emit 'data' event. debug('got data');
if (self._decoder) { // read success.
// Emit a string. // In theory (and in practice) calling readStop right now
var string = self._decoder.write(buffer.slice(offset, end)); // will prevent this from being called again until _read() gets
if (string.length) self.emit('data', string); // called again.
} else {
// Emit a slice. Attempt to avoid slicing the buffer if no one is // if we didn't get any bytes, that doesn't necessarily mean EOF.
// listening for 'data'. // wait for the next one.
if (self._events && self._events['data']) { if (offset === end) {
self.emit('data', buffer.slice(offset, end)); debug('not any data, keep waiting');
} return;
} }
// if it's not enough data, we'll just call handle.readStart()
// again right away.
self.bytesRead += length; self.bytesRead += length;
self._readableState.onread(null, buffer.slice(offset, end));
if (handle.reading && !self._readableState.reading) {
handle.reading = false;
debug('readStop');
var r = handle.readStop();
if (r)
self._destroy(errnoException(errno, 'read'));
}
// Optimization: emit the original buffer with end points // Optimization: emit the original buffer with end points
if (self.ondata) self.ondata(buffer, offset, end); if (self.ondata) self.ondata(buffer, offset, end);
} else if (errno == 'EOF') { } else if (errno == 'EOF') {
// EOF debug('EOF');
self.readable = false;
assert.ok(!(self._flags & FLAG_GOT_EOF)); if (self._readableState.length === 0)
self._flags |= FLAG_GOT_EOF; self.readable = false;
// We call destroy() before end(). 'close' not emitted until nextTick so if (self.onend) self.once('end', self.onend);
// the 'end' event will come first as required.
if (!self.writable) self._destroy();
if (!self.allowHalfOpen) self.end(); // send a null to the _read cb to signal the end of data.
if (self._decoder) { self._readableState.onread(null, null);
var ret = self._decoder.end();
if (ret) // internal end event so that we know that the actual socket
self.emit('data', ret); // is no longer readable, and we can start the shutdown
} // procedure. No need to wait for all the data to be consumed.
if (self._events && self._events['end']) self.emit('end'); self.emit('_socketEnd');
if (self.onend) self.onend();
} else { } else {
debug('error', errno);
// Error // Error
if (errno == 'ECONNRESET') { if (errno == 'ECONNRESET') {
self._destroy(); self._destroy();
@ -434,12 +500,6 @@ function onread(buffer, offset, length) {
} }
Socket.prototype.setEncoding = function(encoding) {
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
this._decoder = new StringDecoder(encoding);
};
Socket.prototype._getpeername = function() { Socket.prototype._getpeername = function() {
if (!this._handle || !this._handle.getpeername) { if (!this._handle || !this._handle.getpeername) {
return {}; return {};
@ -465,63 +525,39 @@ Socket.prototype.__defineGetter__('remotePort', function() {
}); });
/* Socket.prototype.write = function(chunk, encoding, cb) {
* Arguments data, [encoding], [cb] if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk))
*/ throw new TypeError('invalid data');
Socket.prototype.write = function(data, arg1, arg2) { return stream.Duplex.prototype.write.apply(this, arguments);
var encoding, cb; };
// parse arguments
if (arg1) {
if (typeof arg1 === 'string') {
encoding = arg1;
cb = arg2;
} else if (typeof arg1 === 'function') {
cb = arg1;
} else {
throw new Error('bad arg');
}
}
if (typeof data === 'string') { Socket.prototype._write = function(dataEncoding, cb) {
encoding = (encoding || 'utf8').toLowerCase(); assert(Array.isArray(dataEncoding));
switch (encoding) { var data = dataEncoding[0];
case 'utf8': var encoding = dataEncoding[1] || 'utf8';
case 'utf-8':
case 'ascii':
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
// This encoding can be handled in the binding layer.
break;
default: if (this !== process.stderr && this !== process.stdout)
data = new Buffer(data, encoding); debug('Socket._write');
}
} else if (!Buffer.isBuffer(data)) {
throw new TypeError('First argument must be a buffer or a string.');
}
// If we are still connecting, then buffer this for later. // If we are still connecting, then buffer this for later.
// The Writable logic will buffer up any more writes while
// waiting for this one to be done.
if (this._connecting) { if (this._connecting) {
this._connectQueueSize += data.length; debug('_write: waiting for connection');
if (this._connectQueue) { this._pendingWrite = dataEncoding;
this._connectQueue.push([data, encoding, cb]); this.once('connect', function() {
} else { debug('_write: connected now, try again');
this._connectQueue = [[data, encoding, cb]]; this._write(dataEncoding, cb);
} });
return false; return;
} }
this._pendingWrite = null;
return this._write(data, encoding, cb);
};
Socket.prototype._write = function(data, encoding, cb) {
timers.active(this); timers.active(this);
if (!this._handle) { if (!this._handle) {
debug('already destroyed');
this._destroy(new Error('This socket is closed.'), cb); this._destroy(new Error('This socket is closed.'), cb);
return false; return false;
} }
@ -550,39 +586,32 @@ Socket.prototype._write = function(data, encoding, cb) {
break; break;
default: default:
assert(0); writeReq = this._handle.writeBuffer(new Buffer(data, encoding));
break;
} }
} }
if (!writeReq || typeof writeReq !== 'object') { if (!writeReq || typeof writeReq !== 'object')
this._destroy(errnoException(errno, 'write'), cb); return this._destroy(errnoException(errno, 'write'), cb);
return false;
}
writeReq.oncomplete = afterWrite; writeReq.oncomplete = afterWrite;
writeReq.cb = cb; writeReq.cb = cb;
this._pendingWriteReqs++;
this._bytesDispatched += writeReq.bytes; this._bytesDispatched += writeReq.bytes;
return this._handle.writeQueueSize == 0;
}; };
Socket.prototype.__defineGetter__('bytesWritten', function() { Socket.prototype.__defineGetter__('bytesWritten', function() {
var bytes = this._bytesDispatched, var bytes = this._bytesDispatched,
connectQueue = this._connectQueue; state = this._writableState,
pending = this._pendingWrite;
if (connectQueue) { state.buffer.forEach(function(el) {
connectQueue.forEach(function(el) { bytes += Buffer.byteLength(el[0], el[1]);
var data = el[0]; });
if (Buffer.isBuffer(data)) {
bytes += data.length; if (pending)
} else { bytes += Buffer.byteLength(pending[0], pending[1]);
bytes += Buffer.byteLength(data, el[1]);
}
}, this);
}
return bytes; return bytes;
}); });
@ -590,30 +619,28 @@ Socket.prototype.__defineGetter__('bytesWritten', function() {
function afterWrite(status, handle, req) { function afterWrite(status, handle, req) {
var self = handle.owner; var self = handle.owner;
var state = self._writableState;
if (self !== process.stderr && self !== process.stdout)
debug('afterWrite', status, req);
// callback may come after call to destroy. // callback may come after call to destroy.
if (self.destroyed) { if (self.destroyed) {
debug('afterWrite destroyed');
return; return;
} }
if (status) { if (status) {
debug('write failure', errnoException(errno, 'write'));
self._destroy(errnoException(errno, 'write'), req.cb); self._destroy(errnoException(errno, 'write'), req.cb);
return; return;
} }
timers.active(self); timers.active(self);
self._pendingWriteReqs--; if (self !== process.stderr && self !== process.stdout)
debug('afterWrite call cb');
if (self._pendingWriteReqs == 0) {
self.emit('drain');
}
if (req.cb) req.cb(); req.cb.call(self);
if (self._pendingWriteReqs == 0 && self._flags & FLAG_DESTROY_SOON) {
self._destroy();
}
} }
@ -663,10 +690,21 @@ Socket.prototype.connect = function(options, cb) {
return Socket.prototype.connect.apply(this, args); return Socket.prototype.connect.apply(this, args);
} }
if (this.destroyed) {
this._readableState.reading = false;
this._readableState.ended = false;
this._writableState.ended = false;
this._writableState.ending = false;
this._writableState.finished = false;
this._writableState.finishing = false;
this.destroyed = false;
this._handle = null;
}
var self = this; var self = this;
var pipe = !!options.path; var pipe = !!options.path;
if (this.destroyed || !this._handle) { if (!this._handle) {
this._handle = pipe ? createPipe() : createTCP(); this._handle = pipe ? createPipe() : createTCP();
initSocketHandle(this); initSocketHandle(this);
} }
@ -755,28 +793,15 @@ function afterConnect(status, handle, req, readable, writable) {
self.writable = writable; self.writable = writable;
timers.active(self); timers.active(self);
if (self.readable && !self._paused) {
handle.readStart();
}
if (self._connectQueue) {
debug('Drain the connect queue');
var connectQueue = self._connectQueue;
for (var i = 0; i < connectQueue.length; i++) {
self._write.apply(self, connectQueue[i]);
}
self._connectQueueCleanUp();
}
self.emit('connect'); self.emit('connect');
if (self._flags & FLAG_SHUTDOWN_QUEUED) { // start the first read, or get an immediate EOF.
// end called before connected - call end now with no data // this doesn't actually consume any bytes, because len=0.
self._flags &= ~FLAG_SHUTDOWN_QUEUED; if (readable)
self.end(); self.read(0);
}
} else { } else {
self._connectQueueCleanUp(); self._connecting = false;
self._destroy(errnoException(errno, 'connect')); self._destroy(errnoException(errno, 'connect'));
} }
} }
@ -831,9 +856,9 @@ function Server(/* [ options, ] listener */) {
configurable: true, enumerable: true configurable: true, enumerable: true
}); });
this.allowHalfOpen = options.allowHalfOpen || false;
this._handle = null; this._handle = null;
this.allowHalfOpen = options.allowHalfOpen || false;
} }
util.inherits(Server, events.EventEmitter); util.inherits(Server, events.EventEmitter);
exports.Server = Server; exports.Server = Server;
@ -901,12 +926,14 @@ var createServerHandle = exports._createServerHandle =
Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
debug('listen2', address, port, addressType, backlog);
var self = this; var self = this;
var r = 0; var r = 0;
// If there is not yet a handle, we need to create one and bind. // If there is not yet a handle, we need to create one and bind.
// In the case of a server sent via IPC, we don't need to do this. // In the case of a server sent via IPC, we don't need to do this.
if (!self._handle) { if (!self._handle) {
debug('_listen2: create a handle');
self._handle = createServerHandle(address, port, addressType, fd); self._handle = createServerHandle(address, port, addressType, fd);
if (!self._handle) { if (!self._handle) {
var error = errnoException(errno, 'listen'); var error = errnoException(errno, 'listen');
@ -915,6 +942,8 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
}); });
return; return;
} }
} else {
debug('_listen2: have a handle already');
} }
self._handle.onconnection = onconnection; self._handle.onconnection = onconnection;
@ -1049,7 +1078,6 @@ function onconnection(clientHandle) {
}); });
socket.readable = socket.writable = true; socket.readable = socket.writable = true;
clientHandle.readStart();
self._connections++; self._connections++;
socket.server = self; socket.server = self;
@ -1086,11 +1114,17 @@ Server.prototype.close = function(cb) {
}; };
Server.prototype._emitCloseIfDrained = function() { Server.prototype._emitCloseIfDrained = function() {
debug('SERVER _emitCloseIfDrained');
var self = this; var self = this;
if (self._handle || self._connections) return; if (self._handle || self._connections) {
debug('SERVER handle? %j connections? %d',
!!self._handle, self._connections);
return;
}
process.nextTick(function() { process.nextTick(function() {
debug('SERVER: emit close');
self.emit('close'); self.emit('close');
}); });
}; };

4
lib/repl.js

@ -69,8 +69,8 @@ module.paths = require('module')._nodeModulePaths(module.filename);
exports.writer = util.inspect; exports.writer = util.inspect;
exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster', exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster',
'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net', 'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net', 'os',
'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream',
'string_decoder', 'tls', 'tty', 'url', 'util', 'vm', 'zlib']; 'string_decoder', 'tls', 'tty', 'url', 'util', 'vm', 'zlib'];

21
lib/stream.js

@ -19,17 +19,30 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
module.exports = Stream;
var events = require('events'); var events = require('events');
var util = require('util'); var util = require('util');
function Stream() {
events.EventEmitter.call(this);
}
util.inherits(Stream, events.EventEmitter); util.inherits(Stream, events.EventEmitter);
module.exports = Stream; Stream.Readable = require('_stream_readable');
Stream.Writable = require('_stream_writable');
Stream.Duplex = require('_stream_duplex');
Stream.Transform = require('_stream_transform');
Stream.PassThrough = require('_stream_passthrough');
// Backwards-compat with node 0.4.x // Backwards-compat with node 0.4.x
Stream.Stream = Stream; Stream.Stream = Stream;
// old-style streams. Note that the pipe method (the only relevant
// part of this class) is overridden in the Readable class.
function Stream() {
events.EventEmitter.call(this);
}
Stream.prototype.pipe = function(dest, options) { Stream.prototype.pipe = function(dest, options) {
var source = this; var source = this;

7
lib/string_decoder.js

@ -19,8 +19,15 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
function assertEncoding(encoding) {
if (encoding && !Buffer.isEncoding(encoding)) {
throw new Error('Unknown encoding: ' + encoding);
}
}
var StringDecoder = exports.StringDecoder = function(encoding) { var StringDecoder = exports.StringDecoder = function(encoding) {
this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, ''); this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, '');
assertEncoding(encoding);
switch (this.encoding) { switch (this.encoding) {
case 'utf8': case 'utf8':
// CESU-8 represents each of Surrogate Pair by 3-bytes // CESU-8 represents each of Surrogate Pair by 3-bytes

35
lib/tty.js

@ -40,42 +40,47 @@ exports.setRawMode = util.deprecate(function(flag) {
}, 'tty.setRawMode: Use `process.stdin.setRawMode()` instead.'); }, 'tty.setRawMode: Use `process.stdin.setRawMode()` instead.');
function ReadStream(fd) { function ReadStream(fd, options) {
if (!(this instanceof ReadStream)) return new ReadStream(fd); if (!(this instanceof ReadStream))
net.Socket.call(this, { return new ReadStream(fd, options);
options = util._extend({
highWaterMark: 0,
lowWaterMark: 0,
handle: new TTY(fd, true) handle: new TTY(fd, true)
}); }, options);
net.Socket.call(this, options);
this.readable = true; this.readable = true;
this.writable = false; this.writable = false;
this.isRaw = false; this.isRaw = false;
this.isTTY = true;
// this.read = function(orig) { return function(n) {
// var ret = orig.apply(this, arguments);
// console.trace('TTY read(' + n + ') -> ' + ret);
// return ret;
// } }(this.read);
} }
inherits(ReadStream, net.Socket); inherits(ReadStream, net.Socket);
exports.ReadStream = ReadStream; exports.ReadStream = ReadStream;
ReadStream.prototype.pause = function() {
return net.Socket.prototype.pause.call(this);
};
ReadStream.prototype.resume = function() {
return net.Socket.prototype.resume.call(this);
};
ReadStream.prototype.setRawMode = function(flag) { ReadStream.prototype.setRawMode = function(flag) {
flag = !!flag; flag = !!flag;
this._handle.setRawMode(flag); this._handle.setRawMode(flag);
this.isRaw = flag; this.isRaw = flag;
}; };
ReadStream.prototype.isTTY = true;
function WriteStream(fd) { function WriteStream(fd) {
if (!(this instanceof WriteStream)) return new WriteStream(fd); if (!(this instanceof WriteStream)) return new WriteStream(fd);
net.Socket.call(this, { net.Socket.call(this, {
handle: new TTY(fd, false) handle: new TTY(fd, false),
readable: false,
writable: true
}); });
this.readable = false; this.readable = false;

215
lib/zlib.js

@ -19,9 +19,10 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
var Transform = require('_stream_transform');
var binding = process.binding('zlib'); var binding = process.binding('zlib');
var util = require('util'); var util = require('util');
var Stream = require('stream');
var assert = require('assert').ok; var assert = require('assert').ok;
// zlib doesn't provide these, so kludge them in following the same // zlib doesn't provide these, so kludge them in following the same
@ -138,33 +139,35 @@ function zlibBuffer(engine, buffer, callback) {
var buffers = []; var buffers = [];
var nread = 0; var nread = 0;
engine.on('error', onError);
engine.on('end', onEnd);
engine.end(buffer);
flow();
function flow() {
var chunk;
while (null !== (chunk = engine.read())) {
buffers.push(chunk);
nread += chunk.length;
}
engine.once('readable', flow);
}
function onError(err) { function onError(err) {
engine.removeListener('end', onEnd); engine.removeListener('end', onEnd);
engine.removeListener('error', onError); engine.removeListener('readable', flow);
callback(err); callback(err);
} }
function onData(chunk) {
buffers.push(chunk);
nread += chunk.length;
}
function onEnd() { function onEnd() {
var buf = Buffer.concat(buffers, nread); var buf = Buffer.concat(buffers, nread);
buffers = []; buffers = [];
callback(null, buf); callback(null, buf);
} }
engine.on('error', onError);
engine.on('data', onData);
engine.on('end', onEnd);
engine.write(buffer);
engine.end();
} }
// generic zlib // generic zlib
// minimal 2-byte header // minimal 2-byte header
function Deflate(opts) { function Deflate(opts) {
@ -217,15 +220,13 @@ function Unzip(opts) {
// you call the .write() method. // you call the .write() method.
function Zlib(opts, mode) { function Zlib(opts, mode) {
Stream.call(this);
this._opts = opts = opts || {}; this._opts = opts = opts || {};
this._queue = []; this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
this._processing = false;
this._ended = false; Transform.call(this, opts);
this.readable = true;
this.writable = true; // means a different thing there.
this._flush = binding.Z_NO_FLUSH; this._readableState.chunkSize = null;
if (opts.chunkSize) { if (opts.chunkSize) {
if (opts.chunkSize < exports.Z_MIN_CHUNK || if (opts.chunkSize < exports.Z_MIN_CHUNK ||
@ -274,13 +275,12 @@ function Zlib(opts, mode) {
this._binding = new binding.Zlib(mode); this._binding = new binding.Zlib(mode);
var self = this; var self = this;
this._hadError = false;
this._binding.onerror = function(message, errno) { this._binding.onerror = function(message, errno) {
// there is no way to cleanly recover. // there is no way to cleanly recover.
// continuing only obscures problems. // continuing only obscures problems.
self._binding = null; self._binding = null;
self._hadError = true; self._hadError = true;
self._queue.length = 0;
self._processing = false;
var error = new Error(message); var error = new Error(message);
error.errno = errno; error.errno = errno;
@ -294,7 +294,6 @@ function Zlib(opts, mode) {
opts.strategy || exports.Z_DEFAULT_STRATEGY, opts.strategy || exports.Z_DEFAULT_STRATEGY,
opts.dictionary); opts.dictionary);
this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
this._buffer = new Buffer(this._chunkSize); this._buffer = new Buffer(this._chunkSize);
this._offset = 0; this._offset = 0;
this._closed = false; this._closed = false;
@ -302,59 +301,47 @@ function Zlib(opts, mode) {
this.once('end', this.close); this.once('end', this.close);
} }
util.inherits(Zlib, Stream); util.inherits(Zlib, Transform);
Zlib.prototype.write = function write(chunk, cb) {
if (this._hadError) return true;
if (this._ended) {
return this.emit('error', new Error('Cannot write after end'));
}
if (arguments.length === 1 && typeof chunk === 'function') {
cb = chunk;
chunk = null;
}
if (!chunk) {
chunk = null;
} else if (typeof chunk === 'string') {
chunk = new Buffer(chunk);
} else if (!Buffer.isBuffer(chunk)) {
return this.emit('error', new Error('Invalid argument'));
}
var empty = this._queue.length === 0;
this._queue.push([chunk, cb]);
this._process();
if (!empty) {
this._needDrain = true;
}
return empty;
};
Zlib.prototype.reset = function reset() { Zlib.prototype.reset = function reset() {
return this._binding.reset(); return this._binding.reset();
}; };
Zlib.prototype.flush = function flush(cb) { Zlib.prototype._flush = function(output, callback) {
this._flush = binding.Z_SYNC_FLUSH; var rs = this._readableState;
return this.write(cb); var self = this;
this._transform(null, output, function(er) {
if (er)
return callback(er);
// now a weird thing happens... it could be that you called flush
// but everything had already actually been consumed, but it wasn't
// enough to get over the Readable class's lowWaterMark.
// In that case, we emit 'readable' now to make sure it's consumed.
if (rs.length &&
rs.length < rs.lowWaterMark &&
!rs.ended &&
rs.needReadable)
self.emit('readable');
callback();
});
}; };
Zlib.prototype.end = function end(chunk, cb) { Zlib.prototype.flush = function(callback) {
if (this._hadError) return true; var ws = this._writableState;
var ts = this._transformState;
var self = this; if (ws.writing) {
this._ending = true; ws.needDrain = true;
var ret = this.write(chunk, function() { var self = this;
self.emit('end'); this.once('drain', function() {
if (cb) cb(); self._flush(ts.output, callback);
}); });
this._ended = true; return;
return ret; }
this._flush(ts.output, callback || function() {});
}; };
Zlib.prototype.close = function(callback) { Zlib.prototype.close = function(callback) {
@ -368,37 +355,37 @@ Zlib.prototype.close = function(callback) {
this._binding.close(); this._binding.close();
process.nextTick(this.emit.bind(this, 'close')); var self = this;
process.nextTick(function() {
self.emit('close');
});
}; };
Zlib.prototype._process = function() { Zlib.prototype._transform = function(chunk, output, cb) {
if (this._hadError) return; var flushFlag;
var ws = this._writableState;
if (this._processing || this._paused) return; var ending = ws.ending || ws.ended;
var last = ending && (!chunk || ws.length === chunk.length);
if (this._queue.length === 0) {
if (this._needDrain) { if (chunk !== null && !Buffer.isBuffer(chunk))
this._needDrain = false; return cb(new Error('invalid input'));
this.emit('drain');
} // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag.
// nothing to do, waiting for more data at this point. // If it's explicitly flushing at some other time, then we use
return; // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression
} // goodness.
if (last)
var req = this._queue.shift(); flushFlag = binding.Z_FINISH;
var cb = req.pop(); else if (chunk === null)
var chunk = req.pop(); flushFlag = binding.Z_FULL_FLUSH;
else
if (this._ending && this._queue.length === 0) { flushFlag = binding.Z_NO_FLUSH;
this._flush = binding.Z_FINISH;
}
var self = this;
var availInBefore = chunk && chunk.length; var availInBefore = chunk && chunk.length;
var availOutBefore = this._chunkSize - this._offset; var availOutBefore = this._chunkSize - this._offset;
var inOff = 0; var inOff = 0;
var req = this._binding.write(this._flush,
var req = this._binding.write(flushFlag,
chunk, // in chunk, // in
inOff, // in_off inOff, // in_off
availInBefore, // in_len availInBefore, // in_len
@ -408,23 +395,23 @@ Zlib.prototype._process = function() {
req.buffer = chunk; req.buffer = chunk;
req.callback = callback; req.callback = callback;
this._processing = req;
var self = this;
function callback(availInAfter, availOutAfter, buffer) { function callback(availInAfter, availOutAfter, buffer) {
if (self._hadError) return; if (self._hadError)
return;
var have = availOutBefore - availOutAfter; var have = availOutBefore - availOutAfter;
assert(have >= 0, 'have should not go down'); assert(have >= 0, 'have should not go down');
if (have > 0) { if (have > 0) {
var out = self._buffer.slice(self._offset, self._offset + have); var out = self._buffer.slice(self._offset, self._offset + have);
self._offset += have; self._offset += have;
self.emit('data', out); // serve some output to the consumer.
output(out);
} }
// XXX Maybe have a 'min buffer' size so we don't dip into the // exhausted the output buffer, or used all the input create a new one.
// thread pool with only 1 byte available or something?
if (availOutAfter === 0 || self._offset >= self._chunkSize) { if (availOutAfter === 0 || self._offset >= self._chunkSize) {
availOutBefore = self._chunkSize; availOutBefore = self._chunkSize;
self._offset = 0; self._offset = 0;
@ -439,7 +426,7 @@ Zlib.prototype._process = function() {
inOff += (availInBefore - availInAfter); inOff += (availInBefore - availInAfter);
availInBefore = availInAfter; availInBefore = availInAfter;
var newReq = self._binding.write(self._flush, var newReq = self._binding.write(flushFlag,
chunk, chunk,
inOff, inOff,
availInBefore, availInBefore,
@ -448,34 +435,14 @@ Zlib.prototype._process = function() {
self._chunkSize); self._chunkSize);
newReq.callback = callback; // this same function newReq.callback = callback; // this same function
newReq.buffer = chunk; newReq.buffer = chunk;
self._processing = newReq;
return; return;
} }
// finished with the chunk. // finished with the chunk.
self._processing = false; cb();
if (cb) cb();
self._process();
} }
}; };
Zlib.prototype.pause = function() {
this._paused = true;
this.emit('pause');
};
Zlib.prototype.resume = function() {
this._paused = false;
this._process();
};
Zlib.prototype.destroy = function() {
this.readable = false;
this.writable = false;
this._ended = true;
this.emit('close');
};
util.inherits(Deflate, Zlib); util.inherits(Deflate, Zlib);
util.inherits(Inflate, Zlib); util.inherits(Inflate, Zlib);
util.inherits(Gzip, Zlib); util.inherits(Gzip, Zlib);

5
node.gyp

@ -44,6 +44,11 @@
'lib/readline.js', 'lib/readline.js',
'lib/repl.js', 'lib/repl.js',
'lib/stream.js', 'lib/stream.js',
'lib/_stream_readable.js',
'lib/_stream_writable.js',
'lib/_stream_duplex.js',
'lib/_stream_transform.js',
'lib/_stream_passthrough.js',
'lib/string_decoder.js', 'lib/string_decoder.js',
'lib/sys.js', 'lib/sys.js',
'lib/timers.js', 'lib/timers.js',

39
src/node.js

@ -140,7 +140,6 @@
} else { } else {
// Read all of stdin - execute it. // Read all of stdin - execute it.
process.stdin.resume();
process.stdin.setEncoding('utf8'); process.stdin.setEncoding('utf8');
var code = ''; var code = '';
@ -497,17 +496,20 @@
switch (tty_wrap.guessHandleType(fd)) { switch (tty_wrap.guessHandleType(fd)) {
case 'TTY': case 'TTY':
var tty = NativeModule.require('tty'); var tty = NativeModule.require('tty');
stdin = new tty.ReadStream(fd); stdin = new tty.ReadStream(fd, {
highWaterMark: 0,
lowWaterMark: 0
});
break; break;
case 'FILE': case 'FILE':
var fs = NativeModule.require('fs'); var fs = NativeModule.require('fs');
stdin = new fs.ReadStream(null, {fd: fd}); stdin = new fs.ReadStream(null, { fd: fd });
break; break;
case 'PIPE': case 'PIPE':
var net = NativeModule.require('net'); var net = NativeModule.require('net');
stdin = new net.Stream(fd); stdin = new net.Stream({ fd: fd });
stdin.readable = true; stdin.readable = true;
break; break;
@ -520,16 +522,23 @@
stdin.fd = fd; stdin.fd = fd;
// stdin starts out life in a paused state, but node doesn't // stdin starts out life in a paused state, but node doesn't
// know yet. Call pause() explicitly to unref() it. // know yet. Explicitly to readStop() it to put it in the
stdin.pause(); // not-reading state.
if (stdin._handle && stdin._handle.readStop) {
// when piping stdin to a destination stream, stdin._handle.reading = false;
// let the data begin to flow. stdin._readableState.reading = false;
var pipe = stdin.pipe; stdin._handle.readStop();
stdin.pipe = function(dest, opts) { }
stdin.resume();
return pipe.call(stdin, dest, opts); // if the user calls stdin.pause(), then we need to stop reading
}; // immediately, so that the process can close down.
stdin.on('pause', function() {
if (!stdin._handle)
return;
stdin._readableState.reading = false;
stdin._handle.reading = false;
stdin._handle.readStop();
});
return stdin; return stdin;
}); });
@ -701,8 +710,8 @@
var nativeModule = new NativeModule(id); var nativeModule = new NativeModule(id);
nativeModule.compile();
nativeModule.cache(); nativeModule.cache();
nativeModule.compile();
return nativeModule.exports; return nativeModule.exports;
}; };

13
src/node_zlib.cc

@ -109,7 +109,19 @@ class ZCtx : public ObjectWrap {
assert(!ctx->write_in_progress_ && "write already in progress"); assert(!ctx->write_in_progress_ && "write already in progress");
ctx->write_in_progress_ = true; ctx->write_in_progress_ = true;
assert(!args[0]->IsUndefined() && "must provide flush value");
unsigned int flush = args[0]->Uint32Value(); unsigned int flush = args[0]->Uint32Value();
if (flush != Z_NO_FLUSH &&
flush != Z_PARTIAL_FLUSH &&
flush != Z_SYNC_FLUSH &&
flush != Z_FULL_FLUSH &&
flush != Z_FINISH &&
flush != Z_BLOCK) {
assert(0 && "Invalid flush value");
}
Bytef *in; Bytef *in;
Bytef *out; Bytef *out;
size_t in_off, in_len, out_off, out_len; size_t in_off, in_len, out_off, out_len;
@ -483,6 +495,7 @@ void InitZlib(Handle<Object> target) {
callback_sym = NODE_PSYMBOL("callback"); callback_sym = NODE_PSYMBOL("callback");
onerror_sym = NODE_PSYMBOL("onerror"); onerror_sym = NODE_PSYMBOL("onerror");
// valid flush values.
NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH); NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_PARTIAL_FLUSH); NODE_DEFINE_CONSTANT(target, Z_PARTIAL_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_SYNC_FLUSH); NODE_DEFINE_CONSTANT(target, Z_SYNC_FLUSH);

1
test/fixtures/x1024.txt

@ -0,0 +1 @@
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

16
test/message/max_tick_depth_trace.out

@ -8,10 +8,10 @@ tick 14
tick 13 tick 13
tick 12 tick 12
Trace: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral. Trace: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral.
at maxTickWarn (node.js:289:17) at maxTickWarn (node.js:*:*)
at process.nextTick (node.js:362:9) at process.nextTick (node.js:*:*
at f (*test*message*max_tick_depth_trace.js:30:13) at f (*test*message*max_tick_depth_trace.js:*:*)
at process._tickCallback (node.js:335:13) at process._tickCallback (node.js:*:*)
tick 11 tick 11
tick 10 tick 10
tick 9 tick 9
@ -23,9 +23,9 @@ tick 4
tick 3 tick 3
tick 2 tick 2
Trace: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral. Trace: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral.
at maxTickWarn (node.js:289:17) at maxTickWarn (node.js:*:*)
at process.nextTick (node.js:362:9) at process.nextTick (node.js:*:*
at f (*test*message*max_tick_depth_trace.js:30:13) at f (*test*message*max_tick_depth_trace.js:*:*)
at process._tickCallback (node.js:335:13) at process._tickCallback (node.js:*:*)
tick 1 tick 1
tick 0 tick 0

16
test/message/stdin_messages.out

@ -9,7 +9,8 @@ SyntaxError: Strict mode code may not include a with statement
at evalScript (node.js:*:*) at evalScript (node.js:*:*)
at Socket.<anonymous> (node.js:*:*) at Socket.<anonymous> (node.js:*:*)
at Socket.EventEmitter.emit (events.js:*:*) at Socket.EventEmitter.emit (events.js:*:*)
at Pipe.onread (net.js:*:*) at _stream_readable.js:*:*
at process._tickCallback (node.js:*:*)
at process._makeCallback (node.js:*:*) at process._makeCallback (node.js:*:*)
42 42
42 42
@ -18,26 +19,28 @@ SyntaxError: Strict mode code may not include a with statement
throw new Error("hello") throw new Error("hello")
^ ^
Error: hello Error: hello
at [stdin]:1:7 at [stdin]:1:*
at Object.<anonymous> ([stdin]-wrapper:*:*) at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*) at Module._compile (module.js:*:*)
at evalScript (node.js:*:*) at evalScript (node.js:*:*)
at Socket.<anonymous> (node.js:*:*) at Socket.<anonymous> (node.js:*:*)
at Socket.EventEmitter.emit (events.js:*:*) at Socket.EventEmitter.emit (events.js:*:*)
at Pipe.onread (net.js:*:*) at _stream_readable.js:*:*
at process._tickCallback (node.js:*:*)
at process._makeCallback (node.js:*:*) at process._makeCallback (node.js:*:*)
[stdin]:1 [stdin]:1
throw new Error("hello") throw new Error("hello")
^ ^
Error: hello Error: hello
at [stdin]:1:7 at [stdin]:1:*
at Object.<anonymous> ([stdin]-wrapper:*:*) at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*) at Module._compile (module.js:*:*)
at evalScript (node.js:*:*) at evalScript (node.js:*:*)
at Socket.<anonymous> (node.js:*:*) at Socket.<anonymous> (node.js:*:*)
at Socket.EventEmitter.emit (events.js:*:*) at Socket.EventEmitter.emit (events.js:*:*)
at Pipe.onread (net.js:*:*) at _stream_readable.js:*:*
at process._tickCallback (node.js:*:*)
at process._makeCallback (node.js:*:*) at process._makeCallback (node.js:*:*)
100 100
@ -51,7 +54,8 @@ ReferenceError: y is not defined
at evalScript (node.js:*:*) at evalScript (node.js:*:*)
at Socket.<anonymous> (node.js:*:*) at Socket.<anonymous> (node.js:*:*)
at Socket.EventEmitter.emit (events.js:*:*) at Socket.EventEmitter.emit (events.js:*:*)
at Pipe.onread (net.js:*:*) at _stream_readable.js:*:*
at process._tickCallback (node.js:*:*)
at process._makeCallback (node.js:*:*) at process._makeCallback (node.js:*:*)
[stdin]:1 [stdin]:1

2
test/simple/test-child-process-disconnect.js

@ -31,6 +31,8 @@ if (process.argv[2] === 'child') {
server.on('connection', function(socket) { server.on('connection', function(socket) {
socket.resume();
process.on('disconnect', function() { process.on('disconnect', function() {
socket.end((process.connected).toString()); socket.end((process.connected).toString());
}); });

58
test/simple/test-child-process-fork-net2.js

@ -23,31 +23,59 @@ var assert = require('assert');
var common = require('../common'); var common = require('../common');
var fork = require('child_process').fork; var fork = require('child_process').fork;
var net = require('net'); var net = require('net');
var count = 12;
if (process.argv[2] === 'child') { if (process.argv[2] === 'child') {
var endMe = null; var needEnd = [];
process.on('message', function(m, socket) { process.on('message', function(m, socket) {
if (!socket) return; if (!socket) return;
console.error('got socket', m);
// will call .end('end') or .write('write'); // will call .end('end') or .write('write');
socket[m](m); socket[m](m);
socket.resume();
socket.on('data', function() {
console.error('%d socket.data', process.pid, m);
});
socket.on('end', function() {
console.error('%d socket.end', process.pid, m);
});
// store the unfinished socket // store the unfinished socket
if (m === 'write') { if (m === 'write') {
endMe = socket; needEnd.push(socket);
} }
socket.on('close', function() {
console.error('%d socket.close', process.pid, m);
});
socket.on('finish', function() {
console.error('%d socket finished', process.pid, m);
});
}); });
process.on('message', function(m) { process.on('message', function(m) {
if (m !== 'close') return; if (m !== 'close') return;
endMe.end('end'); console.error('got close message');
endMe = null; needEnd.forEach(function(endMe, i) {
console.error('%d ending %d', process.pid, i);
endMe.end('end');
});
}); });
process.on('disconnect', function() { process.on('disconnect', function() {
endMe.end('end'); console.error('%d process disconnect, ending', process.pid);
needEnd.forEach(function(endMe, i) {
console.error('%d ending %d', process.pid, i);
endMe.end('end');
});
endMe = null; endMe = null;
}); });
@ -61,7 +89,7 @@ if (process.argv[2] === 'child') {
var connected = 0; var connected = 0;
server.on('connection', function(socket) { server.on('connection', function(socket) {
switch (connected) { switch (connected % 6) {
case 0: case 0:
child1.send('end', socket); break; child1.send('end', socket); break;
case 1: case 1:
@ -77,7 +105,7 @@ if (process.argv[2] === 'child') {
} }
connected += 1; connected += 1;
if (connected === 6) { if (connected === count) {
closeServer(); closeServer();
} }
}); });
@ -85,17 +113,23 @@ if (process.argv[2] === 'child') {
var disconnected = 0; var disconnected = 0;
server.on('listening', function() { server.on('listening', function() {
var j = 6, client; var j = count, client;
while (j--) { while (j--) {
client = net.connect(common.PORT, '127.0.0.1'); client = net.connect(common.PORT, '127.0.0.1');
client.on('close', function() { client.on('close', function() {
console.error('CLIENT: close event in master');
disconnected += 1; disconnected += 1;
}); });
// XXX This resume() should be unnecessary.
// a stream high water mark should be enough to keep
// consuming the input.
client.resume();
} }
}); });
var closeEmitted = false; var closeEmitted = false;
server.on('close', function() { server.on('close', function() {
console.error('server close');
closeEmitted = true; closeEmitted = true;
child1.kill(); child1.kill();
@ -107,14 +141,18 @@ if (process.argv[2] === 'child') {
var timeElasped = 0; var timeElasped = 0;
var closeServer = function() { var closeServer = function() {
console.error('closeServer');
var startTime = Date.now(); var startTime = Date.now();
server.on('close', function() { server.on('close', function() {
console.error('emit(close)');
timeElasped = Date.now() - startTime; timeElasped = Date.now() - startTime;
}); });
console.error('calling server.close');
server.close(); server.close();
setTimeout(function() { setTimeout(function() {
console.error('sending close to children');
child1.send('close'); child1.send('close');
child2.send('close'); child2.send('close');
child3.disconnect(); child3.disconnect();
@ -122,8 +160,8 @@ if (process.argv[2] === 'child') {
}; };
process.on('exit', function() { process.on('exit', function() {
assert.equal(disconnected, 6); assert.equal(disconnected, count);
assert.equal(connected, 6); assert.equal(connected, count);
assert.ok(closeEmitted); assert.ok(closeEmitted);
assert.ok(timeElasped >= 190 && timeElasped <= 1000, assert.ok(timeElasped >= 190 && timeElasped <= 1000,
'timeElasped was not between 190 and 1000 ms'); 'timeElasped was not between 190 and 1000 ms');

3
test/simple/test-child-process-ipc.js

@ -42,10 +42,13 @@ child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data) { child.stdout.on('data', function(data) {
console.log('child said: ' + JSON.stringify(data)); console.log('child said: ' + JSON.stringify(data));
if (!gotHelloWorld) { if (!gotHelloWorld) {
console.error('testing for hello world');
assert.equal('hello world\r\n', data); assert.equal('hello world\r\n', data);
gotHelloWorld = true; gotHelloWorld = true;
console.error('writing echo me');
child.stdin.write('echo me\r\n'); child.stdin.write('echo me\r\n');
} else { } else {
console.error('testing for echo me');
assert.equal('echo me\r\n', data); assert.equal('echo me\r\n', data);
gotEcho = true; gotEcho = true;
child.stdin.end(); child.stdin.end();

1
test/simple/test-cluster-http-pipe.js

@ -53,6 +53,7 @@ http.createServer(function(req, res) {
}).listen(common.PIPE, function() { }).listen(common.PIPE, function() {
var self = this; var self = this;
http.get({ socketPath: common.PIPE, path: '/' }, function(res) { http.get({ socketPath: common.PIPE, path: '/' }, function(res) {
res.resume();
res.on('end', function(err) { res.on('end', function(err) {
if (err) throw err; if (err) throw err;
process.send('DONE'); process.send('DONE');

2
test/simple/test-cluster-message.js

@ -81,6 +81,7 @@ else if (cluster.isMaster) {
var check = function(type, result) { var check = function(type, result) {
checks[type].receive = true; checks[type].receive = true;
checks[type].correct = result; checks[type].correct = result;
console.error('check', checks);
var missing = false; var missing = false;
forEach(checks, function(type) { forEach(checks, function(type) {
@ -88,6 +89,7 @@ else if (cluster.isMaster) {
}); });
if (missing === false) { if (missing === false) {
console.error('end client');
client.end(); client.end();
} }
}; };

77
test/simple/test-crypto.js

@ -230,15 +230,20 @@ var rfc4231 = [
for (var i = 0, l = rfc4231.length; i < l; i++) { for (var i = 0, l = rfc4231.length; i < l; i++) {
for (var hash in rfc4231[i]['hmac']) { for (var hash in rfc4231[i]['hmac']) {
var str = crypto.createHmac(hash, rfc4231[i].key);
str.end(rfc4231[i].data);
var strRes = str.read().toString('hex');
var result = crypto.createHmac(hash, rfc4231[i]['key']) var result = crypto.createHmac(hash, rfc4231[i]['key'])
.update(rfc4231[i]['data']) .update(rfc4231[i]['data'])
.digest('hex'); .digest('hex');
if (rfc4231[i]['truncate']) { if (rfc4231[i]['truncate']) {
result = result.substr(0, 32); // first 128 bits == 32 hex chars result = result.substr(0, 32); // first 128 bits == 32 hex chars
strRes = strRes.substr(0, 32);
} }
assert.equal(rfc4231[i]['hmac'][hash], assert.equal(rfc4231[i]['hmac'][hash],
result, result,
'Test HMAC-' + hash + ': Test case ' + (i + 1) + ' rfc 4231'); 'Test HMAC-' + hash + ': Test case ' + (i + 1) + ' rfc 4231');
assert.equal(strRes, result, 'Should get same result from stream');
} }
} }
@ -373,6 +378,18 @@ var a2 = crypto.createHash('sha256').update('Test123').digest('base64');
var a3 = crypto.createHash('sha512').update('Test123').digest(); // binary var a3 = crypto.createHash('sha512').update('Test123').digest(); // binary
var a4 = crypto.createHash('sha1').update('Test123').digest('buffer'); var a4 = crypto.createHash('sha1').update('Test123').digest('buffer');
// stream interface
var a5 = crypto.createHash('sha512');
a5.end('Test123');
a5 = a5.read();
var a6 = crypto.createHash('sha512');
a6.write('Te');
a6.write('st');
a6.write('123');
a6.end();
a6 = a6.read();
assert.equal(a0, '8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'Test SHA1'); assert.equal(a0, '8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'Test SHA1');
assert.equal(a1, 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca' + assert.equal(a1, 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca' +
'\u00bd\u008c', 'Test MD5 as binary'); '\u00bd\u008c', 'Test MD5 as binary');
@ -392,6 +409,10 @@ assert.deepEqual(a4,
new Buffer('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'), new Buffer('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'),
'Test SHA1'); 'Test SHA1');
// stream interface should produce the same result.
assert.deepEqual(a5, a3, 'stream interface is consistent');
assert.deepEqual(a6, a3, 'stream interface is consistent');
// Test multiple updates to same hash // Test multiple updates to same hash
var h1 = crypto.createHash('sha1').update('Test123').digest('hex'); var h1 = crypto.createHash('sha1').update('Test123').digest('hex');
var h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex'); var h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex');
@ -419,6 +440,11 @@ assert.throws(function() {
var s1 = crypto.createSign('RSA-SHA1') var s1 = crypto.createSign('RSA-SHA1')
.update('Test123') .update('Test123')
.sign(keyPem, 'base64'); .sign(keyPem, 'base64');
var s1stream = crypto.createSign('RSA-SHA1');
s1stream.end('Test123');
s1stream = s1stream.sign(keyPem, 'base64');
assert.equal(s1, s1stream, 'Stream produces same output');
var verified = crypto.createVerify('RSA-SHA1') var verified = crypto.createVerify('RSA-SHA1')
.update('Test') .update('Test')
.update('123') .update('123')
@ -427,13 +453,25 @@ assert.strictEqual(verified, true, 'sign and verify (base 64)');
var s2 = crypto.createSign('RSA-SHA256') var s2 = crypto.createSign('RSA-SHA256')
.update('Test123') .update('Test123')
.sign(keyPem); // binary .sign(keyPem, 'binary');
var s2stream = crypto.createSign('RSA-SHA256');
s2stream.end('Test123');
s2stream = s2stream.sign(keyPem, 'binary');
assert.equal(s2, s2stream, 'Stream produces same output');
var verified = crypto.createVerify('RSA-SHA256') var verified = crypto.createVerify('RSA-SHA256')
.update('Test') .update('Test')
.update('123') .update('123')
.verify(certPem, s2); // binary .verify(certPem, s2, 'binary');
assert.strictEqual(verified, true, 'sign and verify (binary)'); assert.strictEqual(verified, true, 'sign and verify (binary)');
var verStream = crypto.createVerify('RSA-SHA256');
verStream.write('Tes');
verStream.write('t12');
verStream.end('3');
verified = verStream.verify(certPem, s2, 'binary');
assert.strictEqual(verified, true, 'sign and verify (stream)');
var s3 = crypto.createSign('RSA-SHA1') var s3 = crypto.createSign('RSA-SHA1')
.update('Test123') .update('Test123')
.sign(keyPem, 'buffer'); .sign(keyPem, 'buffer');
@ -443,6 +481,13 @@ var verified = crypto.createVerify('RSA-SHA1')
.verify(certPem, s3); .verify(certPem, s3);
assert.strictEqual(verified, true, 'sign and verify (buffer)'); assert.strictEqual(verified, true, 'sign and verify (buffer)');
var verStream = crypto.createVerify('RSA-SHA1');
verStream.write('Tes');
verStream.write('t12');
verStream.end('3');
verified = verStream.verify(certPem, s3);
assert.strictEqual(verified, true, 'sign and verify (stream)');
function testCipher1(key) { function testCipher1(key) {
// Test encryption and decryption // Test encryption and decryption
@ -460,6 +505,20 @@ function testCipher1(key) {
txt += decipher.final('utf8'); txt += decipher.final('utf8');
assert.equal(txt, plaintext, 'encryption and decryption'); assert.equal(txt, plaintext, 'encryption and decryption');
// streaming cipher interface
// NB: In real life, it's not guaranteed that you can get all of it
// in a single read() like this. But in this case, we know it's
// quite small, so there's no harm.
var cStream = crypto.createCipher('aes192', key);
cStream.end(plaintext);
ciph = cStream.read();
var dStream = crypto.createDecipher('aes192', key);
dStream.end(ciph);
txt = dStream.read().toString('utf8');
assert.equal(txt, plaintext, 'encryption and decryption with streams');
} }
@ -500,6 +559,20 @@ function testCipher3(key, iv) {
txt += decipher.final('utf8'); txt += decipher.final('utf8');
assert.equal(txt, plaintext, 'encryption and decryption with key and iv'); assert.equal(txt, plaintext, 'encryption and decryption with key and iv');
// streaming cipher interface
// NB: In real life, it's not guaranteed that you can get all of it
// in a single read() like this. But in this case, we know it's
// quite small, so there's no harm.
var cStream = crypto.createCipheriv('des-ede3-cbc', key, iv);
cStream.end(plaintext);
ciph = cStream.read();
var dStream = crypto.createDecipheriv('des-ede3-cbc', key, iv);
dStream.end(ciph);
txt = dStream.read().toString('utf8');
assert.equal(txt, plaintext, 'streaming cipher iv');
} }

12
test/simple/test-domain-http-server.js

@ -33,12 +33,13 @@ var disposeEmit = 0;
var server = http.createServer(function(req, res) { var server = http.createServer(function(req, res) {
var dom = domain.create(); var dom = domain.create();
req.resume();
dom.add(req); dom.add(req);
dom.add(res); dom.add(res);
dom.on('error', function(er) { dom.on('error', function(er) {
serverCaught++; serverCaught++;
console.log('server error', er); console.log('horray! got a server error', er);
// try to send a 500. If that fails, oh well. // try to send a 500. If that fails, oh well.
res.writeHead(500, {'content-type':'text/plain'}); res.writeHead(500, {'content-type':'text/plain'});
res.end(er.stack || er.message || 'Unknown error'); res.end(er.stack || er.message || 'Unknown error');
@ -81,12 +82,7 @@ function next() {
dom.on('error', function(er) { dom.on('error', function(er) {
clientCaught++; clientCaught++;
console.log('client error', er); console.log('client error', er);
// kill everything. req.socket.destroy();
dom.dispose();
});
dom.on('dispose', function() {
disposeEmit += 1;
}); });
var req = http.get({ host: 'localhost', port: common.PORT, path: p }); var req = http.get({ host: 'localhost', port: common.PORT, path: p });
@ -106,6 +102,7 @@ function next() {
d += c; d += c;
}); });
res.on('end', function() { res.on('end', function() {
console.error('trying to parse json', d);
d = JSON.parse(d); d = JSON.parse(d);
console.log('json!', d); console.log('json!', d);
}); });
@ -116,6 +113,5 @@ function next() {
process.on('exit', function() { process.on('exit', function() {
assert.equal(serverCaught, 2); assert.equal(serverCaught, 2);
assert.equal(clientCaught, 2); assert.equal(clientCaught, 2);
assert.equal(disposeEmit, 2);
console.log('ok'); console.log('ok');
}); });

35
test/simple/test-file-write-stream.js

@ -22,46 +22,50 @@
var common = require('../common'); var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var path = require('path'), var path = require('path');
fs = require('fs'), var fs = require('fs');
fn = path.join(common.tmpDir, 'write.txt'), var fn = path.join(common.tmpDir, 'write.txt');
file = fs.createWriteStream(fn), var file = fs.createWriteStream(fn, {
lowWaterMark: 0,
highWaterMark: 10
});
EXPECTED = '012345678910', var EXPECTED = '012345678910';
callbacks = { var callbacks = {
open: -1, open: -1,
drain: -2, drain: -2,
close: -1, close: -1
endCb: -1
}; };
file file
.on('open', function(fd) { .on('open', function(fd) {
console.error('open!');
callbacks.open++; callbacks.open++;
assert.equal('number', typeof fd); assert.equal('number', typeof fd);
}) })
.on('error', function(err) { .on('error', function(err) {
throw err; throw err;
console.error('error!', err.stack);
}) })
.on('drain', function() { .on('drain', function() {
console.error('drain!', callbacks.drain);
callbacks.drain++; callbacks.drain++;
if (callbacks.drain == -1) { if (callbacks.drain == -1) {
assert.equal(EXPECTED, fs.readFileSync(fn)); assert.equal(EXPECTED, fs.readFileSync(fn, 'utf8'));
file.write(EXPECTED); file.write(EXPECTED);
} else if (callbacks.drain == 0) { } else if (callbacks.drain == 0) {
assert.equal(EXPECTED + EXPECTED, fs.readFileSync(fn)); assert.equal(EXPECTED + EXPECTED, fs.readFileSync(fn, 'utf8'));
file.end(function(err) { file.end();
assert.ok(!err);
callbacks.endCb++;
});
} }
}) })
.on('close', function() { .on('close', function() {
console.error('close!');
assert.strictEqual(file.bytesWritten, EXPECTED.length * 2); assert.strictEqual(file.bytesWritten, EXPECTED.length * 2);
callbacks.close++; callbacks.close++;
assert.throws(function() { assert.throws(function() {
console.error('write after end should not be allowed');
file.write('should not work anymore'); file.write('should not work anymore');
}); });
@ -70,7 +74,7 @@ file
for (var i = 0; i < 11; i++) { for (var i = 0; i < 11; i++) {
(function(i) { (function(i) {
assert.strictEqual(false, file.write(i)); file.write('' + i);
})(i); })(i);
} }
@ -78,4 +82,5 @@ process.on('exit', function() {
for (var k in callbacks) { for (var k in callbacks) {
assert.equal(0, callbacks[k], k + ' count off by ' + callbacks[k]); assert.equal(0, callbacks[k], k + ' count off by ' + callbacks[k]);
} }
console.log('ok');
}); });

40
test/simple/test-file-write-stream2.js

@ -22,18 +22,18 @@
var common = require('../common'); var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var path = require('path'), var path = require('path');
fs = require('fs'), var fs = require('fs');
util = require('util'); var util = require('util');
var filepath = path.join(common.tmpDir, 'write.txt'), var filepath = path.join(common.tmpDir, 'write.txt');
file; var file;
var EXPECTED = '012345678910'; var EXPECTED = '012345678910';
var cb_expected = 'write open drain write drain close error ', var cb_expected = 'write open drain write drain close error ';
cb_occurred = ''; var cb_occurred = '';
var countDrains = 0; var countDrains = 0;
@ -47,6 +47,8 @@ process.on('exit', function() {
assert.strictEqual(cb_occurred, cb_expected, assert.strictEqual(cb_occurred, cb_expected,
'events missing or out of order: "' + 'events missing or out of order: "' +
cb_occurred + '" !== "' + cb_expected + '"'); cb_occurred + '" !== "' + cb_expected + '"');
} else {
console.log('ok');
} }
}); });
@ -59,22 +61,30 @@ function removeTestFile() {
removeTestFile(); removeTestFile();
file = fs.createWriteStream(filepath); // drain at 0, return false at 10.
file = fs.createWriteStream(filepath, {
lowWaterMark: 0,
highWaterMark: 11
});
file.on('open', function(fd) { file.on('open', function(fd) {
console.error('open');
cb_occurred += 'open '; cb_occurred += 'open ';
assert.equal(typeof fd, 'number'); assert.equal(typeof fd, 'number');
}); });
file.on('drain', function() { file.on('drain', function() {
console.error('drain');
cb_occurred += 'drain '; cb_occurred += 'drain ';
++countDrains; ++countDrains;
if (countDrains === 1) { if (countDrains === 1) {
assert.equal(fs.readFileSync(filepath), EXPECTED); console.error('drain=1, write again');
file.write(EXPECTED); assert.equal(fs.readFileSync(filepath, 'utf8'), EXPECTED);
console.error('ondrain write ret=%j', file.write(EXPECTED));
cb_occurred += 'write '; cb_occurred += 'write ';
} else if (countDrains == 2) { } else if (countDrains == 2) {
assert.equal(fs.readFileSync(filepath), EXPECTED + EXPECTED); console.error('second drain, end');
assert.equal(fs.readFileSync(filepath, 'utf8'), EXPECTED + EXPECTED);
file.end(); file.end();
} }
}); });
@ -88,11 +98,15 @@ file.on('close', function() {
file.on('error', function(err) { file.on('error', function(err) {
cb_occurred += 'error '; cb_occurred += 'error ';
assert.ok(err.message.indexOf('not writable') >= 0); assert.ok(err.message.indexOf('write after end') >= 0);
}); });
for (var i = 0; i < 11; i++) { for (var i = 0; i < 11; i++) {
assert.strictEqual(file.write(i), false); var ret = file.write(i + '');
console.error('%d %j', i, ret);
// return false when i hits 10
assert(ret === (i != 10));
} }
cb_occurred += 'write '; cb_occurred += 'write ';

6
test/simple/test-fs-empty-readStream.js

@ -32,12 +32,13 @@ fs.open(emptyFile, 'r', function (error, fd) {
var read = fs.createReadStream(emptyFile, { 'fd': fd }); var read = fs.createReadStream(emptyFile, { 'fd': fd });
read.once('data', function () { read.once('data', function () {
throw new Error("data event should not emit"); throw new Error('data event should not emit');
}); });
var readEmit = false; var readEmit = false;
read.once('end', function () { read.once('end', function () {
readEmit = true; readEmit = true;
console.error('end event 1');
}); });
setTimeout(function () { setTimeout(function () {
@ -52,12 +53,13 @@ fs.open(emptyFile, 'r', function (error, fd) {
read.pause(); read.pause();
read.once('data', function () { read.once('data', function () {
throw new Error("data event should not emit"); throw new Error('data event should not emit');
}); });
var readEmit = false; var readEmit = false;
read.once('end', function () { read.once('end', function () {
readEmit = true; readEmit = true;
console.error('end event 2');
}); });
setTimeout(function () { setTimeout(function () {

40
test/simple/test-fs-read-stream-err.js

@ -23,28 +23,42 @@ var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var fs = require('fs'); var fs = require('fs');
var stream = fs.createReadStream(__filename, { bufferSize: 64 }); var stream = fs.createReadStream(__filename, {
bufferSize: 64,
lowWaterMark: 0
});
var err = new Error('BAM'); var err = new Error('BAM');
stream.on('data', function(buf) { stream.on('error', common.mustCall(function errorHandler(err_) {
var fd = stream.fd; console.error('error event');
process.nextTick(function() {
assert.equal(stream.fd, null);
assert.equal(err_, err);
});
}));
fs.close = common.mustCall(function(fd_, cb) {
assert.equal(fd_, stream.fd);
process.nextTick(cb);
});
var read = fs.read;
fs.read = function() {
// first time is ok.
read.apply(fs, arguments);
// then it breaks
fs.read = function() { fs.read = function() {
var cb = arguments[arguments.length - 1]; var cb = arguments[arguments.length - 1];
process.nextTick(function() { process.nextTick(function() {
cb(err); cb(err);
}); });
// and should not be called again!
fs.read = function() {
throw new Error('BOOM!');
};
}; };
};
fs.close = common.mustCall(function(fd_, cb) { stream.on('data', function(buf) {
assert.equal(fd_, fd);
process.nextTick(cb);
});
stream.on('error', common.mustCall(function(err_) {
assert.equal(stream.fd, null);
assert.equal(err_, err);
}));
stream.on('data', assert.fail); // no more 'data' events should follow stream.on('data', assert.fail); // no more 'data' events should follow
}); });

4
test/simple/test-fs-read-stream.js

@ -60,12 +60,10 @@ file.on('data', function(data) {
paused = true; paused = true;
file.pause(); file.pause();
assert.ok(file.paused);
setTimeout(function() { setTimeout(function() {
paused = false; paused = false;
file.resume(); file.resume();
assert.ok(!file.paused);
}, 10); }, 10);
}); });
@ -77,7 +75,6 @@ file.on('end', function(chunk) {
file.on('close', function() { file.on('close', function() {
callbacks.close++; callbacks.close++;
assert.ok(!file.readable);
//assert.equal(fs.readFileSync(fn), fileContent); //assert.equal(fs.readFileSync(fn), fileContent);
}); });
@ -104,6 +101,7 @@ process.on('exit', function() {
assert.equal(2, callbacks.close); assert.equal(2, callbacks.close);
assert.equal(30000, file.length); assert.equal(30000, file.length);
assert.equal(10000, file3.length); assert.equal(10000, file3.length);
console.error('ok');
}); });
var file4 = fs.createReadStream(rangeFile, {bufferSize: 1, start: 1, end: 2}); var file4 = fs.createReadStream(rangeFile, {bufferSize: 1, start: 1, end: 2});

3
test/simple/test-fs-write-stream-end.js

@ -31,7 +31,8 @@ var writeEndOk = false;
var file = path.join(common.tmpDir, 'write-end-test.txt'); var file = path.join(common.tmpDir, 'write-end-test.txt');
var stream = fs.createWriteStream(file); var stream = fs.createWriteStream(file);
stream.end('a\n', 'utf8', function() { stream.end('a\n', 'utf8');
stream.on('close', function() {
var content = fs.readFileSync(file, 'utf8'); var content = fs.readFileSync(file, 'utf8');
assert.equal(content, 'a\n'); assert.equal(content, 'a\n');
writeEndOk = true; writeEndOk = true;

56
test/simple/test-fs-write-stream-err.js

@ -23,30 +23,50 @@ var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var fs = require('fs'); var fs = require('fs');
var stream = fs.createWriteStream(common.tmpDir + '/out'); var stream = fs.createWriteStream(common.tmpDir + '/out', {
lowWaterMark: 0,
highWaterMark: 10
});
var err = new Error('BAM'); var err = new Error('BAM');
stream.write(new Buffer(256), function() { var write = fs.write;
var fd = stream.fd; var writeCalls = 0;
fs.write = function() {
switch (writeCalls++) {
case 0:
console.error('first write');
// first time is ok.
return write.apply(fs, arguments);
case 1:
// then it breaks
console.error('second write');
var cb = arguments[arguments.length - 1];
return process.nextTick(function() {
cb(err);
});
default:
// and should not be called again!
throw new Error('BOOM!');
}
};
fs.write = function() { fs.close = common.mustCall(function(fd_, cb) {
var cb = arguments[arguments.length - 1]; console.error('fs.close', fd_, stream.fd);
process.nextTick(function() { assert.equal(fd_, stream.fd);
cb(err); process.nextTick(cb);
}); });
};
fs.close = function(fd_, cb) { stream.on('error', common.mustCall(function(err_) {
assert.equal(fd_, fd); console.error('error handler');
process.nextTick(cb); assert.equal(stream.fd, null);
}; assert.equal(err_, err);
}));
stream.write(new Buffer(256), common.mustCall(function(err_) {
assert.equal(err_, err);
}));
stream.on('error', common.mustCall(function(err_) { stream.write(new Buffer(256), function() {
assert.equal(stream.fd, null); console.error('first cb');
stream.write(new Buffer(256), common.mustCall(function(err_) {
console.error('second cb');
assert.equal(err_, err); assert.equal(err_, err);
})); }));
}); });

4
test/simple/test-http-1.0-keep-alive.js

@ -115,6 +115,7 @@ function check(tests) {
function server(req, res) { function server(req, res) {
if (current + 1 === test.responses.length) this.close(); if (current + 1 === test.responses.length) this.close();
var ctx = test.responses[current]; var ctx = test.responses[current];
console.error('< SERVER SENDING RESPONSE', ctx);
res.writeHead(200, ctx.headers); res.writeHead(200, ctx.headers);
ctx.chunks.slice(0, -1).forEach(function(chunk) { res.write(chunk) }); ctx.chunks.slice(0, -1).forEach(function(chunk) { res.write(chunk) });
res.end(ctx.chunks[ctx.chunks.length - 1]); res.end(ctx.chunks[ctx.chunks.length - 1]);
@ -126,16 +127,19 @@ function check(tests) {
function connected() { function connected() {
var ctx = test.requests[current]; var ctx = test.requests[current];
console.error(' > CLIENT SENDING REQUEST', ctx);
conn.setEncoding('utf8'); conn.setEncoding('utf8');
conn.write(ctx.data); conn.write(ctx.data);
function onclose() { function onclose() {
console.error(' > CLIENT CLOSE');
if (!ctx.expectClose) throw new Error('unexpected close'); if (!ctx.expectClose) throw new Error('unexpected close');
client(); client();
} }
conn.on('close', onclose); conn.on('close', onclose);
function ondata(s) { function ondata(s) {
console.error(' > CLIENT ONDATA %j %j', s.length, s.toString());
current++; current++;
if (ctx.expectClose) return; if (ctx.expectClose) return;
conn.removeListener('close', onclose); conn.removeListener('close', onclose);

10
test/simple/test-http-abort-client.js

@ -46,13 +46,21 @@ server.listen(common.PORT, function() {
res.on('data', function(chunk) { res.on('data', function(chunk) {
console.log('Read ' + chunk.length + ' bytes'); console.log('Read ' + chunk.length + ' bytes');
console.log(chunk.toString()); console.log(' chunk=%j', chunk.toString());
}); });
res.on('end', function() { res.on('end', function() {
console.log('Response ended.'); console.log('Response ended.');
}); });
res.on('aborted', function() {
console.log('Response aborted.');
});
res.socket.on('close', function() {
console.log('socket closed, but not res');
})
// it would be nice if this worked: // it would be nice if this worked:
res.on('close', function() { res.on('close', function() {
console.log('Response aborted'); console.log('Response aborted');

10
test/simple/test-http-agent.js

@ -40,10 +40,14 @@ server.listen(common.PORT, function() {
setTimeout(function() { setTimeout(function() {
for (var j = 0; j < M; j++) { for (var j = 0; j < M; j++) {
http.get({ port: common.PORT, path: '/' }, function(res) { http.get({ port: common.PORT, path: '/' }, function(res) {
console.log(res.statusCode); console.log('%d %d', responses, res.statusCode);
if (++responses == N * M) server.close(); if (++responses == N * M) {
console.error('Received all responses, closing server');
server.close();
}
res.resume();
}).on('error', function(e) { }).on('error', function(e) {
console.log(e.message); console.log('Error!', e);
process.exit(1); process.exit(1);
}); });
} }

1
test/simple/test-http-client-agent.js

@ -61,6 +61,7 @@ function request(i) {
server.close(); server.close();
} }
}); });
res.resume();
}); });
} }

5
test/simple/test-http-client-pipe-end.js

@ -26,6 +26,7 @@ var assert = require('assert');
var http = require('http'); var http = require('http');
var server = http.createServer(function(req, res) { var server = http.createServer(function(req, res) {
req.resume();
req.once('end', function() { req.once('end', function() {
res.writeHead(200); res.writeHead(200);
res.end(); res.end();
@ -50,9 +51,9 @@ server.listen(common.PIPE, function() {
function sched(cb, ticks) { function sched(cb, ticks) {
function fn() { function fn() {
if (--ticks) if (--ticks)
process.nextTick(fn); setImmediate(fn);
else else
cb(); cb();
} }
process.nextTick(fn); setImmediate(fn);
} }

6
test/simple/test-http-connect.js

@ -73,7 +73,11 @@ server.listen(common.PORT, function() {
assert(!socket.onend); assert(!socket.onend);
assert.equal(socket.listeners('connect').length, 0); assert.equal(socket.listeners('connect').length, 0);
assert.equal(socket.listeners('data').length, 0); assert.equal(socket.listeners('data').length, 0);
assert.equal(socket.listeners('end').length, 0);
// the stream.Duplex onend listener
// allow 0 here, so that i can run the same test on streams1 impl
assert(socket.listeners('end').length <= 1);
assert.equal(socket.listeners('free').length, 0); assert.equal(socket.listeners('free').length, 0);
assert.equal(socket.listeners('close').length, 0); assert.equal(socket.listeners('close').length, 0);
assert.equal(socket.listeners('error').length, 0); assert.equal(socket.listeners('error').length, 0);

1
test/simple/test-http-date-header.js

@ -49,6 +49,7 @@ server.addListener('listening', function() {
server.close(); server.close();
process.exit(); process.exit();
}); });
res.resume();
}); });
req.end(); req.end();
}); });

1
test/simple/test-http-default-encoding.js

@ -50,6 +50,7 @@ server.listen(common.PORT, function() {
method: 'POST' method: 'POST'
}, function(res) { }, function(res) {
console.log(res.statusCode); console.log(res.statusCode);
res.resume();
}).on('error', function(e) { }).on('error', function(e) {
console.log(e.message); console.log(e.message);
process.exit(1); process.exit(1);

1
test/simple/test-http-header-read.js

@ -50,5 +50,6 @@ function runTest() {
response.on('end', function() { response.on('end', function() {
s.close(); s.close();
}); });
response.resume();
}); });
} }

21
test/simple/test-http-header-response-splitting.js

@ -27,6 +27,7 @@ var testIndex = 0,
responses = 0; responses = 0;
var server = http.createServer(function(req, res) { var server = http.createServer(function(req, res) {
console.error('request', testIndex);
switch (testIndex++) { switch (testIndex++) {
case 0: case 0:
res.writeHead(200, { test: 'foo \r\ninvalid: bar' }); res.writeHead(200, { test: 'foo \r\ninvalid: bar' });
@ -41,6 +42,7 @@ var server = http.createServer(function(req, res) {
res.writeHead(200, { test: 'foo \n\n\ninvalid: bar' }); res.writeHead(200, { test: 'foo \n\n\ninvalid: bar' });
break; break;
case 4: case 4:
console.error('send request, then close');
res.writeHead(200, { test: 'foo \r\n \r\n \r\ninvalid: bar' }); res.writeHead(200, { test: 'foo \r\n \r\n \r\ninvalid: bar' });
server.close(); server.close();
break; break;
@ -49,15 +51,16 @@ var server = http.createServer(function(req, res) {
} }
res.end('Hi mars!'); res.end('Hi mars!');
}); });
server.listen(common.PORT); server.listen(common.PORT, function() {
for (var i = 0; i < 5; i++) {
for (var i = 0; i < 5; i++) { var req = http.get({ port: common.PORT, path: '/' }, function(res) {
var req = http.get({ port: common.PORT, path: '/' }, function(res) { assert.strictEqual(res.headers.test, 'foo invalid: bar');
assert.strictEqual(res.headers.test, 'foo invalid: bar'); assert.strictEqual(res.headers.invalid, undefined);
assert.strictEqual(res.headers.invalid, undefined); responses++;
responses++; res.resume();
}); });
} }
});
process.on('exit', function() { process.on('exit', function() {
assert.strictEqual(responses, 5); assert.strictEqual(responses, 5);

6
test/simple/test-http-host-headers.js

@ -57,13 +57,14 @@ function testHttp() {
var counter = 0; var counter = 0;
function cb() { function cb(res) {
counter--; counter--;
console.log('back from http request. counter = ' + counter); console.log('back from http request. counter = ' + counter);
if (counter === 0) { if (counter === 0) {
httpServer.close(); httpServer.close();
testHttps(); testHttps();
} }
res.resume();
} }
httpServer.listen(common.PORT, function(er) { httpServer.listen(common.PORT, function(er) {
@ -124,13 +125,14 @@ function testHttps() {
var counter = 0; var counter = 0;
function cb() { function cb(res) {
counter--; counter--;
console.log('back from https request. counter = ' + counter); console.log('back from https request. counter = ' + counter);
if (counter === 0) { if (counter === 0) {
httpsServer.close(); httpsServer.close();
console.log('ok'); console.log('ok');
} }
res.resume();
} }
httpsServer.listen(common.PORT, function(er) { httpsServer.listen(common.PORT, function(er) {

7
test/simple/test-http-keep-alive-close-on-header.js

@ -44,8 +44,9 @@ server.listen(common.PORT, function() {
headers: headers, headers: headers,
port: common.PORT, port: common.PORT,
agent: agent agent: agent
}, function() { }, function(res) {
assert.equal(1, agent.sockets['localhost:' + common.PORT].length); assert.equal(1, agent.sockets['localhost:' + common.PORT].length);
res.resume();
}); });
request.on('socket', function(s) { request.on('socket', function(s) {
s.on('connect', function() { s.on('connect', function() {
@ -60,8 +61,9 @@ server.listen(common.PORT, function() {
headers: headers, headers: headers,
port: common.PORT, port: common.PORT,
agent: agent agent: agent
}, function() { }, function(res) {
assert.equal(1, agent.sockets['localhost:' + common.PORT].length); assert.equal(1, agent.sockets['localhost:' + common.PORT].length);
res.resume();
}); });
request.on('socket', function(s) { request.on('socket', function(s) {
s.on('connect', function() { s.on('connect', function() {
@ -80,6 +82,7 @@ server.listen(common.PORT, function() {
assert.equal(1, agent.sockets['localhost:' + common.PORT].length); assert.equal(1, agent.sockets['localhost:' + common.PORT].length);
server.close(); server.close();
}); });
response.resume();
}); });
request.on('socket', function(s) { request.on('socket', function(s) {
s.on('connect', function() { s.on('connect', function() {

3
test/simple/test-http-keep-alive.js

@ -42,6 +42,7 @@ server.listen(common.PORT, function() {
}, function(response) { }, function(response) {
assert.equal(agent.sockets[name].length, 1); assert.equal(agent.sockets[name].length, 1);
assert.equal(agent.requests[name].length, 2); assert.equal(agent.requests[name].length, 2);
response.resume();
}); });
http.get({ http.get({
@ -49,6 +50,7 @@ server.listen(common.PORT, function() {
}, function(response) { }, function(response) {
assert.equal(agent.sockets[name].length, 1); assert.equal(agent.sockets[name].length, 1);
assert.equal(agent.requests[name].length, 1); assert.equal(agent.requests[name].length, 1);
response.resume();
}); });
http.get({ http.get({
@ -59,6 +61,7 @@ server.listen(common.PORT, function() {
assert(!agent.requests.hasOwnProperty(name)); assert(!agent.requests.hasOwnProperty(name));
server.close(); server.close();
}); });
response.resume();
}); });
}); });

1
test/simple/test-http-many-keep-alive-connections.js

@ -55,6 +55,7 @@ server.listen(common.PORT, function() {
server.close(); server.close();
} }
}); });
res.resume();
}).on('error', function(e) { }).on('error', function(e) {
console.log(e.message); console.log(e.message);
process.exit(1); process.exit(1);

1
test/simple/test-http-parser-free.js

@ -44,6 +44,7 @@ server.listen(common.PORT, function() {
if (++responses === N) { if (++responses === N) {
server.close(); server.close();
} }
res.resume();
}); });
})(i); })(i);
} }

1
test/simple/test-http-request-end-twice.js

@ -36,6 +36,7 @@ server.listen(common.PORT, function() {
assert.ok(!req.end()); assert.ok(!req.end());
server.close(); server.close();
}); });
res.resume();
}); });
}); });

1
test/simple/test-http-request-end.js

@ -47,6 +47,7 @@ server.listen(common.PORT, function() {
method: 'POST' method: 'POST'
}, function(res) { }, function(res) {
console.log(res.statusCode); console.log(res.statusCode);
res.resume();
}).on('error', function(e) { }).on('error', function(e) {
console.log(e.message); console.log(e.message);
process.exit(1); process.exit(1);

6
test/simple/test-http-res-write-end-dont-take-array.js

@ -53,11 +53,13 @@ var server = http.createServer(function(req, res) {
server.listen(common.PORT, function() { server.listen(common.PORT, function() {
// just make a request, other tests handle responses // just make a request, other tests handle responses
http.get({port: common.PORT}, function() { http.get({port: common.PORT}, function(res) {
res.resume();
// lazy serial test, becuase we can only call end once per request // lazy serial test, becuase we can only call end once per request
test += 1; test += 1;
// do it again to test .end(Buffer); // do it again to test .end(Buffer);
http.get({port: common.PORT}, function() { http.get({port: common.PORT}, function(res) {
res.resume();
server.close(); server.close();
}); });
}); });

1
test/simple/test-http-response-readable.js

@ -35,6 +35,7 @@ testServer.listen(common.PORT, function() {
assert.equal(res.readable, false, 'res.readable set to false after end'); assert.equal(res.readable, false, 'res.readable set to false after end');
testServer.close(); testServer.close();
}); });
res.resume();
}); });
}); });

1
test/simple/test-http-set-trailers.js

@ -106,6 +106,7 @@ server.on('listening', function() {
process.exit(); process.exit();
} }
}); });
res.resume();
}); });
outstanding_reqs++; outstanding_reqs++;
}); });

1
test/simple/test-http-status-code.js

@ -59,6 +59,7 @@ function nextTest() {
testIdx += 1; testIdx += 1;
nextTest(); nextTest();
}); });
response.resume();
}); });
} }

2
test/simple/test-http-timeout.js

@ -55,6 +55,8 @@ server.listen(port, function() {
server.close(); server.close();
} }
}) })
res.resume();
}); });
req.setTimeout(1000, callback); req.setTimeout(1000, callback);

1
test/simple/test-https-agent.js

@ -54,6 +54,7 @@ server.listen(common.PORT, function() {
port: common.PORT, port: common.PORT,
rejectUnauthorized: false rejectUnauthorized: false
}, function(res) { }, function(res) {
res.resume();
console.log(res.statusCode); console.log(res.statusCode);
if (++responses == N * M) server.close(); if (++responses == N * M) server.close();
}).on('error', function(e) { }).on('error', function(e) {

2
test/simple/test-https-socket-options.js

@ -55,6 +55,7 @@ server_http.listen(common.PORT, function() {
rejectUnauthorized: false rejectUnauthorized: false
}, function(res) { }, function(res) {
server_http.close(); server_http.close();
res.resume();
}); });
// These methods should exist on the request and get passed down to the socket // These methods should exist on the request and get passed down to the socket
req.setNoDelay(true); req.setNoDelay(true);
@ -77,6 +78,7 @@ server_https.listen(common.PORT+1, function() {
rejectUnauthorized: false rejectUnauthorized: false
}, function(res) { }, function(res) {
server_https.close(); server_https.close();
res.resume();
}); });
// These methods should exist on the request and get passed down to the socket // These methods should exist on the request and get passed down to the socket
req.setNoDelay(true); req.setNoDelay(true);

1
test/simple/test-https-strict.js

@ -170,6 +170,7 @@ function makeReq(path, port, error, host, ca) {
server2.close(); server2.close();
server3.close(); server3.close();
} }
res.resume();
}) })
} }

2
test/simple/test-net-after-close.js

@ -25,12 +25,14 @@ var net = require('net');
var closed = false; var closed = false;
var server = net.createServer(function(s) { var server = net.createServer(function(s) {
console.error('SERVER: got connection');
s.end(); s.end();
}); });
server.listen(common.PORT, function() { server.listen(common.PORT, function() {
var c = net.createConnection(common.PORT); var c = net.createConnection(common.PORT);
c.on('close', function() { c.on('close', function() {
console.error('connection closed');
assert.strictEqual(c._handle, null); assert.strictEqual(c._handle, null);
closed = true; closed = true;
assert.doesNotThrow(function() { assert.doesNotThrow(function() {

28
test/simple/test-net-binary.js

@ -41,12 +41,15 @@ for (var i = 255; i >= 0; i--) {
// safe constructor // safe constructor
var echoServer = net.Server(function(connection) { var echoServer = net.Server(function(connection) {
// connection._readableState.lowWaterMark = 0;
console.error('SERVER got connection');
connection.setEncoding('binary'); connection.setEncoding('binary');
connection.on('data', function(chunk) { connection.on('data', function(chunk) {
common.error('recved: ' + JSON.stringify(chunk)); common.error('SERVER recved: ' + JSON.stringify(chunk));
connection.write(chunk, 'binary'); connection.write(chunk, 'binary');
}); });
connection.on('end', function() { connection.on('end', function() {
console.error('SERVER ending');
connection.end(); connection.end();
}); });
}); });
@ -55,29 +58,44 @@ echoServer.listen(common.PORT);
var recv = ''; var recv = '';
echoServer.on('listening', function() { echoServer.on('listening', function() {
console.error('SERVER listening');
var j = 0; var j = 0;
var c = net.createConnection(common.PORT); var c = net.createConnection({
port: common.PORT
});
// c._readableState.lowWaterMark = 0;
c.setEncoding('binary'); c.setEncoding('binary');
c.on('data', function(chunk) { c.on('data', function(chunk) {
if (j < 256) { console.error('CLIENT data %j', chunk);
common.error('write ' + j); var n = j + chunk.length;
while (j < n && j < 256) {
common.error('CLIENT write ' + j);
c.write(String.fromCharCode(j), 'binary'); c.write(String.fromCharCode(j), 'binary');
j++; j++;
} else { }
if (j === 256) {
console.error('CLIENT ending');
c.end(); c.end();
} }
recv += chunk; recv += chunk;
}); });
c.on('connect', function() { c.on('connect', function() {
console.error('CLIENT connected, writing');
c.write(binaryString, 'binary'); c.write(binaryString, 'binary');
}); });
c.on('close', function() { c.on('close', function() {
console.error('CLIENT closed');
console.dir(recv); console.dir(recv);
echoServer.close(); echoServer.close();
}); });
c.on('finish', function() {
console.error('CLIENT finished');
});
}); });
process.on('exit', function() { process.on('exit', function() {

15
test/simple/test-net-bytes-stats.js

@ -34,33 +34,40 @@ var count = 0;
var tcp = net.Server(function(s) { var tcp = net.Server(function(s) {
console.log('tcp server connection'); console.log('tcp server connection');
// trigger old mode.
s.resume();
s.on('end', function() { s.on('end', function() {
bytesRead += s.bytesRead; bytesRead += s.bytesRead;
console.log('tcp socket disconnect #' + count); console.log('tcp socket disconnect #' + count);
}); });
}); });
tcp.listen(common.PORT, function() { tcp.listen(common.PORT, function doTest() {
console.error('listening');
var socket = net.createConnection(tcpPort); var socket = net.createConnection(tcpPort);
socket.on('connect', function() { socket.on('connect', function() {
count++; count++;
console.log('tcp client connection #' + count); console.error('CLIENT connect #%d', count);
socket.write('foo', function() { socket.write('foo', function() {
console.error('CLIENT: write cb');
socket.end('bar'); socket.end('bar');
}); });
}); });
socket.on('end', function() { socket.on('finish', function() {
bytesWritten += socket.bytesWritten; bytesWritten += socket.bytesWritten;
console.log('tcp client disconnect #' + count); console.error('CLIENT end event #%d', count);
}); });
socket.on('close', function() { socket.on('close', function() {
console.error('CLIENT close event #%d', count);
console.log('Bytes read: ' + bytesRead); console.log('Bytes read: ' + bytesRead);
console.log('Bytes written: ' + bytesWritten); console.log('Bytes written: ' + bytesWritten);
if (count < 2) { if (count < 2) {
console.error('RECONNECTING');
socket.connect(tcpPort); socket.connect(tcpPort);
} else { } else {
tcp.close(); tcp.close();

2
test/simple/test-net-can-reset-timeout.js

@ -28,6 +28,8 @@ var timeoutCount = 0;
var server = net.createServer(function(stream) { var server = net.createServer(function(stream) {
stream.setTimeout(100); stream.setTimeout(100);
stream.resume();
stream.on('timeout', function() { stream.on('timeout', function() {
console.log('timeout'); console.log('timeout');
// try to reset the timeout. // try to reset the timeout.

13
test/simple/test-net-connect-buffer.js

@ -38,6 +38,7 @@ var tcp = net.Server(function(s) {
}); });
s.on('end', function() { s.on('end', function() {
console.error('SERVER: end', buf.toString());
assert.equal(buf, "L'État, c'est moi"); assert.equal(buf, "L'État, c'est moi");
console.log('tcp socket disconnect'); console.log('tcp socket disconnect');
s.end(); s.end();
@ -50,7 +51,7 @@ var tcp = net.Server(function(s) {
}); });
tcp.listen(common.PORT, function() { tcp.listen(common.PORT, function() {
var socket = net.Stream(); var socket = net.Stream({ highWaterMark: 0 });
console.log('Connecting to socket '); console.log('Connecting to socket ');
@ -77,6 +78,7 @@ tcp.listen(common.PORT, function() {
{} {}
].forEach(function(v) { ].forEach(function(v) {
function f() { function f() {
console.error('write', v);
socket.write(v); socket.write(v);
} }
assert.throws(f, TypeError); assert.throws(f, TypeError);
@ -90,12 +92,17 @@ tcp.listen(common.PORT, function() {
// We're still connecting at this point so the datagram is first pushed onto // We're still connecting at this point so the datagram is first pushed onto
// the connect queue. Make sure that it's not added to `bytesWritten` again // the connect queue. Make sure that it's not added to `bytesWritten` again
// when the actual write happens. // when the actual write happens.
var r = socket.write(a, function() { var r = socket.write(a, function(er) {
console.error('write cb');
dataWritten = true; dataWritten = true;
assert.ok(connectHappened); assert.ok(connectHappened);
assert.equal(socket.bytesWritten, Buffer(a + b).length); console.error('socket.bytesWritten', socket.bytesWritten);
//assert.equal(socket.bytesWritten, Buffer(a + b).length);
console.error('data written'); console.error('data written');
}); });
console.error('socket.bytesWritten', socket.bytesWritten);
console.error('write returned', r);
assert.equal(socket.bytesWritten, Buffer(a).length); assert.equal(socket.bytesWritten, Buffer(a).length);
assert.equal(false, r); assert.equal(false, r);

4
test/simple/test-net-connect-options.js

@ -27,6 +27,7 @@ var serverGotEnd = false;
var clientGotEnd = false; var clientGotEnd = false;
var server = net.createServer({allowHalfOpen: true}, function(socket) { var server = net.createServer({allowHalfOpen: true}, function(socket) {
socket.resume();
socket.on('end', function() { socket.on('end', function() {
serverGotEnd = true; serverGotEnd = true;
}); });
@ -39,6 +40,8 @@ server.listen(common.PORT, function() {
port: common.PORT, port: common.PORT,
allowHalfOpen: true allowHalfOpen: true
}, function() { }, function() {
console.error('client connect cb');
client.resume();
client.on('end', function() { client.on('end', function() {
clientGotEnd = true; clientGotEnd = true;
setTimeout(function() { setTimeout(function() {
@ -53,6 +56,7 @@ server.listen(common.PORT, function() {
}); });
process.on('exit', function() { process.on('exit', function() {
console.error('exit', serverGotEnd, clientGotEnd);
assert(serverGotEnd); assert(serverGotEnd);
assert(clientGotEnd); assert(clientGotEnd);
}); });

9
test/simple/test-net-pingpong.js

@ -60,6 +60,8 @@ function pingPongTest(port, host) {
}); });
socket.on('end', function() { socket.on('end', function() {
console.error(socket);
assert.equal(true, socket.allowHalfOpen);
assert.equal(true, socket.writable); // because allowHalfOpen assert.equal(true, socket.writable); // because allowHalfOpen
assert.equal(false, socket.readable); assert.equal(false, socket.readable);
socket.end(); socket.end();
@ -129,10 +131,11 @@ function pingPongTest(port, host) {
} }
/* All are run at once, so run on different ports */ /* All are run at once, so run on different ports */
console.log(common.PIPE);
pingPongTest(common.PIPE); pingPongTest(common.PIPE);
pingPongTest(20988); pingPongTest(common.PORT);
pingPongTest(20989, 'localhost'); pingPongTest(common.PORT + 1, 'localhost');
pingPongTest(20997, '::1'); pingPongTest(common.PORT + 2, '::1');
process.on('exit', function() { process.on('exit', function() {
assert.equal(4, tests_run); assert.equal(4, tests_run);

18
test/simple/test-net-reconnect.js

@ -30,39 +30,49 @@ var client_recv_count = 0;
var disconnect_count = 0; var disconnect_count = 0;
var server = net.createServer(function(socket) { var server = net.createServer(function(socket) {
console.error('SERVER: got socket connection');
socket.resume();
socket.on('connect', function() { socket.on('connect', function() {
console.error('SERVER connect, writing');
socket.write('hello\r\n'); socket.write('hello\r\n');
}); });
socket.on('end', function() { socket.on('end', function() {
console.error('SERVER socket end, calling end()');
socket.end(); socket.end();
}); });
socket.on('close', function(had_error) { socket.on('close', function(had_error) {
//console.log('server had_error: ' + JSON.stringify(had_error)); console.log('SERVER had_error: ' + JSON.stringify(had_error));
assert.equal(false, had_error); assert.equal(false, had_error);
}); });
}); });
server.listen(common.PORT, function() { server.listen(common.PORT, function() {
console.log('listening'); console.log('SERVER listening');
var client = net.createConnection(common.PORT); var client = net.createConnection(common.PORT);
client.setEncoding('UTF8'); client.setEncoding('UTF8');
client.on('connect', function() { client.on('connect', function() {
console.log('client connected.'); console.error('CLIENT connected', client._writableState);
}); });
client.on('data', function(chunk) { client.on('data', function(chunk) {
client_recv_count += 1; client_recv_count += 1;
console.log('client_recv_count ' + client_recv_count); console.log('client_recv_count ' + client_recv_count);
assert.equal('hello\r\n', chunk); assert.equal('hello\r\n', chunk);
console.error('CLIENT: calling end', client._writableState);
client.end(); client.end();
}); });
client.on('end', function() {
console.error('CLIENT end');
});
client.on('close', function(had_error) { client.on('close', function(had_error) {
console.log('disconnect'); console.log('CLIENT disconnect');
assert.equal(false, had_error); assert.equal(false, had_error);
if (disconnect_count++ < N) if (disconnect_count++ < N)
client.connect(common.PORT); // reconnect client.connect(common.PORT); // reconnect

1
test/simple/test-net-remote-address-port.js

@ -34,6 +34,7 @@ var server = net.createServer(function(socket) {
socket.on('end', function() { socket.on('end', function() {
if (++conns_closed == 2) server.close(); if (++conns_closed == 2) server.close();
}); });
socket.resume();
}); });
server.listen(common.PORT, 'localhost', function() { server.listen(common.PORT, 'localhost', function() {

4
test/simple/test-net-write-after-close.js

@ -32,12 +32,16 @@ process.on('exit', function() {
}); });
var server = net.createServer(function(socket) { var server = net.createServer(function(socket) {
socket.resume();
socket.on('error', function(error) { socket.on('error', function(error) {
console.error('got error, closing server', error);
server.close(); server.close();
gotError = true; gotError = true;
}); });
setTimeout(function() { setTimeout(function() {
console.error('about to try to write');
socket.write('test', function(e) { socket.write('test', function(e) {
gotWriteCB = true; gotWriteCB = true;
}); });

13
test/simple/test-pipe-file-to-http.js

@ -31,6 +31,7 @@ var clientReqComplete = false;
var count = 0; var count = 0;
var server = http.createServer(function(req, res) { var server = http.createServer(function(req, res) {
console.error('SERVER request');
var timeoutId; var timeoutId;
assert.equal('POST', req.method); assert.equal('POST', req.method);
req.pause(); req.pause();
@ -63,6 +64,8 @@ server.on('listening', function() {
cp.exec(cmd, function(err, stdout, stderr) { cp.exec(cmd, function(err, stdout, stderr) {
if (err) throw err; if (err) throw err;
console.error('EXEC returned successfully stdout=%d stderr=%d',
stdout.length, stderr.length);
makeRequest(); makeRequest();
}); });
}); });
@ -75,8 +78,15 @@ function makeRequest() {
}); });
common.error('pipe!'); common.error('pipe!');
var s = fs.ReadStream(filename); var s = fs.ReadStream(filename);
s.pipe(req); s.pipe(req);
s.on('data', function(chunk) {
console.error('FS data chunk=%d', chunk.length);
});
s.on('end', function() {
console.error('FS end');
});
s.on('close', function(err) { s.on('close', function(err) {
if (err) throw err; if (err) throw err;
clientReqComplete = true; clientReqComplete = true;
@ -84,7 +94,10 @@ function makeRequest() {
}); });
req.on('response', function(res) { req.on('response', function(res) {
console.error('RESPONSE', res.statusCode, res.headers);
res.resume();
res.on('end', function() { res.on('end', function() {
console.error('RESPONSE end');
server.close(); server.close();
}); });
}); });

1
test/simple/test-pipe.js

@ -125,6 +125,7 @@ function startClient() {
}); });
req.write(buffer); req.write(buffer);
req.end(); req.end();
console.error('ended request', req);
} }
process.on('exit', function() { process.on('exit', function() {

1
test/simple/test-regress-GH-877.js

@ -48,6 +48,7 @@ server.listen(common.PORT, '127.0.0.1', function() {
if (++responses == N) { if (++responses == N) {
server.close(); server.close();
} }
res.resume();
}); });
assert.equal(req.agent, agent); assert.equal(req.agent, agent);

3
test/simple/test-repl-autolibs.js

@ -48,8 +48,9 @@ function test1(){
putIn.write = function (data) { putIn.write = function (data) {
gotWrite = true; gotWrite = true;
if (data.length) { if (data.length) {
// inspect output matches repl output // inspect output matches repl output
assert.equal(data, util.inspect(require('fs'), null, null, false) + '\n'); assert.equal(data, util.inspect(require('fs'), null, 2, false) + '\n');
// globally added lib matches required lib // globally added lib matches required lib
assert.equal(global.fs, require('fs')); assert.equal(global.fs, require('fs'));
test2(); test2();

320
test/simple/test-stream2-basic.js

@ -0,0 +1,320 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var R = require('_stream_readable');
var assert = require('assert');
var util = require('util');
var EE = require('events').EventEmitter;
function TestReader(n) {
R.apply(this);
this._buffer = new Buffer(n || 100);
this._buffer.fill('x');
this._pos = 0;
this._bufs = 10;
}
util.inherits(TestReader, R);
TestReader.prototype.read = function(n) {
var max = this._buffer.length - this._pos;
n = n || max;
n = Math.max(n, 0);
var toRead = Math.min(n, max);
if (toRead === 0) {
// simulate the read buffer filling up with some more bytes some time
// in the future.
setTimeout(function() {
this._pos = 0;
this._bufs -= 1;
if (this._bufs <= 0) {
// read them all!
if (!this.ended) {
this.emit('end');
this.ended = true;
}
} else {
this.emit('readable');
}
}.bind(this), 10);
return null;
}
var ret = this._buffer.slice(this._pos, this._pos + toRead);
this._pos += toRead;
return ret;
};
/////
function TestWriter() {
EE.apply(this);
this.received = [];
this.flush = false;
}
util.inherits(TestWriter, EE);
TestWriter.prototype.write = function(c) {
this.received.push(c.toString());
this.emit('write', c);
return true;
// flip back and forth between immediate acceptance and not.
this.flush = !this.flush;
if (!this.flush) setTimeout(this.emit.bind(this, 'drain'), 10);
return this.flush;
};
TestWriter.prototype.end = function(c) {
if (c) this.write(c);
this.emit('end', this.received);
};
////////
// tiny node-tap lookalike.
var tests = [];
function test(name, fn) {
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: run
});
}
process.nextTick(run);
test('a most basic test', function(t) {
var r = new TestReader(20);
var reads = [];
var expect = [ 'x',
'xx',
'xxx',
'xxxx',
'xxxxx',
'xxxxx',
'xxxxxxxx',
'xxxxxxxxx',
'xxx',
'xxxxxxxxxxxx',
'xxxxxxxx',
'xxxxxxxxxxxxxxx',
'xxxxx',
'xxxxxxxxxxxxxxxxxx',
'xx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx' ];
r.on('end', function() {
t.same(reads, expect);
t.end();
});
var readSize = 1;
function flow() {
var res;
while (null !== (res = r.read(readSize++))) {
reads.push(res.toString());
}
r.once('readable', flow);
}
flow();
});
test('pipe', function(t) {
var r = new TestReader(5);
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ]
var w = new TestWriter;
var flush = true;
w.on('end', function(received) {
t.same(received, expect);
t.end();
});
r.pipe(w);
});
[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) {
test('unpipe', function(t) {
var r = new TestReader(5);
// unpipe after 3 writes, then write to another stream instead.
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ];
expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ];
var w = [ new TestWriter(), new TestWriter() ];
var writes = SPLIT;
w[0].on('write', function() {
if (--writes === 0) {
r.unpipe();
t.equal(r._readableState.pipes, null);
w[0].end();
r.pipe(w[1]);
t.equal(r._readableState.pipes, w[1]);
}
});
var ended = 0;
var ended0 = false;
var ended1 = false;
w[0].on('end', function(results) {
t.equal(ended0, false);
ended0 = true;
ended++;
t.same(results, expect[0]);
});
w[1].on('end', function(results) {
t.equal(ended1, false);
ended1 = true;
ended++;
t.equal(ended, 2);
t.same(results, expect[1]);
t.end();
});
r.pipe(w[0]);
});
});
// both writers should get the same exact data.
test('multipipe', function(t) {
var r = new TestReader(5);
var w = [ new TestWriter, new TestWriter ];
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ];
var c = 2;
w[0].on('end', function(received) {
t.same(received, expect, 'first');
if (--c === 0) t.end();
});
w[1].on('end', function(received) {
t.same(received, expect, 'second');
if (--c === 0) t.end();
});
r.pipe(w[0]);
r.pipe(w[1]);
});
[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) {
test('multi-unpipe', function(t) {
var r = new TestReader(5);
// unpipe after 3 writes, then write to another stream instead.
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ];
expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ];
var w = [ new TestWriter(), new TestWriter(), new TestWriter() ];
var writes = SPLIT;
w[0].on('write', function() {
if (--writes === 0) {
r.unpipe();
w[0].end();
r.pipe(w[1]);
}
});
var ended = 0;
w[0].on('end', function(results) {
ended++;
t.same(results, expect[0]);
});
w[1].on('end', function(results) {
ended++;
t.equal(ended, 2);
t.same(results, expect[1]);
t.end();
});
r.pipe(w[0]);
r.pipe(w[2]);
});
});

76
test/simple/test-stream2-fs.js

@ -0,0 +1,76 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var R = require('_stream_readable');
var assert = require('assert');
var fs = require('fs');
var FSReadable = fs.ReadStream;
var path = require('path');
var file = path.resolve(common.fixturesDir, 'x1024.txt');
var size = fs.statSync(file).size;
// expect to see chunks no more than 10 bytes each.
var expectLengths = [];
for (var i = size; i > 0; i -= 10) {
expectLengths.push(Math.min(i, 10));
}
var util = require('util');
var Stream = require('stream');
util.inherits(TestWriter, Stream);
function TestWriter() {
Stream.apply(this);
this.buffer = [];
this.length = 0;
}
TestWriter.prototype.write = function(c) {
this.buffer.push(c.toString());
this.length += c.length;
return true;
};
TestWriter.prototype.end = function(c) {
if (c) this.buffer.push(c.toString());
this.emit('results', this.buffer);
}
var r = new FSReadable(file, { bufferSize: 10 });
var w = new TestWriter();
w.on('results', function(res) {
console.error(res, w.length);
assert.equal(w.length, size);
var l = 0;
assert.deepEqual(res.map(function (c) {
return c.length;
}), expectLengths);
console.log('ok');
});
r.pipe(w, { chunkSize: 10 });

105
test/simple/test-stream2-pipe-error-handling.js

@ -0,0 +1,105 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var stream = require('stream');
(function testErrorListenerCatches() {
var count = 1000;
var source = new stream.Readable();
source._read = function(n, cb) {
n = Math.min(count, n);
count -= n;
cb(null, new Buffer(n));
};
var unpipedDest;
source.unpipe = function(dest) {
unpipedDest = dest;
stream.Readable.prototype.unpipe.call(this, dest);
};
var dest = new stream.Writable();
dest._write = function(chunk, cb) {
cb();
};
source.pipe(dest);
var gotErr = null;
dest.on('error', function(err) {
gotErr = err;
});
var unpipedSource;
dest.on('unpipe', function(src) {
unpipedSource = src;
});
var err = new Error('This stream turned into bacon.');
dest.emit('error', err);
assert.strictEqual(gotErr, err);
assert.strictEqual(unpipedSource, source);
assert.strictEqual(unpipedDest, dest);
})();
(function testErrorWithoutListenerThrows() {
var count = 1000;
var source = new stream.Readable();
source._read = function(n, cb) {
n = Math.min(count, n);
count -= n;
cb(null, new Buffer(n));
};
var unpipedDest;
source.unpipe = function(dest) {
unpipedDest = dest;
stream.Readable.prototype.unpipe.call(this, dest);
};
var dest = new stream.Writable();
dest._write = function(chunk, cb) {
cb();
};
source.pipe(dest);
var unpipedSource;
dest.on('unpipe', function(src) {
unpipedSource = src;
});
var err = new Error('This stream turned into bacon.');
var gotErr = null;
try {
dest.emit('error', err);
} catch (e) {
gotErr = e;
}
assert.strictEqual(gotErr, err);
assert.strictEqual(unpipedSource, source);
assert.strictEqual(unpipedDest, dest);
})();

109
test/simple/test-stream2-readable-from-list.js

@ -0,0 +1,109 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var assert = require('assert');
var common = require('../common.js');
var fromList = require('_stream_readable')._fromList;
// tiny node-tap lookalike.
var tests = [];
function test(name, fn) {
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: run
});
}
process.nextTick(run);
test('buffers', function(t) {
// have a length
var len = 16;
var list = [ new Buffer('foog'),
new Buffer('bark'),
new Buffer('bazy'),
new Buffer('kuel') ];
// read more than the first element.
var ret = fromList(6, list, 16);
t.equal(ret.toString(), 'foogba');
// read exactly the first element.
ret = fromList(2, list, 10);
t.equal(ret.toString(), 'rk');
// read less than the first element.
ret = fromList(2, list, 8);
t.equal(ret.toString(), 'ba');
// read more than we have.
ret = fromList(100, list, 6);
t.equal(ret.toString(), 'zykuel');
// all consumed.
t.same(list, []);
t.end();
});
test('strings', function(t) {
// have a length
var len = 16;
var list = [ 'foog',
'bark',
'bazy',
'kuel' ];
// read more than the first element.
var ret = fromList(6, list, 16, true);
t.equal(ret, 'foogba');
// read exactly the first element.
ret = fromList(2, list, 10, true);
t.equal(ret, 'rk');
// read less than the first element.
ret = fromList(2, list, 8, true);
t.equal(ret, 'ba');
// read more than we have.
ret = fromList(100, list, 6, true);
t.equal(ret, 'zykuel');
// all consumed.
t.same(list, []);
t.end();
});

299
test/simple/test-stream2-set-encoding.js

@ -0,0 +1,299 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var assert = require('assert');
var R = require('_stream_readable');
var util = require('util');
// tiny node-tap lookalike.
var tests = [];
function test(name, fn) {
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: run
});
}
process.nextTick(run);
/////
util.inherits(TestReader, R);
function TestReader(n, opts) {
R.call(this, util._extend({
bufferSize: 5
}, opts));
this.pos = 0;
this.len = n || 100;
}
TestReader.prototype._read = function(n, cb) {
setTimeout(function() {
if (this.pos >= this.len) {
return cb();
}
n = Math.min(n, this.len - this.pos);
if (n <= 0) {
return cb();
}
this.pos += n;
var ret = new Buffer(n);
ret.fill('a');
return cb(null, ret);
}.bind(this), 1);
};
test('setEncoding utf8', function(t) {
var tr = new TestReader(100);
tr.setEncoding('utf8');
var out = [];
var expect =
[ 'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('setEncoding hex', function(t) {
var tr = new TestReader(100);
tr.setEncoding('hex');
var out = [];
var expect =
[ '6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('setEncoding hex with read(13)', function(t) {
var tr = new TestReader(100);
tr.setEncoding('hex');
var out = [];
var expect =
[ "6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"16161" ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(13)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('encoding: utf8', function(t) {
var tr = new TestReader(100, { encoding: 'utf8' });
var out = [];
var expect =
[ 'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('encoding: hex', function(t) {
var tr = new TestReader(100, { encoding: 'hex' });
var out = [];
var expect =
[ '6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('encoding: hex with read(13)', function(t) {
var tr = new TestReader(100, { encoding: 'hex' });
var out = [];
var expect =
[ "6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"16161" ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(13)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});

314
test/simple/test-stream2-transform.js

@ -0,0 +1,314 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var assert = require('assert');
var common = require('../common.js');
var PassThrough = require('_stream_passthrough');
var Transform = require('_stream_transform');
// tiny node-tap lookalike.
var tests = [];
function test(name, fn) {
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: run
});
}
process.nextTick(run);
/////
test('passthrough', function(t) {
var pt = new PassThrough();
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5).toString(), 'l');
t.end();
});
test('simple transform', function(t) {
var pt = new Transform;
pt._transform = function(c, output, cb) {
var ret = new Buffer(c.length);
ret.fill('x');
output(ret);
cb();
};
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
t.equal(pt.read(5).toString(), 'xxxxx');
t.equal(pt.read(5).toString(), 'xxxxx');
t.equal(pt.read(5).toString(), 'xxxxx');
t.equal(pt.read(5).toString(), 'x');
t.end();
});
test('async passthrough', function(t) {
var pt = new Transform;
pt._transform = function(chunk, output, cb) {
setTimeout(function() {
output(chunk);
cb();
}, 10);
};
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
setTimeout(function() {
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5).toString(), 'l');
t.end();
}, 100);
});
test('assymetric transform (expand)', function(t) {
var pt = new Transform;
// emit each chunk 2 times.
pt._transform = function(chunk, output, cb) {
setTimeout(function() {
output(chunk);
setTimeout(function() {
output(chunk);
cb();
}, 10)
}, 10);
};
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
setTimeout(function() {
t.equal(pt.read(5).toString(), 'foogf');
t.equal(pt.read(5).toString(), 'oogba');
t.equal(pt.read(5).toString(), 'rkbar');
t.equal(pt.read(5).toString(), 'kbazy');
t.equal(pt.read(5).toString(), 'bazyk');
t.equal(pt.read(5).toString(), 'uelku');
t.equal(pt.read(5).toString(), 'el');
t.end();
}, 200);
});
test('assymetric transform (compress)', function(t) {
var pt = new Transform;
// each output is the first char of 3 consecutive chunks,
// or whatever's left.
pt.state = '';
pt._transform = function(chunk, output, cb) {
if (!chunk)
chunk = '';
var s = chunk.toString();
setTimeout(function() {
this.state += s.charAt(0);
if (this.state.length === 3) {
output(new Buffer(this.state));
this.state = '';
}
cb();
}.bind(this), 10);
};
pt._flush = function(output, cb) {
// just output whatever we have.
setTimeout(function() {
output(new Buffer(this.state));
this.state = '';
cb();
}.bind(this), 10);
};
pt._writableState.lowWaterMark = 3;
pt.write(new Buffer('aaaa'));
pt.write(new Buffer('bbbb'));
pt.write(new Buffer('cccc'));
pt.write(new Buffer('dddd'));
pt.write(new Buffer('eeee'));
pt.write(new Buffer('aaaa'));
pt.write(new Buffer('bbbb'));
pt.write(new Buffer('cccc'));
pt.write(new Buffer('dddd'));
pt.write(new Buffer('eeee'));
pt.write(new Buffer('aaaa'));
pt.write(new Buffer('bbbb'));
pt.write(new Buffer('cccc'));
pt.write(new Buffer('dddd'));
pt.end();
// 'abcdeabcdeabcd'
setTimeout(function() {
t.equal(pt.read(5).toString(), 'abcde');
t.equal(pt.read(5).toString(), 'abcde');
t.equal(pt.read(5).toString(), 'abcd');
t.end();
}, 200);
});
test('passthrough event emission', function(t) {
var pt = new PassThrough({
lowWaterMark: 0
});
var emits = 0;
pt.on('readable', function() {
var state = pt._readableState;
console.error('>>> emit readable %d', emits);
emits++;
});
var i = 0;
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5) + '', 'null');
console.error('need emit 0');
pt.write(new Buffer('bazy'));
console.error('should have emitted, but not again');
pt.write(new Buffer('kuel'));
console.error('should have emitted readable now 1 === %d', emits);
t.equal(emits, 1);
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5), null);
console.error('need emit 1');
pt.end();
t.equal(emits, 2);
t.equal(pt.read(5).toString(), 'l');
t.equal(pt.read(5), null);
console.error('should not have emitted again');
t.equal(emits, 2);
t.end();
});
test('passthrough event emission reordered', function(t) {
var pt = new PassThrough;
var emits = 0;
pt.on('readable', function() {
console.error('emit readable', emits)
emits++;
});
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5), null);
console.error('need emit 0');
pt.once('readable', function() {
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5), null);
console.error('need emit 1');
pt.once('readable', function() {
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5), null);
pt.once('readable', function() {
t.equal(pt.read(5).toString(), 'l');
t.equal(pt.read(5), null);
t.equal(emits, 3);
t.end();
});
pt.end();
});
pt.write(new Buffer('kuel'));
});
pt.write(new Buffer('bazy'));
});
test('passthrough facaded', function(t) {
console.error('passthrough facaded');
var pt = new PassThrough;
var datas = [];
pt.on('data', function(chunk) {
datas.push(chunk.toString());
});
pt.on('end', function() {
t.same(datas, ['foog', 'bark', 'bazy', 'kuel']);
t.end();
});
pt.write(new Buffer('foog'));
setTimeout(function() {
pt.write(new Buffer('bark'));
setTimeout(function() {
pt.write(new Buffer('bazy'));
setTimeout(function() {
pt.write(new Buffer('kuel'));
setTimeout(function() {
pt.end();
}, 10);
}, 10);
}, 10);
}, 10);
});

246
test/simple/test-stream2-writable.js

@ -0,0 +1,246 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var W = require('_stream_writable');
var assert = require('assert');
var util = require('util');
util.inherits(TestWriter, W);
function TestWriter() {
W.apply(this, arguments);
this.buffer = [];
this.written = 0;
}
TestWriter.prototype._write = function(chunk, cb) {
// simulate a small unpredictable latency
setTimeout(function() {
this.buffer.push(chunk.toString());
this.written += chunk.length;
cb();
}.bind(this), Math.floor(Math.random() * 10));
};
var chunks = new Array(50);
for (var i = 0; i < chunks.length; i++) {
chunks[i] = new Array(i + 1).join('x');
}
// tiny node-tap lookalike.
var tests = [];
function test(name, fn) {
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.log('ok');
var name = next[0];
var fn = next[1];
if (!fn)
return run();
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: run
});
}
process.nextTick(run);
test('write fast', function(t) {
var tw = new TestWriter({
lowWaterMark: 5,
highWaterMark: 100
});
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.end();
});
chunks.forEach(function(chunk) {
// screw backpressure. Just buffer it all up.
tw.write(chunk);
});
tw.end();
});
test('write slow', function(t) {
var tw = new TestWriter({
lowWaterMark: 5,
highWaterMark: 100
});
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.end();
});
var i = 0;
(function W() {
tw.write(chunks[i++]);
if (i < chunks.length)
setTimeout(W, 10);
else
tw.end();
})();
});
test('write backpressure', function(t) {
var tw = new TestWriter({
lowWaterMark: 5,
highWaterMark: 50
});
var drains = 0;
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.equal(drains, 17);
t.end();
});
tw.on('drain', function() {
drains++;
});
var i = 0;
(function W() {
do {
var ret = tw.write(chunks[i++]);
} while (ret !== false && i < chunks.length);
if (i < chunks.length) {
assert(tw._writableState.length >= 50);
tw.once('drain', W);
} else {
tw.end();
}
})();
});
test('write bufferize', function(t) {
var tw = new TestWriter({
lowWaterMark: 5,
highWaterMark: 100
});
var encodings =
[ 'hex',
'utf8',
'utf-8',
'ascii',
'binary',
'base64',
'ucs2',
'ucs-2',
'utf16le',
'utf-16le',
undefined ];
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got the expected chunks');
});
chunks.forEach(function(chunk, i) {
var enc = encodings[ i % encodings.length ];
chunk = new Buffer(chunk);
tw.write(chunk.toString(enc), enc);
});
t.end();
});
test('write no bufferize', function(t) {
var tw = new TestWriter({
lowWaterMark: 5,
highWaterMark: 100,
decodeStrings: false
});
tw._write = function(chunk, cb) {
assert(Array.isArray(chunk));
assert(typeof chunk[0] === 'string');
chunk = new Buffer(chunk[0], chunk[1]);
return TestWriter.prototype._write.call(this, chunk, cb);
};
var encodings =
[ 'hex',
'utf8',
'utf-8',
'ascii',
'binary',
'base64',
'ucs2',
'ucs-2',
'utf16le',
'utf-16le',
undefined ];
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got the expected chunks');
});
chunks.forEach(function(chunk, i) {
var enc = encodings[ i % encodings.length ];
chunk = new Buffer(chunk);
tw.write(chunk.toString(enc), enc);
});
t.end();
});
test('write callbacks', function (t) {
var callbacks = chunks.map(function(chunk, i) {
return [i, function(er) {
callbacks._called[i] = chunk;
}];
}).reduce(function(set, x) {
set['callback-' + x[0]] = x[1];
return set;
}, {});
callbacks._called = [];
var tw = new TestWriter({
lowWaterMark: 5,
highWaterMark: 100
});
tw.on('finish', function() {
process.nextTick(function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.same(callbacks._called, chunks, 'called all callbacks');
t.end();
});
});
chunks.forEach(function(chunk, i) {
tw.write(chunk, callbacks['callback-' + i]);
});
tw.end();
});

1
test/simple/test-tcp-wrap-connect.js

@ -54,6 +54,7 @@ var shutdownCount = 0;
var server = require('net').Server(function(s) { var server = require('net').Server(function(s) {
console.log('got connection'); console.log('got connection');
connectCount++; connectCount++;
s.resume();
s.on('end', function() { s.on('end', function() {
console.log('got eof'); console.log('got eof');
endCount++; endCount++;

15
test/simple/test-tls-pause.js

@ -41,6 +41,9 @@ var received = 0;
var server = tls.Server(options, function(socket) { var server = tls.Server(options, function(socket) {
socket.pipe(socket); socket.pipe(socket);
socket.on('data', function(c) {
console.error('data', c.length);
});
}); });
server.listen(common.PORT, function() { server.listen(common.PORT, function() {
@ -49,11 +52,16 @@ server.listen(common.PORT, function() {
port: common.PORT, port: common.PORT,
rejectUnauthorized: false rejectUnauthorized: false
}, function() { }, function() {
console.error('connected');
client.pause(); client.pause();
common.debug('paused'); common.debug('paused');
send(); send();
function send() { function send() {
if (client.write(new Buffer(bufSize))) { console.error('sending');
var ret = client.write(new Buffer(bufSize));
console.error('write => %j', ret);
if (false !== ret) {
console.error('write again');
sent += bufSize; sent += bufSize;
assert.ok(sent < 100 * 1024 * 1024); // max 100MB assert.ok(sent < 100 * 1024 * 1024); // max 100MB
return process.nextTick(send); return process.nextTick(send);
@ -62,12 +70,15 @@ server.listen(common.PORT, function() {
common.debug('sent: ' + sent); common.debug('sent: ' + sent);
resumed = true; resumed = true;
client.resume(); client.resume();
common.debug('resumed'); console.error('resumed', client);
} }
}); });
client.on('data', function(data) { client.on('data', function(data) {
console.error('data');
assert.ok(resumed); assert.ok(resumed);
received += data.length; received += data.length;
console.error('received', received);
console.error('sent', sent);
if (received >= sent) { if (received >= sent) {
common.debug('received: ' + received); common.debug('received: ' + received);
client.end(); client.end();

7
test/simple/test-zlib-invalid-input.js

@ -50,13 +50,6 @@ unzips.forEach(function (uz, i) {
uz.on('error', function(er) { uz.on('error', function(er) {
console.error('Error event', er); console.error('Error event', er);
hadError[i] = true; hadError[i] = true;
// to be friendly to the Stream API, zlib objects just return true and
// ignore data on the floor after an error. It's up to the user to
// catch the 'error' event and do something intelligent. They do not
// emit any more data, however.
assert.equal(uz.write('also invalid'), true);
assert.equal(uz.end(), true);
}); });
uz.on('end', function(er) { uz.on('end', function(er) {

17
test/simple/test-zlib-random-byte-pipes.js

@ -150,8 +150,25 @@ var inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 });
var out = new HashStream(); var out = new HashStream();
var gzip = zlib.createGzip(); var gzip = zlib.createGzip();
var gunz = zlib.createGunzip(); var gunz = zlib.createGunzip();
inp.pipe(gzip).pipe(gunz).pipe(out); inp.pipe(gzip).pipe(gunz).pipe(out);
inp.on('data', function(c) {
console.error('inp data', c.length);
});
gzip.on('data', function(c) {
console.error('gzip data', c.length);
});
gunz.on('data', function(c) {
console.error('gunz data', c.length);
});
out.on('data', function(c) {
console.error('out data', c.length);
});
var didSomething = false; var didSomething = false;
out.on('data', function(c) { out.on('data', function(c) {
didSomething = true; didSomething = true;

Loading…
Cancel
Save