Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serve slim client #2776

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 48 additions & 48 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
*/

var http = require('http');
var read = require('fs').readFileSync;
var createReadStream = require('fs').createReadStream;
var path = require('path');
var engine = require('engine.io');
var client = require('socket.io-client');
var clientVersion = require('socket.io-client/package').version;
Expand All @@ -14,6 +15,7 @@ var Namespace = require('./namespace');
var Adapter = require('socket.io-adapter');
var debug = require('debug')('socket.io:server');
var url = require('url');
var zlib = require('zlib');

/**
* Module exports.
Expand All @@ -25,8 +27,9 @@ module.exports = Server;
* Socket.IO client source.
*/

var clientSource = undefined;
var clientSourceMap = undefined;
var clientRoot;
var clientPathRegex;
var dotMap = /\.map/;

/**
* Server constructor.
Expand Down Expand Up @@ -97,15 +100,6 @@ Server.prototype.serveClient = function(v){
if (!arguments.length) return this._serveClient;
this._serveClient = v;

if (v && !clientSource) {
clientSource = read(require.resolve('socket.io-client/dist/socket.io.min.js'), 'utf-8');
try {
clientSourceMap = read(require.resolve('socket.io-client/dist/socket.io.js.map'), 'utf-8');
} catch(err) {
debug('could not load sourcemap file');
}
}

return this;
};

Expand Down Expand Up @@ -159,6 +153,9 @@ Server.prototype.set = function(key, val){
Server.prototype.path = function(v){
if (!arguments.length) return this._path;
this._path = v.replace(/\/$/, '');

var escPath = this._path.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
clientPathRegex = new RegExp('^' + escPath + '/socket\\.io(\\.slim)?\\.js(\\.map)?$');
return this;
};

Expand Down Expand Up @@ -260,16 +257,13 @@ Server.prototype.attach = function(srv, opts){

Server.prototype.attachServe = function(srv){
debug('attaching client serving req handler');
var url = this._path + '/socket.io.js';
var urlMap = this._path + '/socket.io.js.map';

var evs = srv.listeners('request').slice(0);
var self = this;
srv.removeAllListeners('request');
srv.on('request', function(req, res) {
if (0 === req.url.indexOf(urlMap)) {
self.serveMap(req, res);
} else if (0 === req.url.indexOf(url)) {
self.serve(req, res);
if (clientPathRegex.test(req.url)) {
self.serve(req, res, req.url);
} else {
for (var i = 0; i < evs.length; i++) {
evs[i].call(srv, req, res);
Expand All @@ -279,64 +273,70 @@ Server.prototype.attachServe = function(srv){
};

/**
* Handles a request serving `/socket.io.js`
* Handles a request serving of client source and map
*
* @param {http.Request} req
* @param {http.Response} res
* @api private
*/

