Skip to content

Commit

Permalink
Merge pull request socketio#562 from 3rd-Eden/static
Browse files Browse the repository at this point in the history
Hardcore caching for pro's
  • Loading branch information
rauchg committed Oct 10, 2011
2 parents 61bd23f + 0e3bbd0 commit 59e4c3b
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 8 deletions.
1 change: 1 addition & 0 deletions lib/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ function Manager (server, options) {
, 'browser client cache': true
, 'browser client minification': false
, 'browser client etag': false
, 'browser client expires': 315360000
, 'browser client gzip': false
, 'browser client handler': false
, 'client store expiration': 15
Expand Down
33 changes: 27 additions & 6 deletions lib/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ var mime = {
* @api private
*/

var bundle = /\+((?:\+)?[\w\-]+)*(?:\.js)$/g;
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
, versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;

/**
* Export the constructor
Expand Down Expand Up @@ -120,10 +121,14 @@ Static.prototype.init = function () {
build(self.manager.get('transports'), callback);
});

this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
build(self.manager.get('transports'), callback);
});

// allow custom builds based on url paths
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
var available = self.manager.get('transports')
, matches = bundle.exec(path)
, matches = path.match(bundle)
, transports = [];

if (!matches) return callback('No valid transports');
Expand Down Expand Up @@ -217,7 +222,7 @@ Static.prototype.has = function (path) {
, i = keys.length;

while (i--) {
if (!!~path.indexOf(keys[i])) return this.paths[keys[i]];
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
}

return false;
Expand Down Expand Up @@ -271,7 +276,13 @@ Static.prototype.write = function (path, req, res) {
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || undefined);
res.end(content || '', encoding || undefined);

// only write content if it's not a HEAD request and we actually have
// some content to write (304's doesn't have content).
res.end(
req.method !== 'HEAD' && content ? content : ''
, encoding || undefined
);
} catch (e) {}
}

Expand All @@ -291,19 +302,28 @@ Static.prototype.write = function (path, req, res) {
var accept = req.headers['accept-encoding'] || ''
, gzip = !!~accept.toLowerCase().indexOf('gzip')
, mime = reply.mime
, versioned = reply.versioned
, headers = {
'Content-Type': mime.type
};

// check if we can add a etag
if (self.manager.enabled('browser client etag') && reply.etag) {
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
headers['Etag'] = reply.etag;
}

// check if we can send gzip data
// see if we need to set Expire headers because the path is versioned
if (versioned) {
var expires = self.manager.get('browser client expires');
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
headers['Date'] = new Date().toUTCString();
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
}

if (gzip && reply.gzip) {
headers['Content-Length'] = reply.gzip.length;
headers['Content-Encoding'] = 'gzip';
headers['Vary'] = 'Accept-Encoding';
write(200, headers, reply.gzip.content, mime.encoding);
} else {
headers['Content-Length'] = reply.length;
Expand Down Expand Up @@ -342,6 +362,7 @@ Static.prototype.write = function (path, req, res) {
, length: content.length
, mime: details.mime
, etag: etag || client.version
, versioned: versioning.test(path)
};

// check if gzip is enabled
Expand Down
18 changes: 18 additions & 0 deletions test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,24 @@ HTTPClient.prototype.post = function (path, data, opts, fn) {
return this.request(path, opts, fn);
};

/**
* Issue a HEAD request
*
* @api private
*/

HTTPClient.prototype.head = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}

opts = opts || {};
opts.method = 'HEAD';

return this.request(path, opts, fn);
};

/**
* Performs a handshake (GET) request
*
Expand Down
67 changes: 65 additions & 2 deletions test/static.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ module.exports = {
, io = sio.listen(port);

(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/socket.io+')).should.be.true;
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;

Expand Down Expand Up @@ -455,6 +457,67 @@ module.exports = {
io.server.close();
done();
});
}
},

'test that HEAD requests work': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);

cl.head('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);

data.should.eql('');

cl.end();
io.server.close()
done();
});
},

'test that a versioned client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);

cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);

data.should.match(/XMLHttpRequest/);

cl.end();
io.server.close();
done();
});
},

'test that a custom versioned build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);

io.set('browser client expires', 1337);

cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);

data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);

cl.end();
io.server.close();
done();
});
}
};

0 comments on commit 59e4c3b

Please sign in to comment.