Description
- Version: 7.8.0 (also affects 4 & 6)
- Platform: macOS 10.12.4
- Subsystem: http / net
When a server responds with a short payload to a POST
http.request()
with a large payload that has not finished uploading, a node client throws an EPIPE
error on the request object. E.g. with NODE_DEBUG=http
:
HTTP 90220: SOCKET ERROR: write EPIPE Error: write EPIPE
at exports._errnoException (util.js:1034:11)
at WriteWrap.afterWrite [as oncomplete] (net.js:812:14)
This destroys the node socket, and the http client never receives the response
or any of the payload data.
Expected result
The EPIPE
error should be deferred (or possibly just ignored) until the client has had a chance to handle the response and payload.
Serverside workaround
This can be mitigated serverside, by consuming all the uploaded data before sending a response.
Background
I need clients to be able to handle my server responding to POST
requests without consuming the entire payload.
Example
I have created a failing example, which when run will trigger the EPIPE
error.
To visualise that this is indeed a node bug, I added an ignore_epipe
option, which can be set to true
, to ignore EPIPE
errors on the socket. This results in the client behaving as I expected, properly delivering the response
and payload, before emitting an error.
var http = require('http');
var port = 8080;
var ignore_epipe = false;
var upload = function () {
var req = http.request({
port: port,
method: 'POST'
}, function (res) {
console.log('received response code', res.statusCode);
var length = 0
res.setEncoding('utf8');
res.on('data', function (chunk) {
//console.log('BODY: ' + chunk);
length += chunk.length;
});
res.on('end', function () {
console.log('received ' + length + ' bytes');
});
res.on('error', function (err) {
console.log('got response error', err);
});
});
req.end(new Buffer(10*1024*1024));
req.on('error', function (err) {
console.log('got request error', err);
});
if (ignore_epipe) {
req.on('socket', function (socket) {
var destroy = socket._destroy;
socket._destroy = function (exception, cb) {
if (exception && (exception.code === 'EPIPE')) {
return;
}
return destroy.call(this, exception, cb);
};
});
}
};
// create server
var server = http.createServer(function (req, res) {
res.end('done');
});
server.listen(port, function (err) {
if (err) {
return console.log('something bad happened', err)
}
console.log('server is listening on', port);
upload();
});