Skip to content

Commit bff5299

Browse files
committed
Merge pull request cloudhead#100 from thbaja/master
Remove of in-memory cache, and check for gzipped files. Remerge of PR cloudhead#36
2 parents 544f7ad + 131b667 commit bff5299

2 files changed

Lines changed: 100 additions & 48 deletions

File tree

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,24 @@ example: `{ 'X-Hello': 'World!' }`
152152

153153
> defaults to `{}`
154154
155+
#### `gzip` #
156+
157+
Enable support for sending compressed responses. This will enable a check for a
158+
file with the same name plus '.gz' in the same folder. If the compressed file is
159+
found and the client has indicated support for gzip file transfer, the contents
160+
of the .gz file will be sent in place of the uncompressed file along with a
161+
Content-Encoding: gzip header to inform the client the data has been compressed.
162+
163+
example: `{ gzip: true }`
164+
example: `{ gzip: /^\/text/ }`
165+
166+
Passing `true` will enable this check for all files.
167+
Passing a RegExp instance will only enable this check if the content-type of the
168+
respond would match that RegExp using its test() method.
169+
170+
> Defaults to `false`
171+
172+
155173
Command Line Interface
156174
----------------------
157175

lib/node-static.js

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ var fs = require('fs')
1010
// Current version
1111
var version = [0, 6, 9];
1212

