annotate node_modules/socket.io/lib/static.js @ 75:3a2845e3156e

added martin tweaks
author Rob Canning <rc@kiben.net>
date Tue, 01 Jul 2014 08:51:53 +0000
parents 333afcfd3f3a
children
rev   line source
rc-web@69 1
rc-web@69 2 /*!
rc-web@69 3 * socket.io-node
rc-web@69 4 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
rc-web@69 5 * MIT Licensed
rc-web@69 6 */
rc-web@69 7
rc-web@69 8 /**
rc-web@69 9 * Module dependencies.
rc-web@69 10 */
rc-web@69 11
rc-web@69 12 var client = require('socket.io-client')
rc-web@69 13 , cp = require('child_process')
rc-web@69 14 , fs = require('fs')
rc-web@69 15 , util = require('./util');
rc-web@69 16
rc-web@69 17 /**
rc-web@69 18 * File type details.
rc-web@69 19 *
rc-web@69 20 * @api private
rc-web@69 21 */
rc-web@69 22
rc-web@69 23 var mime = {
rc-web@69 24 js: {
rc-web@69 25 type: 'application/javascript'
rc-web@69 26 , encoding: 'utf8'
rc-web@69 27 , gzip: true
rc-web@69 28 }
rc-web@69 29 , swf: {
rc-web@69 30 type: 'application/x-shockwave-flash'
rc-web@69 31 , encoding: 'binary'
rc-web@69 32 , gzip: false
rc-web@69 33 }
rc-web@69 34 };
rc-web@69 35
rc-web@69 36 /**
rc-web@69 37 * Regexp for matching custom transport patterns. Users can configure their own
rc-web@69 38 * socket.io bundle based on the url structure. Different transport names are
rc-web@69 39 * concatinated using the `+` char. /socket.io/socket.io+websocket.js should
rc-web@69 40 * create a bundle that only contains support for the websocket.
rc-web@69 41 *
rc-web@69 42 * @api private
rc-web@69 43 */
rc-web@69 44
rc-web@69 45 var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
rc-web@69 46 , versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
rc-web@69 47
rc-web@69 48 /**
rc-web@69 49 * Export the constructor
rc-web@69 50 */
rc-web@69 51
rc-web@69 52 exports = module.exports = Static;
rc-web@69 53
rc-web@69 54 /**
rc-web@69 55 * Static constructor
rc-web@69 56 *
rc-web@69 57 * @api public
rc-web@69 58 */
rc-web@69 59
rc-web@69 60 function Static (manager) {
rc-web@69 61 this.manager = manager;
rc-web@69 62 this.cache = {};
rc-web@69 63 this.paths = {};
rc-web@69 64
rc-web@69 65 this.init();
rc-web@69 66 }
rc-web@69 67
rc-web@69 68 /**
rc-web@69 69 * Initialize the Static by adding default file paths.
rc-web@69 70 *
rc-web@69 71 * @api public
rc-web@69 72 */
rc-web@69 73
rc-web@69 74 Static.prototype.init = function () {
rc-web@69 75 /**
rc-web@69 76 * Generates a unique id based the supplied transports array
rc-web@69 77 *
rc-web@69 78 * @param {Array} transports The array with transport types
rc-web@69 79 * @api private
rc-web@69 80 */
rc-web@69 81 function id (transports) {
rc-web@69 82 var id = transports.join('').split('').map(function (char) {
rc-web@69 83 return ('' + char.charCodeAt(0)).split('').pop();
rc-web@69 84 }).reduce(function (char, id) {
rc-web@69 85 return char +id;
rc-web@69 86 });
rc-web@69 87
rc-web@69 88 return client.version + ':' + id;
rc-web@69 89 }
rc-web@69 90
rc-web@69 91 /**
rc-web@69 92 * Generates a socket.io-client file based on the supplied transports.
rc-web@69 93 *
rc-web@69 94 * @param {Array} transports The array with transport types
rc-web@69 95 * @param {Function} callback Callback for the static.write
rc-web@69 96 * @api private
rc-web@69 97 */
rc-web@69 98
rc-web@69 99 function build (transports, callback) {
rc-web@69 100 client.builder(transports, {
rc-web@69 101 minify: self.manager.enabled('browser client minification')
rc-web@69 102 }, function (err, content) {
rc-web@69 103 callback(err, content ? new Buffer(content) : null, id(transports));
rc-web@69 104 }
rc-web@69 105 );
rc-web@69 106 }
rc-web@69 107
rc-web@69 108 var self = this;
rc-web@69 109
rc-web@69 110 // add our default static files
rc-web@69 111 this.add('/static/flashsocket/WebSocketMain.swf', {
rc-web@69 112 file: client.dist + '/WebSocketMain.swf'
rc-web@69 113 });
rc-web@69 114
rc-web@69 115 this.add('/static/flashsocket/WebSocketMainInsecure.swf', {
rc-web@69 116 file: client.dist + '/WebSocketMainInsecure.swf'
rc-web@69 117 });
rc-web@69 118
rc-web@69 119 // generates dedicated build based on the available transports
rc-web@69 120 this.add('/socket.io.js', function (path, callback) {
rc-web@69 121 build(self.manager.get('transports'), callback);
rc-web@69 122 });
rc-web@69 123
rc-web@69 124 this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
rc-web@69 125 build(self.manager.get('transports'), callback);
rc-web@69 126 });
rc-web@69 127
rc-web@69 128 // allow custom builds based on url paths
rc-web@69 129 this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
rc-web@69 130 var available = self.manager.get('transports')
rc-web@69 131 , matches = path.match(bundle)
rc-web@69 132 , transports = [];
rc-web@69 133
rc-web@69 134 if (!matches) return callback('No valid transports');
rc-web@69 135
rc-web@69 136 // make sure they valid transports
rc-web@69 137 matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) {
rc-web@69 138 if (!!~available.indexOf(transport)) {
rc-web@69 139 transports.push(transport);
rc-web@69 140 }
rc-web@69 141 });
rc-web@69 142
rc-web@69 143 if (!transports.length) return callback('No valid transports');
rc-web@69 144 build(transports, callback);
rc-web@69 145 });
rc-web@69 146
rc-web@69 147 // clear cache when transports change
rc-web@69 148 this.manager.on('set:transports', function (key, value) {
rc-web@69 149 delete self.cache['/socket.io.js'];
rc-web@69 150 Object.keys(self.cache).forEach(function (key) {
rc-web@69 151 if (bundle.test(key)) {
rc-web@69 152 delete self.cache[key];
rc-web@69 153 }
rc-web@69 154 });
rc-web@69 155 });
rc-web@69 156 };
rc-web@69 157
rc-web@69 158 /**
rc-web@69 159 * Gzip compress buffers.
rc-web@69 160 *
rc-web@69 161 * @param {Buffer} data The buffer that needs gzip compression
rc-web@69 162 * @param {Function} callback
rc-web@69 163 * @api public
rc-web@69 164 */
rc-web@69 165
rc-web@69 166 Static.prototype.gzip = function (data, callback) {
rc-web@69 167 var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n'])
rc-web@69 168 , encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8'
rc-web@69 169 , buffer = []
rc-web@69 170 , err;
rc-web@69 171
rc-web@69 172 gzip.stdout.on('data', function (data) {
rc-web@69 173 buffer.push(data);
rc-web@69 174 });
rc-web@69 175
rc-web@69 176 gzip.stderr.on('data', function (data) {
rc-web@69 177 err = data +'';
rc-web@69 178 buffer.length = 0;
rc-web@69 179 });
rc-web@69 180
rc-web@69 181 gzip.on('close', function () {
rc-web@69 182 if (err) return callback(err);
rc-web@69 183
rc-web@69 184 var size = 0
rc-web@69 185 , index = 0
rc-web@69 186 , i = buffer.length
rc-web@69 187 , content;
rc-web@69 188
rc-web@69 189 while (i--) {
rc-web@69 190 size += buffer[i].length;
rc-web@69 191 }
rc-web@69 192
rc-web@69 193 content = new Buffer(size);
rc-web@69 194 i = buffer.length;
rc-web@69 195
rc-web@69 196 buffer.forEach(function (buffer) {
rc-web@69 197 var length = buffer.length;
rc-web@69 198
rc-web@69 199 buffer.copy(content, index, 0, length);
rc-web@69 200 index += length;
rc-web@69 201 });
rc-web@69 202
rc-web@69 203 buffer.length = 0;
rc-web@69 204 callback(null, content);
rc-web@69 205 });
rc-web@69 206
rc-web@69 207 gzip.stdin.end(data, encoding);
rc-web@69 208 };
rc-web@69 209
rc-web@69 210 /**
rc-web@69 211 * Is the path a static file?
rc-web@69 212 *
rc-web@69 213 * @param {String} path The path that needs to be checked
rc-web@69 214 * @api public
rc-web@69 215 */
rc-web@69 216
rc-web@69 217 Static.prototype.has = function (path) {
rc-web@69 218 // fast case
rc-web@69 219 if (this.paths[path]) return this.paths[path];
rc-web@69 220
rc-web@69 221 var keys = Object.keys(this.paths)
rc-web@69 222 , i = keys.length;
rc-web@69 223
rc-web@69 224 while (i--) {
rc-web@69 225 if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
rc-web@69 226 }
rc-web@69 227
rc-web@69 228 return false;
rc-web@69 229 };
rc-web@69 230
rc-web@69 231 /**
rc-web@69 232 * Add new paths new paths that can be served using the static provider.
rc-web@69 233 *
rc-web@69 234 * @param {String} path The path to respond to
rc-web@69 235 * @param {Options} options Options for writing out the response
rc-web@69 236 * @param {Function} [callback] Optional callback if no options.file is
rc-web@69 237 * supplied this would be called instead.
rc-web@69 238 * @api public
rc-web@69 239 */
rc-web@69 240
rc-web@69 241 Static.prototype.add = function (path, options, callback) {
rc-web@69 242 var extension = /(?:\.(\w{1,4}))$/.exec(path);
rc-web@69 243
rc-web@69 244 if (!callback && typeof options == 'function') {
rc-web@69 245 callback = options;
rc-web@69 246 options = {};
rc-web@69 247 }
rc-web@69 248
rc-web@69 249 options.mime = options.mime || (extension ? mime[extension[1]] : false);
rc-web@69 250
rc-web@69 251 if (callback) options.callback = callback;
rc-web@69 252 if (!(options.file || options.callback) || !options.mime) return false;
rc-web@69 253
rc-web@69 254 this.paths[path] = options;
rc-web@69 255
rc-web@69 256 return true;
rc-web@69 257 };
rc-web@69 258
rc-web@69 259 /**
rc-web@69 260 * Writes a static response.
rc-web@69 261 *
rc-web@69 262 * @param {String} path The path for the static content
rc-web@69 263 * @param {HTTPRequest} req The request object
rc-web@69 264 * @param {HTTPResponse} res The response object
rc-web@69 265 * @api public
rc-web@69 266 */
rc-web@69 267
rc-web@69 268 Static.prototype.write = function (path, req, res) {
rc-web@69 269 /**
rc-web@69 270 * Write a response without throwing errors because can throw error if the
rc-web@69 271 * response is no longer writable etc.
rc-web@69 272 *
rc-web@69 273 * @api private
rc-web@69 274 */
rc-web@69 275
rc-web@69 276 function write (status, headers, content, encoding) {
rc-web@69 277 try {
rc-web@69 278 res.writeHead(status, headers || undefined);
rc-web@69 279
rc-web@69 280 // only write content if it's not a HEAD request and we actually have
rc-web@69 281 // some content to write (304's doesn't have content).
rc-web@69 282 res.end(
rc-web@69 283 req.method !== 'HEAD' && content ? content : ''
rc-web@69 284 , encoding || undefined
rc-web@69 285 );
rc-web@69 286 } catch (e) {}
rc-web@69 287 }
rc-web@69 288
rc-web@69 289 /**
rc-web@69 290 * Answers requests depending on the request properties and the reply object.
rc-web@69 291 *
rc-web@69 292 * @param {Object} reply The details and content to reply the response with
rc-web@69 293 * @api private
rc-web@69 294 */
rc-web@69 295
rc-web@69 296 function answer (reply) {
rc-web@69 297 var cached = req.headers['if-none-match'] === reply.etag;
rc-web@69 298 if (cached && self.manager.enabled('browser client etag')) {
rc-web@69 299 return write(304);
rc-web@69 300 }
rc-web@69 301
rc-web@69 302 var accept = req.headers['accept-encoding'] || ''
rc-web@69 303 , gzip = !!~accept.toLowerCase().indexOf('gzip')
rc-web@69 304 , mime = reply.mime
rc-web@69 305 , versioned = reply.versioned
rc-web@69 306 , headers = {
rc-web@69 307 'Content-Type': mime.type
rc-web@69 308 };
rc-web@69 309
rc-web@69 310 // check if we can add a etag
rc-web@69 311 if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
rc-web@69 312 headers['Etag'] = reply.etag;
rc-web@69 313 }
rc-web@69 314
rc-web@69 315 // see if we need to set Expire headers because the path is versioned
rc-web@69 316 if (versioned) {
rc-web@69 317 var expires = self.manager.get('browser client expires');
rc-web@69 318 headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
rc-web@69 319 headers['Date'] = new Date().toUTCString();
rc-web@69 320 headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
rc-web@69 321 }
rc-web@69 322
rc-web@69 323 if (gzip && reply.gzip) {
rc-web@69 324 headers['Content-Length'] = reply.gzip.length;
rc-web@69 325 headers['Content-Encoding'] = 'gzip';
rc-web@69 326 headers['Vary'] = 'Accept-Encoding';
rc-web@69 327 write(200, headers, reply.gzip.content, mime.encoding);
rc-web@69 328 } else {
rc-web@69 329 headers['Content-Length'] = reply.length;
rc-web@69 330 write(200, headers, reply.content, mime.encoding);
rc-web@69 331 }
rc-web@69 332
rc-web@69 333 self.manager.log.debug('served static content ' + path);
rc-web@69 334 }
rc-web@69 335
rc-web@69 336 var self = this
rc-web@69 337 , details;
rc-web@69 338
rc-web@69 339 // most common case first
rc-web@69 340 if (this.manager.enabled('browser client cache') && this.cache[path]) {
rc-web@69 341 return answer(this.cache[path]);
rc-web@69 342 } else if (this.manager.get('browser client handler')) {
rc-web@69 343 return this.manager.get('browser client handler').call(this, req, res);
rc-web@69 344 } else if ((details = this.has(path))) {
rc-web@69 345 /**
rc-web@69 346 * A small helper function that will let us deal with fs and dynamic files
rc-web@69 347 *
rc-web@69 348 * @param {Object} err Optional error
rc-web@69 349 * @param {Buffer} content The data
rc-web@69 350 * @api private
rc-web@69 351 */
rc-web@69 352
rc-web@69 353 function ready (err, content, etag) {
rc-web@69 354 if (err) {
rc-web@69 355 self.manager.log.warn('Unable to serve file. ' + (err.message || err));
rc-web@69 356 return write(500, null, 'Error serving static ' + path);
rc-web@69 357 }
rc-web@69 358
rc-web@69 359 // store the result in the cache
rc-web@69 360 var reply = self.cache[path] = {
rc-web@69 361 content: content
rc-web@69 362 , length: content.length
rc-web@69 363 , mime: details.mime
rc-web@69 364 , etag: etag || client.version
rc-web@69 365 , versioned: versioning.test(path)
rc-web@69 366 };
rc-web@69 367
rc-web@69 368 // check if gzip is enabled
rc-web@69 369 if (details.mime.gzip && self.manager.enabled('browser client gzip')) {
rc-web@69 370 self.gzip(content, function (err, content) {
rc-web@69 371 if (!err) {
rc-web@69 372 reply.gzip = {
rc-web@69 373 content: content
rc-web@69 374 , length: content.length
rc-web@69 375 }
rc-web@69 376 }
rc-web@69 377
rc-web@69 378 answer(reply);
rc-web@69 379 });
rc-web@69 380 } else {
rc-web@69 381 answer(reply);
rc-web@69 382 }
rc-web@69 383 }
rc-web@69 384
rc-web@69 385 if (details.file) {
rc-web@69 386 fs.readFile(details.file, ready);
rc-web@69 387 } else if(details.callback) {
rc-web@69 388 details.callback.call(this, path, ready);
rc-web@69 389 } else {
rc-web@69 390 write(404, null, 'File handle not found');
rc-web@69 391 }
rc-web@69 392 } else {
rc-web@69 393 write(404, null, 'File not found');
rc-web@69 394 }
rc-web@69 395 };