Server.prototype.serve = function(req, res){
Server.prototype.serve = function(req, res, url) {
var file = url.replace(this._path, '');
var isMap = dotMap.test(file);
var message = (isMap ? 'map' : 'source');

// Per the standard, ETags must be quoted:
// https://tools.ietf.org/html/rfc7232#section-2.3
var expectedEtag = '"' + clientVersion + '"';

var etag = req.headers['if-none-match'];
if (etag) {
if (expectedEtag == etag) {
debug('serve client 304');
if (expectedEtag === etag) {
debug('serve client %s 304', message);
res.writeHead(304);
res.end();
return;
}
}

debug('serve client source');
res.setHeader('Content-Type', 'application/javascript');
debug('serve client %s', message);

res.setHeader('Content-Type', 'application/' + (isMap ? 'json' : 'javascript'));
res.setHeader('ETag', expectedEtag);
res.setHeader('X-SourceMap', 'socket.io.js.map');
res.writeHead(200);
res.end(clientSource);

if (!isMap) {
res.setHeader('X-SourceMap', file.substring(1) + '.map');
file = file.replace('.js', '.min.js');
}

this.sendFile(file, req, res);
};

/**
* Handles a request serving `/socket.io.js.map`
* Writes (and gzippes) a file to the response.
*
* @param {String} file The file to write (relative the client dist directory).
* @param {http.Request} req
* @param {http.Response} res
* @api private
*/

Server.prototype.serveMap = function(req, res){
// Per the standard, ETags must be quoted:
// https://tools.ietf.org/html/rfc7232#section-2.3
var expectedEtag = '"' + clientVersion + '"';

var etag = req.headers['if-none-match'];
if (etag) {
if (expectedEtag == etag) {
debug('serve client 304');
res.writeHead(304);
res.end();
return;
}
Server.prototype.sendFile = function(file, req, res) {
if (!clientRoot) {
clientRoot = require.resolve('socket.io-client/dist' + file).replace(file, '/');
}

debug('serve client sourcemap');
res.setHeader('Content-Type', 'application/json');
res.setHeader('ETag', expectedEtag);
res.writeHead(200);
res.end(clientSourceMap);
var readStream = createReadStream(path.join(clientRoot, file));
if (~req.headers['accept-encoding'].indexOf('gzip')) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this if is necessary.

I don't think we support any browser that doesn't support gzip.

res.setHeader('content-encoding', 'gzip');
res.writeHead(200);

var gzip = zlib.createGzip();
readStream.pipe(gzip).pipe(res);
} else {
res.writeHead(200);
readStream.pipe(res);
}
};

/**
Expand Down Expand Up @@ -378,7 +378,7 @@ Server.prototype.onconnection = function(conn){

Server.prototype.of = function(name, fn){
if (String(name)[0] !== '/') name = '/' + name;

var nsp = this.nsps[name];
if (!nsp) {
debug('initializing namespace %s', name);
Expand All @@ -392,7 +392,7 @@ Server.prototype.of = function(name, fn){
/**
* Closes server connection
*
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
* @api public
*/

Expand Down
63 changes: 45 additions & 18 deletions test/socket.io.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,49 @@ describe('socket.io', function(){
describe('http.Server', function(){
var clientVersion = require('socket.io-client/package').version;

it('should serve static files', function(done){
var srv = http();
io(srv);
request(srv)
.get('/socket.io/socket.io.js')
.buffer(true)
.end(function(err, res){
if (err) return done(err);
var ctype = res.headers['content-type'];
expect(ctype).to.be('application/javascript');
expect(res.headers.etag).to.be('"' + clientVersion + '"');
expect(res.text).to.match(/engine\.io/);
expect(res.status).to.be(200);
done();
});
});
function clientSourceTest(file) {
return function(done){
var srv = http();
io(srv);
request(srv)
.get('/socket.io/' + file)
.buffer(true)
.end(function(err, res){
if (err) return done(err);
var ctype = res.headers['content-type'];
expect(ctype).to.be('application/javascript');
expect(res.headers.etag).to.be('"' + clientVersion + '"');
expect(res.headers['x-sourcemap']).to.be(file + '.map');
expect(res.text).to.match(/engine\.io/);
expect(res.status).to.be(200);
done();
});
};
}

function clientMapTest(file) {
return function(done){
var srv = http();
io(srv);
request(srv)
.get('/socket.io/' + file)
.buffer(true)
.end(function(err, res){
if (err) return done(err);
var ctype = res.headers['content-type'];
expect(ctype).to.be('application/json');
expect(res.headers.etag).to.be('"' + clientVersion + '"');
expect(res.text).to.match(/engine\.io/);
expect(res.status).to.be(200);
done();
});
};
}

it('should serve client', clientSourceTest('socket.io.js'));
it('should serve client', clientMapTest('socket.io.js.map'));
it('should serve slim client', clientSourceTest('socket.io.slim.js'));
it('should serve slim client', clientMapTest('socket.io.slim.js.map'));

it('should handle 304', function(done){
var srv = http();
Expand Down Expand Up @@ -680,7 +707,7 @@ describe('socket.io', function(){
});
});
});

it('should not reuse same-namespace connections', function(done){
var srv = http();
var sio = io(srv);
Expand Down Expand Up @@ -1556,7 +1583,7 @@ describe('socket.io', function(){
});
});
});

it('should see query parameters sent from secondary namespace connections in handshake object', function(done){
var srv = http();
var sio = io(srv);
Expand Down