Skip to content

Commit 2106ef0

Browse files
committed
net: Provide better error when writing after FIN
The stock writable stream "write after end" message is overly vague, if you have clearly not called end() yourself yet. When we receive a FIN from the other side, and call destroySoon() as a result, then generate an EPIPE error (which is what would happen if you did actually write to the socket), with a message explaining what actually happened.
1 parent 47e1150 commit 2106ef0

4 files changed

Lines changed: 179 additions & 4 deletions

File tree

lib/_stream_writable.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,15 @@ Writable.prototype.write = function(chunk, encoding, cb) {
126126
}
127127

128128
if (state.ended) {
129+
var self = this;
129130
var er = new Error('write after end');
130-
if (typeof cb === 'function')
131-
cb(er);
132-
this.emit('error', er);
131+
// TODO: defer error events consistently everywhere, not just the cb
132+
self.emit('error', er);
133+
if (typeof cb === 'function') {
134+
process.nextTick(function() {
135+
cb(er);
136+
});
137+
}
133138
return;
134139
}
135140

lib/net.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,31 @@ function onSocketEnd() {
240240
this.read(0);
241241
}
242242

243-
if (!this.allowHalfOpen)
243+
if (!this.allowHalfOpen) {
244+
this.write = writeAfterFIN;
244245
this.destroySoon();
246+
}
247+
}
248+
249+
// Provide a better error message when we call end() as a result
250+
// of the other side sending a FIN. The standard 'write after end'
251+
// is overly vague, and makes it seem like the user's code is to blame.
252+
function writeAfterFIN(chunk, encoding, cb) {
253+
if (typeof encoding === 'function') {
254+
cb = encoding;
255+
encoding = null;
256+
}
257+
258+
var er = new Error('This socket has been closed by the other party');
259+
er.code = 'EPIPE';
260+
var self = this;
261+
// TODO: defer error events consistently everywhere, not just the cb
262+
self.emit('error', er);
263+
if (typeof cb === 'function') {
264+
process.nextTick(function() {
265+
cb(er);
266+
});
267+
}
245268
}
246269

247270
exports.Socket = Socket;
@@ -708,6 +731,9 @@ function connect(self, address, port, addressType, localAddress) {
708731

709732

710733
Socket.prototype.connect = function(options, cb) {
734+
if (this.write !== Socket.prototype.write)
735+
this.write = Socket.prototype.write;
736+
711737
if (typeof options !== 'object') {
712738
// Old API:
713739
// connect(port, [host], [cb])
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
var common = require('../common');
23+
var assert = require('assert');
24+
25+
// This is similar to simple/test-socket-write-after-fin, except that
26+
// we don't set allowHalfOpen. Then we write after the client has sent
27+
// a FIN, and this is an error. However, the standard "write after end"
28+
// message is too vague, and doesn't actually tell you what happens.
29+
30+
var net = require('net');
31+
var serverData = '';
32+
var gotServerEnd = false;
33+
var clientData = '';
34+
var gotClientEnd = false;
35+
var gotServerError = false;
36+
37+
var server = net.createServer(function(sock) {
38+
sock.setEncoding('utf8');
39+
sock.on('error', function(er) {
40+
console.error(er.code + ': ' + er.message);
41+
gotServerError = er;
42+
});
43+
44+
sock.on('data', function(c) {
45+
serverData += c;
46+
});
47+
sock.on('end', function() {
48+
gotServerEnd = true
49+
sock.write(serverData);
50+
sock.end();
51+
});
52+
server.close();
53+
});
54+
server.listen(common.PORT);
55+
56+
var sock = net.connect(common.PORT);
57+
sock.setEncoding('utf8');
58+
sock.on('data', function(c) {
59+
clientData += c;
60+
});
61+
62+
sock.on('end', function() {
63+
gotClientEnd = true;
64+
});
65+
66+
process.on('exit', function() {
67+
assert.equal(clientData, '');
68+
assert.equal(serverData, 'hello1hello2hello3\nTHUNDERMUSCLE!');
69+
assert(gotClientEnd);
70+
assert(gotServerEnd);
71+
assert(gotServerError);
72+
assert.equal(gotServerError.code, 'EPIPE');
73+
assert.notEqual(gotServerError.message, 'write after end');
74+
console.log('ok');
75+
});
76+
77+
sock.write('hello1');
78+
sock.write('hello2');
79+
sock.write('hello3\n');
80+
sock.end('THUNDERMUSCLE!');
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
var common = require('../common');
23+
var assert = require('assert');
24+
var net = require('net');
25+
var serverData = '';
26+
var gotServerEnd = false;
27+
var clientData = '';
28+
var gotClientEnd = false;
29+
30+
var server = net.createServer({ allowHalfOpen: true }, function(sock) {
31+
sock.setEncoding('utf8');
32+
sock.on('data', function(c) {
33+
serverData += c;
34+
});
35+
sock.on('end', function() {
36+
gotServerEnd = true
37+
sock.end(serverData);
38+
server.close();
39+
});
40+
});
41+
server.listen(common.PORT);
42+
43+
var sock = net.connect(common.PORT);
44+
sock.setEncoding('utf8');
45+
sock.on('data', function(c) {
46+
clientData += c;
47+
});
48+
49+
sock.on('end', function() {
50+
gotClientEnd = true;
51+
});
52+
53+
process.on('exit', function() {
54+
assert.equal(serverData, clientData);
55+
assert.equal(serverData, 'hello1hello2hello3\nTHUNDERMUSCLE!');
56+
assert(gotClientEnd);
57+
assert(gotServerEnd);
58+
console.log('ok');
59+
});
60+
61+
sock.write('hello1');
62+
sock.write('hello2');
63+
sock.write('hello3\n');
64+
sock.end('THUNDERMUSCLE!');

0 commit comments

Comments
 (0)