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.
Writer.prototype.on = function() {};
Writer.prototype.once = function() {};
Writer.prototype.emit = function() {};
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.
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`.
### hash.update(data, [input_encoding])
@ -114,6 +119,11 @@ called.
Creates and returns a hmac object, a cryptographic hmac with the given
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
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,
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)
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`.
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])
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`.
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])
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
display the available signing algorithms. Examples are `'RSA-SHA256'`.
## Class: Signer
## Class: Sign
Class for generating signatures.
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.
### signer.sign(private_key, [output_format])
### sign.sign(private_key, [output_format])
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.
Returns the signature in `output_format` which can be `'binary'`,
`'hex'` or `'base64'`. If no encoding is provided, then a buffer is
returned.
Note: `signer` object can not be used after `sign()` method been
Note: `sign` object can not be used after `sign()` method been
called.
## crypto.createVerify(algorithm)
@ -281,6 +311,12 @@ Class for verifying signatures.
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)
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
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
[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
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-->
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
`setEncoding()` was used.
* `options` {Object}
* `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
`Readable Stream` emits a `'data'` event.
In classes that extend the Readable class, make sure to call the
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).
Indicates that no more `'data'` events will happen. If the stream is
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.
### Event: 'close'
`function () { }`
Emitted when the underlying resource (for example, the backing file
descriptor) has been closed. Not all streams will emit this.
### stream.readable
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])
### readable.setEncoding(encoding)
Makes the `'data'` event emit a string instead of a `Buffer`. `encoding`
can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`. Defaults
to `'utf8'`.
### stream.pause()
can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`.
Issues an advisory signal to the underlying communication layer,
requesting that no further data be sent until `resume()` is called.
The encoding can also be set by specifying an `encoding` field to the
constructor.
Note that, due to the advisory nature, certain streams will not be
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.
### readable.read([size])
### 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`
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.
If there is no data to consume, or if there are fewer bytes in the
internal buffer than the `size` argument, then `null` is returned, and
a future `'readable'` event will be emitted when more is available.
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
this stream gets written to `destination`. The destination and source
streams are kept in sync by pausing and resuming as necessary.
Connects this readable stream to `destination` WriteStream. Incoming
data on this stream gets written to `destination`. Properly manages
back-pressure so that a slow destination will not be overwhelmed by a
fast readable stream.
This function returns the `destination` stream.
Emulating the Unix `cat` command:
process.stdin.resume(); process.stdin.pipe(process.stdout);
For example, emulating the Unix `cat` command:
process.stdin.pipe(process.stdout);
By default `end()` is called on the destination when the source stream
emits `end`, so that `destination` is no longer writable. Pass `{ end:
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.
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() {
process.stdout.write("Goodbye\n"); });
Ceases the flow of data. No `'data'` events are emitted while the
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-->
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
buffering again. Listen for it when `stream.write()` returns `false`.
* `options` {Object}
* `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
`stream.write()` has previously returned `false`. To avoid receiving unwanted
`'drain'` events, listen using `stream.once()`.
In classes that extend the Writable class, make sure to call the
constructor so that the buffering settings can be properly
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'
`function (src) { }`
* `source` {Readable Stream}
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
`'error'` occurred or `end()` / `destroy()` was called.
All Transform stream implementations must provide a `_transform`
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`
if the string has been flushed to the kernel buffer. Returns `false` to
indicate that the kernel buffer is full, and the data will be sent out
in the future. The `'drain'` event will indicate when the kernel buffer
is empty again. The `encoding` defaults to `'utf8'`.
`_transform` should do whatever has to be done in this specific
Transform class, to handle the bytes being written, and pass them off
to the readable portion of the interface. Do asynchronous I/O,
process things, and so on.
### 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
write data to be sent before closing the stream.
* `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 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
EOF or FIN. This is useful to reduce the number of packets sent.
In some cases, your transform operation may need to emit a bit more
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`
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.
## Class: stream.PassThrough
### 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

2
lib/_debugger.js

@ -36,7 +36,7 @@ exports.start = function(argv, stdin, stdout) {
}
// Setup input/output streams
stdin = stdin || process.openStdin();
stdin = stdin || process.stdin;
stdout = stdout || process.stdout;
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
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var zlib = require('zlib');
// a passthrough stream.
// basically just the most minimal sort of Transform stream.
// Every written chunk gets output as-is.
['Deflate', 'Inflate', 'Gzip', 'Gunzip', 'DeflateRaw', 'InflateRaw', 'Unzip']
.forEach(function (name) {
var a = false;
var zStream = new zlib[name]();
zStream.on('close', function () {
a = true;
});
zStream.destroy();
module.exports = PassThrough;
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': {
send: function(message, socket) {
// pause socket so no data is lost, will be resumed later
socket.pause();
// if the socket wsa created by net.Server
if (socket.server) {
@ -142,7 +141,6 @@ var handleConversion = {
got: function(message, handle, emit) {
var socket = new net.Socket({handle: handle});
socket.readable = socket.writable = true;
socket.pause();
// if the socket was created by net.Server we will track the socket
if (message.key) {
@ -153,7 +151,6 @@ var handleConversion = {
}
emit(socket);
socket.resume();
}
}
};

79
lib/crypto.js

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

423
lib/fs.js

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

134
lib/http.js

@ -114,19 +114,30 @@ function parserOnHeadersComplete(info) {
return skipBody;
}
// XXX This is a mess.
// TODO: http.Parser should be a Writable emits request/response events.
function parserOnBody(b, start, len) {
var parser = this;
var slice = b.slice(start, start + len);
if (parser.incoming._paused || parser.incoming._pendings.length) {
parser.incoming._pendings.push(slice);
} else {
parser.incoming._emitData(slice);
var stream = parser.incoming;
var rs = stream._readableState;
var socket = stream.socket;
// 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() {
var parser = this;
parser.incoming.complete = true;
var stream = parser.incoming;
var socket = stream.socket;
stream.complete = true;
// Emit any trailing headers.
var headers = parser._headers;
@ -140,19 +151,13 @@ function parserOnMessageComplete() {
parser._url = '';
}
if (!parser.incoming.upgrade) {
if (!stream.upgrade)
// For upgraded connections, also emit this after parser.execute
if (parser.incoming._paused || parser.incoming._pendings.length) {
parser.incoming._pendings.push(END_OF_FILE);
} else {
parser.incoming.readable = false;
parser.incoming._emitEnd();
}
}
stream._readableState.onread(null, null);
if (parser.socket.readable) {
// 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. */
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.connection = socket;
@ -276,77 +285,49 @@ function IncomingMessage(socket) {
this.readable = true;
this._paused = false;
this._pendings = [];
this._endEmitted = false;
this._pendingIndex = 0;
// request (server) only
this.url = '';
this.method = null;
// response (client) only
this.statusCode = null;
this.client = this.socket;
// flag for backwards compatibility grossness.
this._consuming = false;
}
util.inherits(IncomingMessage, Stream);
util.inherits(IncomingMessage, Stream.Readable);
exports.IncomingMessage = IncomingMessage;
IncomingMessage.prototype.destroy = function(error) {
this.socket.destroy(error);
IncomingMessage.prototype.read = function(n) {
this._consuming = true;
return Stream.Readable.prototype.read.call(this, n);
};
IncomingMessage.prototype.setEncoding = function(encoding) {
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
this._decoder = new StringDecoder(encoding);
};
IncomingMessage.prototype.pause = function() {
this._paused = true;
this.socket.pause();
IncomingMessage.prototype._read = function(n, callback) {
// We actually do almost nothing here, because the parserOnBody
// 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);
else
this.socket.resume();
};
IncomingMessage.prototype.resume = function() {
this._paused = false;
if (this.socket) {
this.socket.resume();
}
this._emitPending();
IncomingMessage.prototype.destroy = function(error) {
this.socket.destroy(error);
};
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) {
@ -1016,7 +997,7 @@ ServerResponse.prototype.writeHead = function(statusCode) {
// 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.
if (this._expect_continue && ! this._sent100) {
if (this._expect_continue && !this._sent100) {
this.shouldKeepAlive = false;
}
@ -1321,11 +1302,10 @@ function socketCloseListener() {
// Socket closed before we emitted 'end' below.
req.res.emit('aborted');
var res = req.res;
req.res._emitPending(function() {
res._emitEnd();
res.on('end', function() {
res.emit('close');
res = null;
});
res._readableState.onread(null, null);
} else if (!req.res && !req._hadError) {
// This socket error fired before we started to
// receive a response. The error needs to
@ -1428,11 +1408,13 @@ function socketOnData(d, start, end) {
}
// client
function parserOnIncomingClient(res, shouldKeepAlive) {
var parser = this;
var socket = this.socket;
var req = socket._httpMessage;
// propogate "domain" setting...
if (req.domain && !res.domain) {
debug('setting "res.domain"');
@ -1480,15 +1462,21 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
COUNTER_HTTP_CLIENT_RESPONSE();
req.emit('response', res);
req.res = res;
res.req = req;
var handled = req.emit('response', res);
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;
}
// client
function responseOnEnd() {
var res = this;
var req = res.req;
@ -1784,7 +1772,7 @@ function connectionListener(socket) {
incoming.push(req);
var res = new ServerResponse(req);
debug('server response shouldKeepAlive: ' + shouldKeepAlive);
res.shouldKeepAlive = shouldKeepAlive;
DTRACE_HTTP_SERVER_REQUEST(req, socket);
COUNTER_HTTP_SERVER_REQUEST();
@ -1806,6 +1794,12 @@ function connectionListener(socket) {
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);
if (res._last) {

470
lib/net.js

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var events = require('events');
var Stream = require('stream');
var stream = require('stream');
var timers = require('timers');
var util = require('util');
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;
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 {
debug = function() { };
}
@ -110,12 +110,8 @@ function normalizeConnectArgs(args) {
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) {
self._pendingWriteReqs = 0;
self._flags = 0;
self._connectQueueSize = 0;
self.destroyed = false;
self.errorEmitted = false;
self.bytesRead = 0;
@ -131,8 +127,6 @@ function initSocketHandle(self) {
function Socket(options) {
if (!(this instanceof Socket)) return new Socket(options);
Stream.call(this);
switch (typeof options) {
case 'number':
options = { fd: options }; // Legacy interface.
@ -142,7 +136,10 @@ function Socket(options) {
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
} else {
this._handle = createPipe();
@ -150,17 +147,105 @@ function Socket(options) {
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);
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.Stream = Socket; // Legacy naming.
Socket.prototype.listen = function() {
debug('socket.listen');
var self = this;
self.on('connection', arguments[0]);
listen(self, null, null, null);
@ -230,96 +315,62 @@ Object.defineProperty(Socket.prototype, 'readyState', {
Object.defineProperty(Socket.prototype, 'bufferSize', {
get: function() {
if (this._handle) {
return this._handle.writeQueueSize + this._connectQueueSize;
return this._handle.writeQueueSize;
}
}
});
Socket.prototype.pause = function() {
this._paused = true;
if (this._handle && !this._connecting) {
this._handle.readStop();
// Just call handle.readStart until we have enough in the buffer
Socket.prototype._read = function(n, callback) {
debug('_read');
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() {
this._paused = false;
if (this._handle && !this._connecting) {
this._handle.readStart();
if (!this._handle.reading) {
debug('Socket._read readStart');
this._handle.reading = true;
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) {
if (this._connecting && ((this._flags & FLAG_SHUTDOWN_QUEUED) == 0)) {
// still connecting, add data to buffer
if (data) this.write(data, encoding);
this.writable = false;
this._flags |= FLAG_SHUTDOWN_QUEUED;
}
if (!this.writable) return;
stream.Duplex.prototype.end.call(this, data, encoding);
this.writable = false;
if (data) this.write(data, encoding);
DTRACE_NET_STREAM_END(this);
if (!this.readable) {
this.destroySoon();
} else {
this._flags |= FLAG_SHUTDOWN;
var shutdownReq = this._handle.shutdown();
if (!shutdownReq) {
this._destroy(errnoException(errno, 'shutdown'));
return false;
}
shutdownReq.oncomplete = afterShutdown;
}
return true;
// just in case we're waiting for an EOF.
if (!this._readableState.endEmitted)
this.read(0);
return;
};
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() {
this.writable = false;
this._flags |= FLAG_DESTROY_SOON;
if (this._pendingWriteReqs == 0) {
this._destroy();
}
};
if (this.writable)
this.end();
Socket.prototype._connectQueueCleanUp = function(exception) {
this._connecting = false;
this._connectQueueSize = 0;
this._connectQueue = null;
if (this._writableState.finishing || this._writableState.finished)
this.destroy();
else
this.once('finish', this.destroy);
};
Socket.prototype._destroy = function(exception, cb) {
debug('destroy');
var self = this;
function fireErrorCallbacks() {
@ -333,13 +384,12 @@ Socket.prototype._destroy = function(exception, cb) {
};
if (this.destroyed) {
debug('already destroyed, fire error callbacks');
fireErrorCallbacks();
return;
}
self._connectQueueCleanUp();
debug('destroy');
self._connecting = false;
this.readable = this.writable = false;
@ -347,6 +397,8 @@ Socket.prototype._destroy = function(exception, cb) {
debug('close');
if (this._handle) {
if (this !== process.stderr)
debug('close handle');
this._handle.close();
this._handle.onread = noop;
this._handle = null;
@ -355,6 +407,7 @@ Socket.prototype._destroy = function(exception, cb) {
fireErrorCallbacks();
process.nextTick(function() {
debug('emit close');
self.emit('close', exception ? true : false);
});
@ -362,6 +415,7 @@ Socket.prototype._destroy = function(exception, cb) {
if (this.server) {
COUNTER_NET_SERVER_CONNECTION_CLOSE(this);
debug('has server');
this.server._connections--;
if (this.server._emitCloseIfDrained) {
this.server._emitCloseIfDrained();
@ -371,10 +425,13 @@ Socket.prototype._destroy = function(exception, cb) {
Socket.prototype.destroy = function(exception) {
debug('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) {
var handle = this;
var self = handle.owner;
@ -383,47 +440,56 @@ function onread(buffer, offset, length) {
timers.active(self);
var end = offset + length;
debug('onread', global.errno, offset, length, end);
if (buffer) {
// Emit 'data' event.
debug('got data');
if (self._decoder) {
// Emit a string.
var string = self._decoder.write(buffer.slice(offset, end));
if (string.length) self.emit('data', string);
} else {
// Emit a slice. Attempt to avoid slicing the buffer if no one is
// listening for 'data'.
if (self._events && self._events['data']) {
self.emit('data', buffer.slice(offset, end));
}
// read success.
// In theory (and in practice) calling readStop right now
// will prevent this from being called again until _read() gets
// called again.
// if we didn't get any bytes, that doesn't necessarily mean EOF.
// wait for the next one.
if (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._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
if (self.ondata) self.ondata(buffer, offset, end);
} else if (errno == 'EOF') {
// EOF
self.readable = false;
debug('EOF');
assert.ok(!(self._flags & FLAG_GOT_EOF));
self._flags |= FLAG_GOT_EOF;
if (self._readableState.length === 0)
self.readable = false;
// We call destroy() before end(). 'close' not emitted until nextTick so
// the 'end' event will come first as required.
if (!self.writable) self._destroy();
if (self.onend) self.once('end', self.onend);
if (!self.allowHalfOpen) self.end();
if (self._decoder) {
var ret = self._decoder.end();
if (ret)
self.emit('data', ret);
}
if (self._events && self._events['end']) self.emit('end');
if (self.onend) self.onend();
// send a null to the _read cb to signal the end of data.
self._readableState.onread(null, null);
// internal end event so that we know that the actual socket
// is no longer readable, and we can start the shutdown
// procedure. No need to wait for all the data to be consumed.
self.emit('_socketEnd');
} else {
debug('error', errno);
// Error
if (errno == 'ECONNRESET') {
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() {
if (!this._handle || !this._handle.getpeername) {
return {};
@ -465,63 +525,39 @@ Socket.prototype.__defineGetter__('remotePort', function() {
});
/*
* Arguments data, [encoding], [cb]
*/
Socket.prototype.write = function(data, arg1, arg2) {
var encoding, cb;
Socket.prototype.write = function(chunk, encoding, cb) {
if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk))
throw new TypeError('invalid data');
return stream.Duplex.prototype.write.apply(this, arguments);
};
// 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') {
encoding = (encoding || 'utf8').toLowerCase();
switch (encoding) {
case '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;
Socket.prototype._write = function(dataEncoding, cb) {
assert(Array.isArray(dataEncoding));
var data = dataEncoding[0];
var encoding = dataEncoding[1] || 'utf8';
default:
data = new Buffer(data, encoding);
}
} else if (!Buffer.isBuffer(data)) {
throw new TypeError('First argument must be a buffer or a string.');
}
if (this !== process.stderr && this !== process.stdout)
debug('Socket._write');
// 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) {
this._connectQueueSize += data.length;
if (this._connectQueue) {
this._connectQueue.push([data, encoding, cb]);
} else {
this._connectQueue = [[data, encoding, cb]];
}
return false;
debug('_write: waiting for connection');
this._pendingWrite = dataEncoding;
this.once('connect', function() {
debug('_write: connected now, try again');
this._write(dataEncoding, cb);
});
return;
}
this._pendingWrite = null;
return this._write(data, encoding, cb);
};
Socket.prototype._write = function(data, encoding, cb) {
timers.active(this);
if (!this._handle) {
debug('already destroyed');
this._destroy(new Error('This socket is closed.'), cb);
return false;
}
@ -550,39 +586,32 @@ Socket.prototype._write = function(data, encoding, cb) {
break;
default:
assert(0);
writeReq = this._handle.writeBuffer(new Buffer(data, encoding));
break;
}
}
if (!writeReq || typeof writeReq !== 'object') {
this._destroy(errnoException(errno, 'write'), cb);
return false;
}
if (!writeReq || typeof writeReq !== 'object')
return this._destroy(errnoException(errno, 'write'), cb);
writeReq.oncomplete = afterWrite;
writeReq.cb = cb;
this._pendingWriteReqs++;
this._bytesDispatched += writeReq.bytes;
return this._handle.writeQueueSize == 0;
};
Socket.prototype.__defineGetter__('bytesWritten', function() {
var bytes = this._bytesDispatched,
connectQueue = this._connectQueue;
state = this._writableState,
pending = this._pendingWrite;
if (connectQueue) {
connectQueue.forEach(function(el) {
var data = el[0];
if (Buffer.isBuffer(data)) {
bytes += data.length;
} else {
bytes += Buffer.byteLength(data, el[1]);
}
}, this);
}
state.buffer.forEach(function(el) {
bytes += Buffer.byteLength(el[0], el[1]);
});
if (pending)
bytes += Buffer.byteLength(pending[0], pending[1]);
return bytes;
});
@ -590,30 +619,28 @@ Socket.prototype.__defineGetter__('bytesWritten', function() {
function afterWrite(status, handle, req) {
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.
if (self.destroyed) {
debug('afterWrite destroyed');
return;
}
if (status) {
debug('write failure', errnoException(errno, 'write'));
self._destroy(errnoException(errno, 'write'), req.cb);
return;
}
timers.active(self);
self._pendingWriteReqs--;
if (self._pendingWriteReqs == 0) {
self.emit('drain');
}
if (self !== process.stderr && self !== process.stdout)
debug('afterWrite call cb');
if (req.cb) req.cb();
if (self._pendingWriteReqs == 0 && self._flags & FLAG_DESTROY_SOON) {
self._destroy();
}
req.cb.call(self);
}
@ -663,10 +690,21 @@ Socket.prototype.connect = function(options, cb) {
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 pipe = !!options.path;
if (this.destroyed || !this._handle) {
if (!this._handle) {
this._handle = pipe ? createPipe() : createTCP();
initSocketHandle(this);
}
@ -755,28 +793,15 @@ function afterConnect(status, handle, req, readable, writable) {
self.writable = writable;
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');
if (self._flags & FLAG_SHUTDOWN_QUEUED) {
// end called before connected - call end now with no data
self._flags &= ~FLAG_SHUTDOWN_QUEUED;
self.end();
}
// start the first read, or get an immediate EOF.
// this doesn't actually consume any bytes, because len=0.
if (readable)
self.read(0);
} else {
self._connectQueueCleanUp();
self._connecting = false;
self._destroy(errnoException(errno, 'connect'));
}
}
@ -831,9 +856,9 @@ function Server(/* [ options, ] listener */) {
configurable: true, enumerable: true
});
this.allowHalfOpen = options.allowHalfOpen || false;
this._handle = null;
this.allowHalfOpen = options.allowHalfOpen || false;
}
util.inherits(Server, events.EventEmitter);
exports.Server = Server;
@ -901,12 +926,14 @@ var createServerHandle = exports._createServerHandle =
Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
debug('listen2', address, port, addressType, backlog);
var self = this;
var r = 0;
// 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.
if (!self._handle) {
debug('_listen2: create a handle');
self._handle = createServerHandle(address, port, addressType, fd);
if (!self._handle) {
var error = errnoException(errno, 'listen');
@ -915,6 +942,8 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
});
return;
}
} else {
debug('_listen2: have a handle already');
}
self._handle.onconnection = onconnection;
@ -1049,7 +1078,6 @@ function onconnection(clientHandle) {
});
socket.readable = socket.writable = true;
clientHandle.readStart();
self._connections++;
socket.server = self;
@ -1086,11 +1114,17 @@ Server.prototype.close = function(cb) {
};
Server.prototype._emitCloseIfDrained = function() {
debug('SERVER _emitCloseIfDrained');
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() {
debug('SERVER: 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._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster',
'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net',
'os', 'path', 'punycode', 'querystring', 'readline', 'repl',
'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net', 'os',
'path', 'punycode', 'querystring', 'readline', 'repl', 'stream',
'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
// USE OR OTHER DEALINGS IN THE SOFTWARE.
module.exports = Stream;
var events = require('events');
var util = require('util');
function Stream() {
events.EventEmitter.call(this);
}
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
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) {
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
// 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) {
this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, '');
assertEncoding(encoding);
switch (this.encoding) {
case 'utf8':
// 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.');
function ReadStream(fd) {
if (!(this instanceof ReadStream)) return new ReadStream(fd);
net.Socket.call(this, {
function ReadStream(fd, options) {
if (!(this instanceof ReadStream))
return new ReadStream(fd, options);
options = util._extend({
highWaterMark: 0,
lowWaterMark: 0,
handle: new TTY(fd, true)
});
}, options);
net.Socket.call(this, options);
this.readable = true;
this.writable = 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);
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) {
flag = !!flag;
this._handle.setRawMode(flag);
this.isRaw = flag;
};
ReadStream.prototype.isTTY = true;
function WriteStream(fd) {
if (!(this instanceof WriteStream)) return new WriteStream(fd);
net.Socket.call(this, {
handle: new TTY(fd, false)
handle: new TTY(fd, false),
readable: false,
writable: true
});
this.readable = false;

215
lib/zlib.js

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

5
node.gyp

@ -44,6 +44,11 @@
'lib/readline.js',
'lib/repl.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/sys.js',
'lib/timers.js',

39
src/node.js

@ -140,7 +140,6 @@
} else {
// Read all of stdin - execute it.
process.stdin.resume();
process.stdin.setEncoding('utf8');
var code = '';
@ -497,17 +496,20 @@
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
var tty = NativeModule.require('tty');
stdin = new tty.ReadStream(fd);
stdin = new tty.ReadStream(fd, {
highWaterMark: 0,
lowWaterMark: 0
});
break;
case 'FILE':
var fs = NativeModule.require('fs');
stdin = new fs.ReadStream(null, {fd: fd});
stdin = new fs.ReadStream(null, { fd: fd });
break;
case 'PIPE':
var net = NativeModule.require('net');
stdin = new net.Stream(fd);
stdin = new net.Stream({ fd: fd });
stdin.readable = true;
break;
@ -520,16 +522,23 @@
stdin.fd = fd;
// stdin starts out life in a paused state, but node doesn't
// know yet. Call pause() explicitly to unref() it.
stdin.pause();
// when piping stdin to a destination stream,
// let the data begin to flow.
var pipe = stdin.pipe;
stdin.pipe = function(dest, opts) {
stdin.resume();
return pipe.call(stdin, dest, opts);
};
// know yet. Explicitly to readStop() it to put it in the
// not-reading state.
if (stdin._handle && stdin._handle.readStop) {
stdin._handle.reading = false;
stdin._readableState.reading = false;
stdin._handle.readStop();
}
// 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;
});
@ -701,8 +710,8 @@
var nativeModule = new NativeModule(id);
nativeModule.compile();
nativeModule.cache();
nativeModule.compile();
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");
ctx->write_in_progress_ = true;
assert(!args[0]->IsUndefined() && "must provide flush value");
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 *out;
size_t in_off, in_len, out_off, out_len;
@ -483,6 +495,7 @@ void InitZlib(Handle<Object> target) {
callback_sym = NODE_PSYMBOL("callback");
onerror_sym = NODE_PSYMBOL("onerror");
// valid flush values.
NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_PARTIAL_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 12
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 process.nextTick (node.js:362:9)
at f (*test*message*max_tick_depth_trace.js:30:13)
at process._tickCallback (node.js:335:13)
at maxTickWarn (node.js:*:*)
at process.nextTick (node.js:*:*
at f (*test*message*max_tick_depth_trace.js:*:*)
at process._tickCallback (node.js:*:*)
tick 11
tick 10
tick 9
@ -23,9 +23,9 @@ tick 4
tick 3
tick 2
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 process.nextTick (node.js:362:9)
at f (*test*message*max_tick_depth_trace.js:30:13)
at process._tickCallback (node.js:335:13)
at maxTickWarn (node.js:*:*)
at process.nextTick (node.js:*:*
at f (*test*message*max_tick_depth_trace.js:*:*)
at process._tickCallback (node.js:*:*)
tick 1
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 Socket.<anonymous> (node.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:*:*)
42
42
@ -18,26 +19,28 @@ SyntaxError: Strict mode code may not include a with statement
throw new Error("hello")
^
Error: hello
at [stdin]:1:7
at [stdin]:1:*
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
at evalScript (node.js:*:*)
at Socket.<anonymous> (node.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:*:*)
[stdin]:1
throw new Error("hello")
^
Error: hello
at [stdin]:1:7
at [stdin]:1:*
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
at evalScript (node.js:*:*)
at Socket.<anonymous> (node.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:*:*)
100
@ -51,7 +54,8 @@ ReferenceError: y is not defined
at evalScript (node.js:*:*)
at Socket.<anonymous> (node.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:*:*)
[stdin]:1

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

@ -31,6 +31,8 @@ if (process.argv[2] === 'child') {
server.on('connection', function(socket) {
socket.resume();
process.on('disconnect', function() {
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 fork = require('child_process').fork;
var net = require('net');
var count = 12;
if (process.argv[2] === 'child') {
var endMe = null;
var needEnd = [];
process.on('message', function(m, socket) {
if (!socket) return;
console.error('got socket', m);
// will call .end('end') or .write('write');
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
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) {
if (m !== 'close') return;
endMe.end('end');
endMe = null;
console.error('got close message');
needEnd.forEach(function(endMe, i) {
console.error('%d ending %d', process.pid, i);
endMe.end('end');
});
});
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;
});
@ -61,7 +89,7 @@ if (process.argv[2] === 'child') {
var connected = 0;
server.on('connection', function(socket) {
switch (connected) {
switch (connected % 6) {
case 0:
child1.send('end', socket); break;
case 1:
@ -77,7 +105,7 @@ if (process.argv[2] === 'child') {
}
connected += 1;
if (connected === 6) {
if (connected === count) {
closeServer();
}
});
@ -85,17 +113,23 @@ if (process.argv[2] === 'child') {
var disconnected = 0;
server.on('listening', function() {
var j = 6, client;
var j = count, client;
while (j--) {
client = net.connect(common.PORT, '127.0.0.1');
client.on('close', function() {
console.error('CLIENT: close event in master');
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;
server.on('close', function() {
console.error('server close');
closeEmitted = true;
child1.kill();
@ -107,14 +141,18 @@ if (process.argv[2] === 'child') {
var timeElasped = 0;
var closeServer = function() {
console.error('closeServer');
var startTime = Date.now();
server.on('close', function() {
console.error('emit(close)');
timeElasped = Date.now() - startTime;
});
console.error('calling server.close');
server.close();
setTimeout(function() {
console.error('sending close to children');
child1.send('close');
child2.send('close');
child3.disconnect();
@ -122,8 +160,8 @@ if (process.argv[2] === 'child') {
};
process.on('exit', function() {
assert.equal(disconnected, 6);
assert.equal(connected, 6);
assert.equal(disconnected, count);
assert.equal(connected, count);
assert.ok(closeEmitted);
assert.ok(timeElasped >= 190 && timeElasped <= 1000,
'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) {
console.log('child said: ' + JSON.stringify(data));
if (!gotHelloWorld) {
console.error('testing for hello world');
assert.equal('hello world\r\n', data);
gotHelloWorld = true;
console.error('writing echo me');
child.stdin.write('echo me\r\n');
} else {
console.error('testing for echo me');
assert.equal('echo me\r\n', data);
gotEcho = true;
child.stdin.end();

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

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

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

@ -81,6 +81,7 @@ else if (cluster.isMaster) {
var check = function(type, result) {
checks[type].receive = true;
checks[type].correct = result;
console.error('check', checks);
var missing = false;
forEach(checks, function(type) {
@ -88,6 +89,7 @@ else if (cluster.isMaster) {
});
if (missing === false) {
console.error('end client');
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 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'])
.update(rfc4231[i]['data'])
.digest('hex');
if (rfc4231[i]['truncate']) {
result = result.substr(0, 32); // first 128 bits == 32 hex chars
strRes = strRes.substr(0, 32);
}
assert.equal(rfc4231[i]['hmac'][hash],
result,
'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 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(a1, 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca' +
'\u00bd\u008c', 'Test MD5 as binary');
@ -392,6 +409,10 @@ assert.deepEqual(a4,
new Buffer('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'),
'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
var h1 = crypto.createHash('sha1').update('Test123').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')
.update('Test123')
.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')
.update('Test')
.update('123')
@ -427,13 +453,25 @@ assert.strictEqual(verified, true, 'sign and verify (base 64)');
var s2 = crypto.createSign('RSA-SHA256')
.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')
.update('Test')
.update('123')
.verify(certPem, s2); // binary
.verify(certPem, s2, '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')
.update('Test123')
.sign(keyPem, 'buffer');
@ -443,6 +481,13 @@ var verified = crypto.createVerify('RSA-SHA1')
.verify(certPem, s3);
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) {
// Test encryption and decryption
@ -460,6 +505,20 @@ function testCipher1(key) {
txt += decipher.final('utf8');
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');
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 dom = domain.create();
req.resume();
dom.add(req);
dom.add(res);
dom.on('error', function(er) {
serverCaught++;
console.log('server error', er);
console.log('horray! got a server error', er);
// try to send a 500. If that fails, oh well.
res.writeHead(500, {'content-type':'text/plain'});
res.end(er.stack || er.message || 'Unknown error');
@ -81,12 +82,7 @@ function next() {
dom.on('error', function(er) {
clientCaught++;
console.log('client error', er);
// kill everything.
dom.dispose();
});
dom.on('dispose', function() {
disposeEmit += 1;
req.socket.destroy();
});
var req = http.get({ host: 'localhost', port: common.PORT, path: p });
@ -106,6 +102,7 @@ function next() {
d += c;
});
res.on('end', function() {
console.error('trying to parse json', d);
d = JSON.parse(d);
console.log('json!', d);
});
@ -116,6 +113,5 @@ function next() {
process.on('exit', function() {
assert.equal(serverCaught, 2);
assert.equal(clientCaught, 2);
assert.equal(disposeEmit, 2);
console.log('ok');
});

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

@ -22,46 +22,50 @@
var common = require('../common');
var assert = require('assert');
var path = require('path'),
fs = require('fs'),
fn = path.join(common.tmpDir, 'write.txt'),
file = fs.createWriteStream(fn),
var path = require('path');
var fs = require('fs');
var fn = path.join(common.tmpDir, 'write.txt');
var file = fs.createWriteStream(fn, {
lowWaterMark: 0,
highWaterMark: 10
});
EXPECTED = '012345678910',
var EXPECTED = '012345678910';
callbacks = {
var callbacks = {
open: -1,
drain: -2,
close: -1,
endCb: -1
close: -1
};
file
.on('open', function(fd) {
console.error('open!');
callbacks.open++;
assert.equal('number', typeof fd);
})
.on('error', function(err) {
throw err;
console.error('error!', err.stack);
})
.on('drain', function() {
console.error('drain!', callbacks.drain);
callbacks.drain++;
if (callbacks.drain == -1) {
assert.equal(EXPECTED, fs.readFileSync(fn));
assert.equal(EXPECTED, fs.readFileSync(fn, 'utf8'));
file.write(EXPECTED);
} else if (callbacks.drain == 0) {
assert.equal(EXPECTED + EXPECTED, fs.readFileSync(fn));
file.end(function(err) {
assert.ok(!err);
callbacks.endCb++;
});
assert.equal(EXPECTED + EXPECTED, fs.readFileSync(fn, 'utf8'));
file.end();
}
})
.on('close', function() {
console.error('close!');
assert.strictEqual(file.bytesWritten, EXPECTED.length * 2);
callbacks.close++;
assert.throws(function() {
console.error('write after end should not be allowed');
file.write('should not work anymore');
});
@ -70,7 +74,7 @@ file
for (var i = 0; i < 11; i++) {
(function(i) {
assert.strictEqual(false, file.write(i));
file.write('' + i);
})(i);
}
@ -78,4 +82,5 @@ process.on('exit', function() {
for (var k in callbacks) {
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 assert = require('assert');
var path = require('path'),
fs = require('fs'),
util = require('util');
var path = require('path');
var fs = require('fs');
var util = require('util');
var filepath = path.join(common.tmpDir, 'write.txt'),
file;
var filepath = path.join(common.tmpDir, 'write.txt');
var file;
var EXPECTED = '012345678910';
var cb_expected = 'write open drain write drain close error ',
cb_occurred = '';
var cb_expected = 'write open drain write drain close error ';
var cb_occurred = '';
var countDrains = 0;
@ -47,6 +47,8 @@ process.on('exit', function() {
assert.strictEqual(cb_occurred, cb_expected,
'events missing or out of order: "' +
cb_occurred + '" !== "' + cb_expected + '"');
} else {
console.log('ok');
}
});
@ -59,22 +61,30 @@ function 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) {
console.error('open');
cb_occurred += 'open ';
assert.equal(typeof fd, 'number');
});
file.on('drain', function() {
console.error('drain');
cb_occurred += 'drain ';
++countDrains;
if (countDrains === 1) {
assert.equal(fs.readFileSync(filepath), EXPECTED);
file.write(EXPECTED);
console.error('drain=1, write again');
assert.equal(fs.readFileSync(filepath, 'utf8'), EXPECTED);
console.error('ondrain write ret=%j', file.write(EXPECTED));
cb_occurred += 'write ';
} 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();
}
});
@ -88,11 +98,15 @@ file.on('close', function() {
file.on('error', function(err) {
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++) {
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 ';

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

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

@ -23,28 +23,42 @@ var common = require('../common');
var assert = require('assert');
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');
stream.on('data', function(buf) {
var fd = stream.fd;
stream.on('error', common.mustCall(function errorHandler(err_) {
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() {
var cb = arguments[arguments.length - 1];
process.nextTick(function() {
cb(err);
});
// and should not be called again!
fs.read = function() {
throw new Error('BOOM!');
};
};
};
fs.close = common.mustCall(function(fd_, cb) {
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', function(buf) {
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;
file.pause();
assert.ok(file.paused);
setTimeout(function() {
paused = false;
file.resume();
assert.ok(!file.paused);
}, 10);
});
@ -77,7 +75,6 @@ file.on('end', function(chunk) {
file.on('close', function() {
callbacks.close++;
assert.ok(!file.readable);
//assert.equal(fs.readFileSync(fn), fileContent);
});
@ -104,6 +101,7 @@ process.on('exit', function() {
assert.equal(2, callbacks.close);
assert.equal(30000, file.length);
assert.equal(10000, file3.length);
console.error('ok');
});
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 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');
assert.equal(content, 'a\n');
writeEndOk = true;

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

@ -23,30 +23,50 @@ var common = require('../common');
var assert = require('assert');
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');
stream.write(new Buffer(256), function() {
var fd = stream.fd;
var write = fs.write;
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() {
var cb = arguments[arguments.length - 1];
process.nextTick(function() {
cb(err);
});
};
fs.close = common.mustCall(function(fd_, cb) {
console.error('fs.close', fd_, stream.fd);
assert.equal(fd_, stream.fd);
process.nextTick(cb);
});
fs.close = function(fd_, cb) {
assert.equal(fd_, fd);
process.nextTick(cb);
};
stream.on('error', common.mustCall(function(err_) {
console.error('error handler');
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_) {
assert.equal(stream.fd, null);
stream.write(new Buffer(256), function() {
console.error('first cb');
stream.write(new Buffer(256), common.mustCall(function(err_) {
console.error('second cb');
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) {
if (current + 1 === test.responses.length) this.close();
var ctx = test.responses[current];
console.error('< SERVER SENDING RESPONSE', ctx);
res.writeHead(200, ctx.headers);
ctx.chunks.slice(0, -1).forEach(function(chunk) { res.write(chunk) });
res.end(ctx.chunks[ctx.chunks.length - 1]);
@ -126,16 +127,19 @@ function check(tests) {
function connected() {
var ctx = test.requests[current];
console.error(' > CLIENT SENDING REQUEST', ctx);
conn.setEncoding('utf8');
conn.write(ctx.data);
function onclose() {
console.error(' > CLIENT CLOSE');
if (!ctx.expectClose) throw new Error('unexpected close');
client();
}
conn.on('close', onclose);
function ondata(s) {
console.error(' > CLIENT ONDATA %j %j', s.length, s.toString());
current++;
if (ctx.expectClose) return;
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) {
console.log('Read ' + chunk.length + ' bytes');
console.log(chunk.toString());
console.log(' chunk=%j', chunk.toString());
});
res.on('end', function() {
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:
res.on('close', function() {
console.log('Response aborted');

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

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

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

@ -61,6 +61,7 @@ function request(i) {
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 server = http.createServer(function(req, res) {
req.resume();
req.once('end', function() {
res.writeHead(200);
res.end();
@ -50,9 +51,9 @@ server.listen(common.PIPE, function() {
function sched(cb, ticks) {
function fn() {
if (--ticks)
process.nextTick(fn);
setImmediate(fn);
else
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.equal(socket.listeners('connect').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('close').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();
process.exit();
});
res.resume();
});
req.end();
});

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

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

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

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

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

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

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

@ -57,13 +57,14 @@ function testHttp() {
var counter = 0;
function cb() {
function cb(res) {
counter--;
console.log('back from http request. counter = ' + counter);
if (counter === 0) {
httpServer.close();
testHttps();
}
res.resume();
}
httpServer.listen(common.PORT, function(er) {
@ -124,13 +125,14 @@ function testHttps() {
var counter = 0;
function cb() {
function cb(res) {
counter--;
console.log('back from https request. counter = ' + counter);
if (counter === 0) {
httpsServer.close();
console.log('ok');
}
res.resume();
}
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,
port: common.PORT,
agent: agent
}, function() {
}, function(res) {
assert.equal(1, agent.sockets['localhost:' + common.PORT].length);
res.resume();
});
request.on('socket', function(s) {
s.on('connect', function() {
@ -60,8 +61,9 @@ server.listen(common.PORT, function() {
headers: headers,
port: common.PORT,
agent: agent
}, function() {
}, function(res) {
assert.equal(1, agent.sockets['localhost:' + common.PORT].length);
res.resume();
});
request.on('socket', function(s) {
s.on('connect', function() {
@ -80,6 +82,7 @@ server.listen(common.PORT, function() {
assert.equal(1, agent.sockets['localhost:' + common.PORT].length);
server.close();
});
response.resume();
});
request.on('socket', function(s) {
s.on('connect', function() {

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

@ -42,6 +42,7 @@ server.listen(common.PORT, function() {
}, function(response) {
assert.equal(agent.sockets[name].length, 1);
assert.equal(agent.requests[name].length, 2);
response.resume();
});
http.get({
@ -49,6 +50,7 @@ server.listen(common.PORT, function() {
}, function(response) {
assert.equal(agent.sockets[name].length, 1);
assert.equal(agent.requests[name].length, 1);
response.resume();
});
http.get({
@ -59,6 +61,7 @@ server.listen(common.PORT, function() {
assert(!agent.requests.hasOwnProperty(name));
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();
}
});
res.resume();
}).on('error', function(e) {
console.log(e.message);
process.exit(1);

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

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

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

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

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

@ -47,6 +47,7 @@ server.listen(common.PORT, function() {
method: 'POST'
}, function(res) {
console.log(res.statusCode);
res.resume();
}).on('error', function(e) {
console.log(e.message);
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() {
// 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
test += 1;
// do it again to test .end(Buffer);
http.get({port: common.PORT}, function() {
http.get({port: common.PORT}, function(res) {
res.resume();
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');
testServer.close();
});
res.resume();
});
});

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

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

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

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

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

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

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

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

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

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

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

@ -170,6 +170,7 @@ function makeReq(path, port, error, host, ca) {
server2.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 server = net.createServer(function(s) {
console.error('SERVER: got connection');
s.end();
});
server.listen(common.PORT, function() {
var c = net.createConnection(common.PORT);
c.on('close', function() {
console.error('connection closed');
assert.strictEqual(c._handle, null);
closed = true;
assert.doesNotThrow(function() {

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

@ -41,12 +41,15 @@ for (var i = 255; i >= 0; i--) {
// safe constructor
var echoServer = net.Server(function(connection) {
// connection._readableState.lowWaterMark = 0;
console.error('SERVER got connection');
connection.setEncoding('binary');
connection.on('data', function(chunk) {
common.error('recved: ' + JSON.stringify(chunk));
common.error('SERVER recved: ' + JSON.stringify(chunk));
connection.write(chunk, 'binary');
});
connection.on('end', function() {
console.error('SERVER ending');
connection.end();
});
});
@ -55,29 +58,44 @@ echoServer.listen(common.PORT);
var recv = '';
echoServer.on('listening', function() {
console.error('SERVER listening');
var j = 0;
var c = net.createConnection(common.PORT);
var c = net.createConnection({
port: common.PORT
});
// c._readableState.lowWaterMark = 0;
c.setEncoding('binary');
c.on('data', function(chunk) {
if (j < 256) {
common.error('write ' + j);
console.error('CLIENT data %j', chunk);
var n = j + chunk.length;
while (j < n && j < 256) {
common.error('CLIENT write ' + j);
c.write(String.fromCharCode(j), 'binary');
j++;
} else {
}
if (j === 256) {
console.error('CLIENT ending');
c.end();
}
recv += chunk;
});
c.on('connect', function() {
console.error('CLIENT connected, writing');
c.write(binaryString, 'binary');
});
c.on('close', function() {
console.error('CLIENT closed');
console.dir(recv);
echoServer.close();
});
c.on('finish', function() {
console.error('CLIENT finished');
});
});
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) {
console.log('tcp server connection');
// trigger old mode.
s.resume();
s.on('end', function() {
bytesRead += s.bytesRead;
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);
socket.on('connect', function() {
count++;
console.log('tcp client connection #' + count);
console.error('CLIENT connect #%d', count);
socket.write('foo', function() {
console.error('CLIENT: write cb');
socket.end('bar');
});
});
socket.on('end', function() {
socket.on('finish', function() {
bytesWritten += socket.bytesWritten;
console.log('tcp client disconnect #' + count);
console.error('CLIENT end event #%d', count);
});
socket.on('close', function() {
console.error('CLIENT close event #%d', count);
console.log('Bytes read: ' + bytesRead);
console.log('Bytes written: ' + bytesWritten);
if (count < 2) {
console.error('RECONNECTING');
socket.connect(tcpPort);
} else {
tcp.close();

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

@ -28,6 +28,8 @@ var timeoutCount = 0;
var server = net.createServer(function(stream) {
stream.setTimeout(100);
stream.resume();
stream.on('timeout', function() {
console.log('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() {
console.error('SERVER: end', buf.toString());
assert.equal(buf, "L'État, c'est moi");
console.log('tcp socket disconnect');
s.end();
@ -50,7 +51,7 @@ var tcp = net.Server(function(s) {
});
tcp.listen(common.PORT, function() {
var socket = net.Stream();
var socket = net.Stream({ highWaterMark: 0 });
console.log('Connecting to socket ');
@ -77,6 +78,7 @@ tcp.listen(common.PORT, function() {
{}
].forEach(function(v) {
function f() {
console.error('write', v);
socket.write(v);
}
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
// the connect queue. Make sure that it's not added to `bytesWritten` again
// when the actual write happens.
var r = socket.write(a, function() {
var r = socket.write(a, function(er) {
console.error('write cb');
dataWritten = true;
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('socket.bytesWritten', socket.bytesWritten);
console.error('write returned', r);
assert.equal(socket.bytesWritten, Buffer(a).length);
assert.equal(false, r);

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

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

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

@ -60,6 +60,8 @@ function pingPongTest(port, host) {
});
socket.on('end', function() {
console.error(socket);
assert.equal(true, socket.allowHalfOpen);
assert.equal(true, socket.writable); // because allowHalfOpen
assert.equal(false, socket.readable);
socket.end();
@ -129,10 +131,11 @@ function pingPongTest(port, host) {
}
/* All are run at once, so run on different ports */
console.log(common.PIPE);
pingPongTest(common.PIPE);
pingPongTest(20988);
pingPongTest(20989, 'localhost');
pingPongTest(20997, '::1');
pingPongTest(common.PORT);
pingPongTest(common.PORT + 1, 'localhost');
pingPongTest(common.PORT + 2, '::1');
process.on('exit', function() {
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 server = net.createServer(function(socket) {
console.error('SERVER: got socket connection');
socket.resume();
socket.on('connect', function() {
console.error('SERVER connect, writing');
socket.write('hello\r\n');
});
socket.on('end', function() {
console.error('SERVER socket end, calling end()');
socket.end();
});
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);
});
});
server.listen(common.PORT, function() {
console.log('listening');
console.log('SERVER listening');
var client = net.createConnection(common.PORT);
client.setEncoding('UTF8');
client.on('connect', function() {
console.log('client connected.');
console.error('CLIENT connected', client._writableState);
});
client.on('data', function(chunk) {
client_recv_count += 1;
console.log('client_recv_count ' + client_recv_count);
assert.equal('hello\r\n', chunk);
console.error('CLIENT: calling end', client._writableState);
client.end();
});
client.on('end', function() {
console.error('CLIENT end');
});
client.on('close', function(had_error) {
console.log('disconnect');
console.log('CLIENT disconnect');
assert.equal(false, had_error);
if (disconnect_count++ < N)
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() {
if (++conns_closed == 2) server.close();
});
socket.resume();
});
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) {
socket.resume();
socket.on('error', function(error) {
console.error('got error, closing server', error);
server.close();
gotError = true;
});
setTimeout(function() {
console.error('about to try to write');
socket.write('test', function(e) {
gotWriteCB = true;
});

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

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

1
test/simple/test-pipe.js

@ -125,6 +125,7 @@ function startClient() {
});
req.write(buffer);
req.end();
console.error('ended request', req);
}
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) {
server.close();
}
res.resume();
});
assert.equal(req.agent, agent);

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

@ -48,8 +48,9 @@ function test1(){
putIn.write = function (data) {
gotWrite = true;
if (data.length) {
// 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
assert.equal(global.fs, require('fs'));
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) {
console.log('got connection');
connectCount++;
s.resume();
s.on('end', function() {
console.log('got eof');
endCount++;

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

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

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

@ -50,13 +50,6 @@ unzips.forEach(function (uz, i) {
uz.on('error', function(er) {
console.error('Error event', er);
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) {

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 gzip = zlib.createGzip();
var gunz = zlib.createGunzip();
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;
out.on('data', function(c) {
didSomething = true;

Loading…
Cancel
Save