13-
// In-memory file store
14-
var store = {};
15-
var indexStore = {};
16-
1713
Server = function (root, options) {
1814
if (root && (typeof(root) === 'object')) { options = root; root = null }
1915

@@ -65,17 +61,12 @@ Server.prototype.serveDir = function (pathname, req, res, finish) {
6561
that.respond(null, status, headers, [htmlIndex], stat, req, res, finish);
6662
}
6763
} else {
68-
if (pathname in indexStore) {
69-
streamFiles(indexStore[pathname].files);
70-
} else {
71-
// Stream a directory of files as a single file.
72-
fs.readFile(path.join(pathname, 'index.json'), function (e, contents) {
73-
if (e) { return finish(404, {}) }
74-
var index = JSON.parse(contents);
75-
indexStore[pathname] = index;
76-
streamFiles(index.files);
77-
});
78-
}
64+
// Stream a directory of files as a single file.
65+
fs.readFile(path.join(pathname, 'index.json'), function (e, contents) {
66+
if (e) { return finish(404, {}) }
67+
var index = JSON.parse(contents);
68+
streamFiles(index.files);
69+
});
7970
}
8071
});
8172
function streamFiles(files) {
@@ -201,61 +192,107 @@ Server.prototype.serve = function (req, res, callback) {
201192
if (! callback) { return promise }
202193
};
203194

204-
Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) {
195+
/* Check if we should consider sending a gzip version of the file based on the
196+
* file content type and client's Accept-Encoding header value.
197+
*/
198+
Server.prototype.gzipOk = function(req, contentType) {
199+
var enable = this.options.gzip;
200+
if(enable &&
201+
(typeof enable === 'boolean' ||
202+
(contentType && (enable instanceof RegExp) && enable.test(contentType)))) {
203+
var acceptEncoding = req.headers['accept-encoding'];
204+
return acceptEncoding && acceptEncoding.indexOf("gzip") >= 0;
205+
}
206+
return false;
207+
}
208+
209+
/* Send a gzipped version of the file if the options and the client indicate gzip is enabled and
210+
* we find a .gz file mathing the static resource requested.
211+
*/
212+
Server.prototype.respondGzip = function(pathname, status, contentType, _headers, files, stat, req, res, finish) {
213+
var that = this;
214+
if(files.length == 1 && this.gzipOk(req)) {
215+
var gzFile = files[0] + ".gz";
216+
fs.stat(gzFile, function(e, gzStat) {
217+
if(!e && gzStat.isFile()) {
218+
//console.log('Serving', gzFile, 'to gzip-capable client instead of', files[0], 'new size is', gzStat.size, 'uncompressed size', stat.size);
219+
var vary = _headers['Vary'];
220+
_headers['Vary'] = (vary && vary != 'Accept-Encoding'?vary+', ':'')+'Accept-Encoding';
221+
_headers['Content-Encoding'] = 'gzip';
222+
stat.size = gzStat.size;
223+
files = [gzFile];
224+
} else {
225+
//console.log('gzip file not found or error finding it', gzFile, String(e), stat.isFile());
226+
}
227+
that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
228+
});
229+
} else {
230+
// Client doesn't want gzip or we're sending multiple files
231+
that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
232+
}
233+
}
234+
235+
Server.prototype.respondNoGzip = function (pathname, status, contentType, _headers, files, stat, req, res, finish) {
205236
var mtime = Date.parse(stat.mtime),
206237
key = pathname || files[0],
207238
headers = {},
208239
clientETag = req.headers['if-none-match'],
209240
clientMTime = Date.parse(req.headers['if-modified-since']);
210241

242+
211243
// Copy default headers
212244
for (var k in this.options.headers) { headers[k] = this.options.headers[k] }
213245
// Copy custom headers
214246
for (var k in _headers) { headers[k] = _headers[k] }
215247

216-
headers['etag'] = JSON.stringify([stat.ino, stat.size, mtime].join('-'));
217-
headers['date'] = new(Date)().toUTCString();
218-
headers['last-modified'] = new(Date)(stat.mtime).toUTCString();
248+
headers['Etag'] = JSON.stringify([stat.ino, stat.size, mtime].join('-'));
249+
headers['Date'] = new(Date)().toUTCString();
250+
headers['Last-Modified'] = new(Date)(stat.mtime).toUTCString();
251+
headers['Content-Type'] = contentType;
252+
headers['Content-Length'] = stat.size;
253+
254+
for (var k in _headers) { headers[k] = _headers[k] }
219255

220256
// Conditional GET
221257
// If the "If-Modified-Since" or "If-None-Match" headers
222258
// match the conditions, send a 304 Not Modified.
223259
if ((clientMTime || clientETag) &&
224-
(!clientETag || clientETag === headers['etag']) &&
260+
(!clientETag || clientETag === headers['Etag']) &&
225261
(!clientMTime || clientMTime >= mtime)) {
262+
// 304 response should not contain entity headers
263+
['Content-Encoding',
264+
'Content-Language',
265+
'Content-Length',
266+
'Content-Location',
267+
'Content-MD5',
268+
'Content-Range',
269+
'Content-Type',
270+
'Expires',
271+
'Last-Modified'].forEach(function(entityHeader) {
272+
delete headers[entityHeader];
273+
});
226274
finish(304, headers);
227275
} else {
228-
headers['content-length'] = stat.size;
229-
headers['content-type'] = mime.lookup(files[0]);
230-
'application/octet-stream';
231-
232276
res.writeHead(status, headers);
233277

234-
if (req.method === 'HEAD') {
235-
finish(200, headers);
236-
return;
237-
}
238-
239-
// If the file was cached and it's not older
240-
// than what's on disk, serve the cached version.
241-
if (this.cache && (key in store) &&
242-
store[key].stat.mtime >= stat.mtime) {
243-
res.end(store[key].buffer);
278+
this.stream(pathname, files, new(buffer.Buffer)(stat.size), res, function (e, buffer) {
279+
if (e) { return finish(500, {}) }
244280
finish(status, headers);
245-
} else {
246-
this.stream(pathname, files, new(buffer.Buffer)(stat.size), res, function (e, buffer) {
247-
if (e) { return finish(500, {}) }
248-
store[key] = {
249-
stat: stat,
250-
buffer: buffer,
251-
timestamp: Date.now()
252-
};
253-
finish(status, headers);
254-
});
255-
}
281+
});
256282
}
257283
};
258284

285+
Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) {
286+
var contentType = _headers['Content-Type'] ||
287+
mime.lookup(files[0]) ||
288+
'application/octet-stream';
289+
if(this.options.gzip) {
290+
this.respondGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
291+
} else {
292+
this.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
293+
}
294+
}
295+
259296
Server.prototype.stream = function (pathname, files, buffer, res, callback) {
260297
(function streamFile(files, offset) {
261298
var file = files.shift();
@@ -287,9 +324,6 @@ Server.prototype.stream = function (pathname, files, buffer, res, callback) {
287324
exports.Server = Server;
288325
exports.version = version;
289326
exports.mime = mime;
290-
exports.store = store;
291-
exports.indexStore = indexStore;
292-
293327

294328

295329

0 commit comments

Comments
 (0)