@@ -10,10 +10,6 @@ var fs = require('fs')
1010// Current version
1111var version = [ 0 , 6 , 9 ] ;
1212
13- // In-memory file store
14- var store = { } ;
15- var indexStore = { } ;
16-
1713Server = 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+
259296Server . 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) {
287324exports . Server = Server ;
288325exports . version = version ;
289326exports . mime = mime ;
290- exports . store = store ;
291- exports . indexStore = indexStore ;
292-
293327
294328
295329
0 commit comments