Skip to content

Commit 59e4c3b

Browse files
committed
Merge pull request #562 from 3rd-Eden/static
Hardcore caching for pro's
2 parents 61bd23f + 0e3bbd0 commit 59e4c3b

File tree

4 files changed

+111
-8
lines changed

4 files changed

+111
-8
lines changed

lib/manager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function Manager (server, options) {
7979
, 'browser client cache': true
8080
, 'browser client minification': false
8181
, 'browser client etag': false
82+
, 'browser client expires': 315360000
8283
, 'browser client gzip': false
8384
, 'browser client handler': false
8485
, 'client store expiration': 15

lib/static.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ var mime = {
4242
* @api private
4343
*/
4444

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

4748
/**
4849
* Export the constructor
@@ -120,10 +121,14 @@ Static.prototype.init = function () {
120121
build(self.manager.get('transports'), callback);
121122
});
122123

124+
this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
125+
build(self.manager.get('transports'), callback);
126+
});
127+
123128
// allow custom builds based on url paths
124129
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
125130
var available = self.manager.get('transports')
126-
, matches = bundle.exec(path)
131+
, matches = path.match(bundle)
127132
, transports = [];
128133

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

219224
while (i--) {
220-
if (!!~path.indexOf(keys[i])) return this.paths[keys[i]];
225+
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
221226
}
222227

223228
return false;
@@ -271,7 +276,13 @@ Static.prototype.write = function (path, req, res) {
271276
function write (status, headers, content, encoding) {
272277
try {
273278
res.writeHead(status, headers || undefined);
274-
res.end(content || '', encoding || undefined);
279+
280+
// only write content if it's not a HEAD request and we actually have
281+
// some content to write (304's doesn't have content).
282+
res.end(
283+
req.method !== 'HEAD' && content ? content : ''
284+
, encoding || undefined
285+
);
275286
} catch (e) {}
276287
}
277288

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

298310
// check if we can add a etag
299-
if (self.manager.enabled('browser client etag') && reply.etag) {
311+
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
300312
headers['Etag'] = reply.etag;
301313
}
302314

303-
// check if we can send gzip data
315+
// see if we need to set Expire headers because the path is versioned
316+
if (versioned) {
317+
var expires = self.manager.get('browser client expires');
318+
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
319+
headers['Date'] = new Date().toUTCString();
320+
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
321+
}
322+
304323
if (gzip && reply.gzip) {
305324
headers['Content-Length'] = reply.gzip.length;
306325
headers['Content-Encoding'] = 'gzip';
326+
headers['Vary'] = 'Accept-Encoding';
307327
write(200, headers, reply.gzip.content, mime.encoding);
308328
} else {
309329
headers['Content-Length'] = reply.length;
@@ -342,6 +362,7 @@ Static.prototype.write = function (path, req, res) {
342362
, length: content.length
343363
, mime: details.mime
344364
, etag: etag || client.version
365+
, versioned: versioning.test(path)
345366
};
346367

347368
// check if gzip is enabled

test/common.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,24 @@ HTTPClient.prototype.post = function (path, data, opts, fn) {
147147
return this.request(path, opts, fn);
148148
};
149149

150+
/**
151+
* Issue a HEAD request
152+
*
153+
* @api private
154+
*/
155+
156+
HTTPClient.prototype.head = function (path, opts, fn) {
157+
if ('function' == typeof opts) {
158+
fn = opts;
159+
opts = {};
160+
}
161+
162+
opts = opts || {};
163+
opts.method = 'HEAD';
164+
165+
return this.request(path, opts, fn);
166+
};
167+
150168
/**
151169
* Performs a handshake (GET) request
152170
*

test/static.test.js

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ module.exports = {
2424
, io = sio.listen(port);
2525

2626
(!!io.static.has('/socket.io.js')).should.be.true;
27-
(!!io.static.has('/socket.io+')).should.be.true;
27+
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
28+
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
29+
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
2830
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
2931
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;
3032

@@ -455,6 +457,67 @@ module.exports = {
455457
io.server.close();
456458
done();
457459
});
458-
}
460+
},
461+
462+
'test that HEAD requests work': function (done) {
463+
var port = ++ports
464+
, io = sio.listen(port)
465+
, cl = client(port);
466+
467+
cl.head('/socket.io/socket.io.js', function (res, data) {
468+
res.headers['content-type'].should.eql('application/javascript');
469+
res.headers['content-length'].should.match(/([0-9]+)/);
470+
471+
data.should.eql('');
472+
473+
cl.end();
474+
io.server.close()
475+
done();
476+
});
477+
},
478+
479+
'test that a versioned client is served': function (done) {
480+
var port = ++ports
481+
, io = sio.listen(port)
482+
, cl = client(port);
483+
484+
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
485+
res.headers['content-type'].should.eql('application/javascript');
486+
res.headers['content-length'].should.match(/([0-9]+)/);
487+
res.headers['cache-control']
488+
.indexOf(io.get('browser client expires')).should.be.above(-1);
489+
490+
data.should.match(/XMLHttpRequest/);
491+
492+
cl.end();
493+
io.server.close();
494+
done();
495+
});
496+
},
497+
498+
'test that a custom versioned build client is served': function (done) {
499+
var port = ++ports
500+
, io = sio.listen(port)
501+
, cl = client(port);
502+
503+
io.set('browser client expires', 1337);
504+
505+
cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
506+
res.headers['content-type'].should.eql('application/javascript');
507+
res.headers['content-length'].should.match(/([0-9]+)/);
508+
res.headers['cache-control']
509+
.indexOf(io.get('browser client expires')).should.be.above(-1);
459510

511+
data.should.match(/XMLHttpRequest/);
512+
data.should.match(/WS\.prototype\.name/);
513+
data.should.not.match(/Flashsocket\.prototype\.name/);
514+
data.should.not.match(/HTMLFile\.prototype\.name/);
515+
data.should.not.match(/JSONPPolling\.prototype\.name/);
516+
data.should.not.match(/XHRPolling\.prototype\.name/);
517+
518+
cl.end();
519+
io.server.close();
520+
done();
521+
});
522+
}
460523
};

0 commit comments

Comments
 (0)