123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- // file-cache.js
- // This file contains the implementation of the file cache system used by the VWF nodeJS server.
- var fs = require( 'fs' ),
- mime = require( 'mime' ),
- zlib = require( 'zlib' );
- // Helper function to generate a hash for a string.
- function hash( str ) {
- return require( 'crypto' ).createHash( 'md5' ).update( str ).digest( "hex" );
- }
- function _FileCache( ) {
- this.files = [ ];
- this.enabled = true;
- this.clear = function ( ) {
- this.files.length = 0;
- };
-
- // Function for determining whether to treat file as binary or text
- // based on file extentsion.
- this.getDataType = function ( file ) {
- var type = file.substr( file.lastIndexOf( '.' ) + 1 ).toLowerCase( );
- if ( type === 'js' || type === 'html' || type === 'xml' || type === 'txt' || type === 'xhtml' || type === 'css' ) {
- return "utf8";
- }
- else return "binary";
- };
- // Function for getting a file from the file cache.
- // Asynchronous, returns file to the callback function.
- // Passes null to the callback function if there is no such file.
- this.getFile = function ( path, callback ) {
- //First, check if we have already served this file
- // and have it already cached, if so, return the
- // already cached file.
- for ( var i = 0; i < this.files.length; i++ ) {
- if( this.files[ i ].path == path ) {
- global.log( 'serving from cache: ' + path, 2 );
- callback( this.files[ i ] );
- return;
- }
- }
- // if got here, have no record of this file yet.
- var datatype = this.getDataType( path );
- var file = fs.readFileSync( path );
- var stats = fs.statSync( path );
- if ( file ) {
- // The file was not already in the cache, but does exist.
- // Gzip the file, which is an async process, and callback
- // with the gzipped file entry when finished.
- var self = this;
- zlib.gzip( file, function ( _, zippeddata ) {
-
- var newentry = { };
- newentry.path = path;
- newentry.data = file;
- newentry.stats = stats;
- newentry.zippeddata = zippeddata;
- newentry.contentlength = file.length;
- newentry.datatype = datatype;
- newentry.hash = hash( file );
-
- global.log( newentry.hash, 2 );
- global.log( 'loading into cache: ' + path, 2 );
- if ( self.enabled == true ) {
- self.files.push( newentry );
- var watcher = fs.watchFile( path, { }, function ( event, filename ) {
- global.log( this.entry.path + ' has changed on disk', 2 );
- self.files.splice( self.files.indexOf( this.entry ), 1 );
- fs.unwatchFile(path); //make sure not to create new watchers at every reload
- } );
- watcher.entry = newentry;
- }
- callback( newentry );
- return;
- } );
- // Just returning since we're waiting on the async gzip process.
- return;
- }
- // File was not in cache, and did not exists, return null.
- callback( null );
- };
-
- // Function to serve a file out of the filecache.
- this.ServeFile = function ( request, filename, response, URL ) {
- FileCache.getFile( filename, function ( file ) {
- if ( !file ) {
- response.writeHead( 500, {
- "Content-Type": "text/plain"
- } );
- response.write( 'file load error' + '\n');
- response.end( );
- return;
- }
- var type = mime.getType( filename );
- if ( request.headers[ 'if-none-match' ] === file.hash ) {
- response.writeHead( 304, {
- "Content-Type": type,
- "Last-Modified": encodeURIComponent(file.stats.mtime),
- "ETag": file.hash,
- "Cache-Control":"public; max-age=31536000" ,
- } );
- response.end( );
- return;
- }
- if ( request.headers[ 'accept-encoding' ] && request.headers[ 'accept-encoding' ].indexOf( 'gzip' ) >= 0 ) {
- response.writeHead( 200, {
- "Content-Type": type,
- "Last-Modified": encodeURIComponent(file.stats.mtime),
- "ETag": file.hash,
- "Cache-Control":"public; max-age=31536000" ,
- 'Content-Encoding': 'gzip'
- } );
- response.write(file.zippeddata, file.datatype);
- }
- else if ( request.headers['range'] ) {
- // To support the HTML 5 audio tag, the server needs to respond with the Content-Range in the header
- var range = request.headers['range'];
- range = range.split('=')[1];
- var ranges = range.split('-');
- var start = parseInt(ranges[0]);
- var end = parseInt(ranges[1]) || (file.contentlength - 1);
- var bytesheader = 'bytes ' + start + '-' + end + '/' + file.contentlength;
- response.writeHead(206, {
- "Content-Type": type,
- "Content-Length": (end - start) + 1,
- "Last-Modified": encodeURIComponent(file.stats.mtime),
- "ETag": file.hash,
- "Cache-Control": "public; max-age=31536000",
- "Accept-Ranges": "bytes",
- "Content-Range": bytesheader
- });
- var newdata = file.data.slice(start, end + 1);
- response.write(newdata, file.datatype);
- }
- else {
- response.writeHead( 200, {
- "Content-Type": type,
- "Last-Modified": encodeURIComponent(file.stats.mtime),
- "ETag": file.hash,
- "Cache-Control":"public; max-age=31536000"
- } );
- response.write( file.data, file.datatype );
- }
- response.end( );
- } );
- };
- }
- exports._FileCache = _FileCache;
|