Skip to content
Permalink
Browse files

http,stream: add writableEnded

This is work towards resolving the response.finished confusion and
future deprecation.

Note that implementation-wise, streams have both an ending and ended
state. However, in this case (in order to avoid confusion in user space)
writableEnded is equal to writable.ending. The ending vs ended situation
is internal state required for internal stream logic.

PR-URL: #28934
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information...
ronag authored and targos committed Aug 2, 2019
1 parent bb19d82 commit f9b61d2bc7d730e2d5767d0056f9a0801173942c
@@ -764,6 +764,27 @@ req.once('response', (res) => {
});
```

### request.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`request.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`request.writableFinished`][] instead.

### request.writableFinished
<!-- YAML
added: v12.7.0
-->

* {boolean}

Is `true` if all data has been flushed to the underlying system, immediately
before the [`'finish'`][] event is emitted.

### request.write(chunk[, encoding][, callback])
<!-- YAML
added: v0.1.29
@@ -1434,6 +1455,17 @@ response.statusMessage = 'Not found';
After response header was sent to the client, this property indicates the
status message which was sent out.

### response.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`response.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`response.writableFinished`][] instead.

### response.writableFinished
<!-- YAML
added: v12.7.0
@@ -2221,11 +2253,13 @@ not abort the request or do anything besides add a `'timeout'` event.
[`request.setTimeout()`]: #http_request_settimeout_timeout_callback
[`request.socket.getPeerCertificate()`]: tls.html#tls_tlssocket_getpeercertificate_detailed
[`request.socket`]: #http_request_socket
[`request.writableFinished`]: #http_request_writablefinished
[`request.write(data, encoding)`]: #http_request_write_chunk_encoding_callback
[`response.end()`]: #http_response_end_data_encoding_callback
[`response.getHeader()`]: #http_response_getheader_name
[`response.setHeader()`]: #http_response_setheader_name_value
[`response.socket`]: #http_response_socket
[`response.writableFinished`]: #http_response_writablefinished
[`response.write()`]: #http_response_write_chunk_encoding_callback
[`response.write(data, encoding)`]: #http_response_write_chunk_encoding_callback
[`response.writeContinue()`]: #http_response_writecontinue
@@ -3269,6 +3269,17 @@ added: v8.4.0

The [`Http2Stream`][] object backing the response.

#### response.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`response.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`writable.writableFinished`][] instead.

#### response.write(chunk[, encoding][, callback])
<!-- YAML
added: v8.4.0
@@ -3509,3 +3520,4 @@ following additional properties:
[`tls.connect()`]: tls.html#tls_tls_connect_options_callback
[`tls.createServer()`]: tls.html#tls_tls_createserver_options_secureconnectionlistener
[error code]: #error_codes
[`writable.writableFinished`]: stream.html#stream_writable_writablefinished
@@ -491,6 +491,17 @@ added: v11.4.0

Is `true` if it is safe to call [`writable.write()`][stream-write].

##### writable.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`writable.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`writable.writableFinished`][] instead.

##### writable.writableFinished
<!-- YAML
added: v12.6.0
@@ -2704,7 +2715,9 @@ contain multi-byte characters.
[`stream.unpipe()`]: #stream_readable_unpipe_destination
[`stream.wrap()`]: #stream_readable_wrap_stream
[`writable.cork()`]: #stream_writable_cork
[`writable.end()`]: #stream_writable_end_chunk_encoding_callback
[`writable.uncork()`]: #stream_writable_uncork
[`writable.writableFinished`]: #stream_writable_writablefinished
[`zlib.createDeflate()`]: zlib.html#zlib_zlib_createdeflate_options
[API for Stream Consumers]: #stream_api_for_stream_consumers
[API for Stream Implementers]: #stream_api_for_stream_implementers
@@ -575,6 +575,10 @@ Object.defineProperty(OutgoingMessage.prototype, 'headersSent', {
get: function() { return !!this._header; }
});

Object.defineProperty(OutgoingMessage.prototype, 'writableEnded', {
get: function() { return this.finished; }
});


