Skip to content
This repository was archived by the owner on Aug 10, 2023. It is now read-only.

Commit 8cb0734

Browse files
koutemicahr
authored andcommitted
(serveStatic plugin) Do not leak file descriptors and always call next() (#43)
1 parent 82c2ed0 commit 8cb0734

2 files changed

Lines changed: 104 additions & 1 deletion

File tree

lib/plugins/static.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ function serveStatic(options) {
4949
var re = new RegExp('^' + escapeRE(p) + '/?.*');
5050

5151
function serveFileFromStats(file, err, stats, isGzip, req, res, next) {
52+
if (req.clientClosed) {
53+
next(false);
54+
return;
55+
}
56+
5257
if (err) {
5358
next(new ResourceNotFoundError(err, '%s', req.path()));
5459
return;
@@ -80,10 +85,14 @@ function serveStatic(options) {
8085
}
8186
res.writeHead(200);
8287
fstream.pipe(res);
83-
fstream.once('end', function () {
88+
fstream.once('close', function () {
8489
next(false);
8590
});
8691
});
92+
93+
res.once('close', function () {
94+
fstream.close();
95+
});
8796
}
8897

8998
function serveNormal(file, req, res, next) {

test/static.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// core requires
44
var fs = require('fs');
55
var path = require('path');
6+
var net = require('net');
67

78
// external requires
89
var assert = require('chai').assert;
@@ -231,4 +232,97 @@ describe('static resource plugin', function () {
231232
function (done) {
232233
testNoAppendPath(done, false, '.tmp', null, true);
233234
});
235+
236+
// To ensure this will always get properly restored (even in case of a test
237+
// failure) we do it here.
238+
var originalCreateReadStream = fs.createReadStream;
239+
afterEach(function () {
240+
fs.createReadStream = originalCreateReadStream;
241+
});
242+
243+
var TMP_PATH = path.join(__dirname, '../', '.tmp');
244+
var RAW_REQUEST =
245+
'GET /index.html HTTP/1.1\r\n' +
246+
'Host: 127.0.0.1:' + PORT + '\r\n' +
247+
'User-Agent: curl/7.48.0\r\n' +
248+
'Accept: */*\r\n' +
249+
'\r\n';
250+
251+
it('static does not leak the file stream and next() is properly called ' +
252+
'when the client disconnects before receiving a reply', function (done) {
253+
var streamWasAlreadyCreated = false;
254+
var streamWasClosed = false;
255+
var socket = new net.Socket();
256+
257+
fs.createReadStream = function () {
258+
// Just in case the code would open more streams in the future.
259+
assert.strictEqual(streamWasAlreadyCreated, false);
260+
streamWasAlreadyCreated = true;
261+
262+
var stream = originalCreateReadStream.apply(this, arguments);
263+
stream.once('close', function () {
264+
streamWasClosed = true;
265+
});
266+
267+
socket.end();
268+
return stream;
269+
};
270+
271+
mkdirp(TMP_PATH, function (err) {
272+
assert.ifError(err);
273+
DIRS_TO_DELETE.push(TMP_PATH);
274+
fs.writeFileSync(path.join(TMP_PATH, 'index.html'), 'Hello world!');
275+
276+
var serve = plugins.serveStatic({
277+
directory: TMP_PATH
278+
});
279+
280+
SERVER.get(/.*/, function (req, res, next) {
281+
serve(req, res, function (nextRoute) {
282+
assert.strictEqual(streamWasClosed, true);
283+
assert.strictEqual(nextRoute, false);
284+
done();
285+
});
286+
});
287+
288+
socket.connect({host: '127.0.0.1', port: PORT}, function () {
289+
socket.write(RAW_REQUEST, 'utf-8', function (err2, data) {
290+
assert.ifError(err2);
291+
});
292+
});
293+
});
294+
});
295+
296+
it('static does not open a file stream and next() is properly called ' +
297+
'when the client disconnects immediately after sending a request',
298+
function (done) {
299+
fs.createReadStream = function () {
300+
assert(false);
301+
};
302+
303+
mkdirp(TMP_PATH, function (err) {
304+
assert.ifError(err);
305+
DIRS_TO_DELETE.push(TMP_PATH);
306+
fs.writeFileSync(path.join(TMP_PATH, 'index.html'), 'Hello world!');
307+
308+
var serve = plugins.serveStatic({
309+
directory: TMP_PATH
310+
});
311+
312+
SERVER.get(/.*/, function (req, res, next) {
313+
serve(req, res, function (nextRoute) {
314+
assert.strictEqual(nextRoute, false);
315+
done();
316+
});
317+
});
318+
319+
var socket = new net.Socket();
320+
socket.connect({host: '127.0.0.1', port: PORT}, function () {
321+
socket.write(RAW_REQUEST, 'utf-8', function (err2, data) {
322+
assert.ifError(err2);
323+
socket.end();
324+
});
325+
});
326+
});
327+
});
234328
});

0 commit comments

Comments
 (0)