-var ws = require('socket.io'),
- events = require('events'),
- http = require('http'),
- https = require('https'),
- util = require('util'),
- fs = require('fs'),
- dns = require('dns'),
- url = require('url'),
- _ = require('lodash'),
- spdy = require('spdy'),
- Client = require('./client.js').Client,
- HttpHandler = require('./httphandler.js').HttpHandler,
- rehash = require('./rehash.js'),
- range_check = require('range_check');
+var engine = require('engine.io'),
+ WebsocketRpc = require('./websocketrpc.js'),
+ events = require('events'),
+ http = require('http'),
+ https = require('https'),
+ util = require('util'),
+ fs = require('fs'),
+ dns = require('dns'),
+ url = require('url'),
+ _ = require('lodash'),
+ spdy = require('spdy'),
+ ipaddr = require('ipaddr.js'),
+ winston = require('winston'),
+ Client = require('./client.js').Client,
+ HttpHandler = require('./httphandler.js').HttpHandler,
+ rehash = require('./rehash.js'),
+ Stats = require('./stats.js');
var http_handler;
-var WebListener = function (web_config, transports) {
- var hs, opts, ws_opts,
+var WebListener = module.exports = function (web_config) {
+ var hs, opts,
that = this;
http_handler = new HttpHandler(web_config);
- // Standard options for the socket.io connections
- ws_opts = {
- 'log level': 0,
- 'log colors': 0
- };
-
-
if (web_config.ssl) {
opts = {
key: fs.readFileSync(web_config.ssl_key),
}
}
- hs = spdy.createServer(opts, handleHttpRequest);
+ hs = spdy.createServer(opts);
- // Start socket.io listening on this weblistener
- this.ws = ws.listen(hs, _.extend({ssl: true}, ws_opts));
hs.listen(web_config.port, web_config.address, function () {
that.emit('listening');
});
} else {
// Start some plain-text server up
- hs = http.createServer(handleHttpRequest);
+ hs = http.createServer();
- // Start socket.io listening on this weblistener
- this.ws = ws.listen(hs, _.extend({ssl: false}, ws_opts));
hs.listen(web_config.port, web_config.address, function () {
that.emit('listening');
});
that.emit('error', err);
});
- this.ws.enable('browser client minification');
- this.ws.enable('browser client etag');
- this.ws.set('transports', transports);
- this.ws.set('resource', (global.config.http_base_path || '') + '/transport');
+ this.ws = new engine.Server();
+
+ hs.on('upgrade', function(req, socket, head){
+ // engine.io can sometimes "loose" the clients remote address. Keep note of it
+ req.meta = {
+ remote_address: req.connection.remoteAddress
+ };
+
+ that.ws.handleUpgrade(req, socket, head);
+ });
+
+ hs.on('request', function(req, res){
+ var base_path = (global.config.http_base_path || ''),
+ transport_url;
+
+ // Trim off any trailing slashes
+ if (base_path.substr(base_path.length - 1) === '/') {
+ base_path = base_path.substr(0, base_path.length - 1);
+ }
+ transport_url = base_path + '/transport';
+
+ Stats.incr('http.request');
- this.ws.of('/kiwi').authorization(authoriseConnection)
- .on('connection', function () {
- newConnection.apply(that, arguments);
+ // engine.io can sometimes "loose" the clients remote address. Keep note of it
+ req.meta = {
+ remote_address: req.connection.remoteAddress
+ };
+
+ // If the request is for our transport, pass it onto engine.io
+ if (req.url.toLowerCase().indexOf(transport_url.toLowerCase()) === 0) {
+ that.ws.handleRequest(req, res);
+ } else {
+ http_handler.serve(req, res);
}
- );
- this.ws.of('/kiwi').on('error', console.log);
+
+
+ });
+
+ this.ws.on('connection', function(socket) {
+ Stats.incr('http.websocket');
+
+ initialiseSocket(socket, function(err, authorised) {
+ var client;
+
+ if (!authorised) {
+ socket.close();
+ return;
+ }
+
+ client = new Client(socket, {server_config: web_config});
+ client.on('dispose', function () {
+ that.emit('client_dispose', this);
+ });
+
+ that.emit('connection', client);
+
+ // Call any modules listening for new clients
+ global.modules.emit('client created', {client: client});
+ });
+ });
};
util.inherits(WebListener, events.EventEmitter);
-function handleHttpRequest(request, response) {
- var uri = url.parse(request.url, true);
-
- // If this isn't a socket.io request, pass it onto the http handler
- if (uri.pathname.substr(0, 10) !== '/socket.io') {
- http_handler.serve(request, response);
+function rangeCheck(addr, range) {
+ var i, ranges, parts;
+ ranges = (!_.isArray(range)) ? [range] : range;
+ for (i = 0; i < ranges.length; i++) {
+ parts = ranges[i].split('/');
+ if (ipaddr.process(addr).match(ipaddr.process(parts[0]), parts[1])) {
+ return true;
+ }
}
+ return false;
}
* Get the reverse DNS entry for this connection.
* Used later on for webirc, etc functionality
*/
-function authoriseConnection(handshakeData, callback) {
- var address = handshakeData.address.address;
+function initialiseSocket(socket, callback) {
+ var request = socket.request,
+ address = request.meta.remote_address,
+ revdns;
+
+ // Key/val data stored to the socket to be read later on
+ // May also be synced to a redis DB to lookup clients
+ socket.meta = socket.request.meta;
// If a forwarded-for header is found, switch the source address
- if (handshakeData.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']) {
+ if (request.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']) {
// Check we're connecting from a whitelisted proxy
- if (!global.config.http_proxies || !range_check.in_range(address, global.config.http_proxies)) {
- console.log('Unlisted proxy:', address);
+ if (!global.config.http_proxies || !rangeCheck(address, global.config.http_proxies)) {
+ winston.info('Unlisted proxy: %s', address);
callback(null, false);
return;
}
// We're sent from a whitelisted proxy, replace the hosts
- address = handshakeData.headers['x-forwarded-for'];
+ address = request.headers[global.config.http_proxy_ip_header || 'x-forwarded-for'];
}
- handshakeData.real_address = address;
+ socket.meta.real_address = address;
// If enabled, don't go over the connection limit
if (global.config.max_client_conns && global.config.max_client_conns > 0) {
try {
dns.reverse(address, function (err, domains) {
- if (err || domains.length === 0) {
- handshakeData.revdns = address;
- } else {
- handshakeData.revdns = _.first(domains) || address;
+ if (!err && domains.length > 0) {
+ revdns = _.first(domains);
}
- // All is well, authorise the connection
- callback(null, true);
+ if (!revdns) {
+ // No reverse DNS found, use the IP
+ socket.meta.revdns = address;
+ callback(null, true);
+
+ } else {
+ // Make sure the reverse DNS matches the A record to use the hostname..
+ dns.lookup(revdns, function (err, ip_address, family) {
+ if (!err && ip_address == address) {
+ // A record matches PTR, perfectly valid hostname
+ socket.meta.revdns = revdns;
+ } else {
+ // A record does not match the PTR, invalid hostname
+ socket.meta.revdns = address;
+ }
+
+ // We have all the info we need, proceed with the connection
+ callback(null, true);
+ });
+ }
});
+
} catch (err) {
- handshakeData.revdns = address;
+ socket.meta.revdns = address;
callback(null, true);
}
}
-
-function newConnection(websocket) {
- var client, that = this;
- client = new Client(websocket);
- client.on('dispose', function () {
- that.emit('client_dispose', this);
- });
- this.emit('connection', client);
-}
-
-
-
-
-
-module.exports = WebListener;