const crlf_buf = Buffer.from('\r\n');
OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
@@ -74,7 +74,7 @@ Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', {
// userland will fail
enumerable: false,
get() {
return this._writableState.highWaterMark;
return this._writableState && this._writableState.highWaterMark;
}
});

@@ -94,7 +94,7 @@ Object.defineProperty(Duplex.prototype, 'writableLength', {
// userland will fail
enumerable: false,
get() {
return this._writableState.length;
return this._writableState && this._writableState.length;
}
});

@@ -104,7 +104,17 @@ Object.defineProperty(Duplex.prototype, 'writableFinished', {
// userland will fail
enumerable: false,
get() {
return this._writableState.finished;
return this._writableState ? this._writableState.finished : false;
}
});

Object.defineProperty(Duplex.prototype, 'writableEnded', {
// Making it explicit this property is not enumerable
// because otherwise some prototype manipulation in
// userland will fail
enumerable: false,
get() {
return this._writableState ? this._writableState.ending : false;
}
});

@@ -352,13 +352,23 @@ function decodeChunk(state, chunk, encoding) {
return chunk;
}

Object.defineProperty(Writable.prototype, 'writableEnded', {
// Making it explicit this property is not enumerable
// because otherwise some prototype manipulation in
// userland will fail
enumerable: false,
get: function() {
return this._writableState ? this._writableState.ending : false;
}
});

Object.defineProperty(Writable.prototype, 'writableHighWaterMark', {
// Making it explicit this property is not enumerable
// because otherwise some prototype manipulation in
// userland will fail
enumerable: false,
get: function() {
return this._writableState.highWaterMark;
return this._writableState && this._writableState.highWaterMark;
}
});

@@ -713,7 +723,7 @@ Object.defineProperty(Writable.prototype, 'writableFinished', {
// userland will fail
enumerable: false,
get() {
return this._writableState.finished;
return this._writableState ? this._writableState.finished : false;
}
});

@@ -454,6 +454,11 @@ class Http2ServerResponse extends Stream {
return this.headersSent;
}

get writableEnded() {
const state = this[kState];
return state.ending;
}

get finished() {
const stream = this[kStream];
return stream.destroyed ||
@@ -9,12 +9,14 @@ const http = require('http');
const server = http.createServer(common.mustCall(function(req, res) {
assert.strictEqual(res.writable, true);
assert.strictEqual(res.finished, false);
assert.strictEqual(res.writableEnded, false);
res.end();

// res.writable is set to false after it has finished sending
// Ref: https://github.com/nodejs/node/issues/15029
assert.strictEqual(res.writable, true);
assert.strictEqual(res.finished, true);
assert.strictEqual(res.writableEnded, true);

server.close();
}));
@@ -150,8 +150,10 @@ const {
// for http1 compatibility
const server = createServer(mustCall((request, response) => {
strictEqual(response.finished, true);
strictEqual(response.writableEnded, false);
response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
response.end('data', mustCall());
strictEqual(response.writableEnded, true);
}));
server.listen(0, mustCall(() => {
const { port } = server.address();
@@ -25,8 +25,10 @@ server.listen(0, common.mustCall(() => {
}));
}));
assert.strictEqual(response.finished, false);
assert.strictEqual(response.writableEnded, false);
response.end();
assert.strictEqual(response.finished, true);
assert.strictEqual(response.writableEnded, true);
}));

const url = `http://localhost:${port}`;
@@ -9,13 +9,17 @@ const writable = new stream.Writable();

writable._write = (chunk, encoding, cb) => {
assert.strictEqual(writable._writableState.ended, false);
assert.strictEqual(writable.writableEnded, false);
cb();
};

assert.strictEqual(writable._writableState.ended, false);
assert.strictEqual(writable.writableEnded, false);

writable.end('testing ended state', common.mustCall(() => {
assert.strictEqual(writable._writableState.ended, true);
assert.strictEqual(writable.writableEnded, true);
}));

assert.strictEqual(writable._writableState.ended, true);
assert.strictEqual(writable.writableEnded, true);

0 comments on commit f9b61d2

Please sign in to comment.
You can’t perform that action at this time.