/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var extend = require('./utils').extend;
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var WebSocketRouterRequest = require('./websocket_router_request');
function WebSocketRouter(config) {
// Superclass Constructor
EventEmitter.call(this);
this.config = {
// The WebSocketServer instance to attach to.
server: null
};
if (config) {
extend(this.config, config);
}
this.handlers = [];
this._requestHandler = this.handleRequest.bind(this);
if (this.config.server) {
this.attachServer(this.config.server);
}
}
util.inherits(WebSocketRouter, EventEmitter);
WebSocketRouter.prototype.attachServer = function(server) {
if (server) {
this.server = server;
this.server.on('request', this._requestHandler);
}
else {
throw new Error('You must specify a WebSocketServer instance to attach to.');
}
};
WebSocketRouter.prototype.detachServer = function() {
if (this.server) {
this.server.removeListener('request', this._requestHandler);
this.server = null;
}
else {
throw new Error('Cannot detach from server: not attached.');
}
};
WebSocketRouter.prototype.mount = function(path, protocol, callback) {
if (!path) {
throw new Error('You must specify a path for this handler.');
}
if (!protocol) {
protocol = '____no_protocol____';
}
if (!callback) {
throw new Error('You must specify a callback for this handler.');
}
path = this.pathToRegExp(path);
if (!(path instanceof RegExp)) {
throw new Error('Path must be specified as either a string or a RegExp.');
}
var pathString = path.toString();
// normalize protocol to lower-case
protocol = protocol.toLocaleLowerCase();
if (this.findHandlerIndex(pathString, protocol) !== -1) {
throw new Error('You may only mount one handler per path/protocol combination.');
}
this.handlers.push({
'path': path,
'pathString': pathString,
'protocol': protocol,
'callback': callback
});
};
WebSocketRouter.prototype.unmount = function(path, protocol) {
var index = this.findHandlerIndex(this.pathToRegExp(path).toString(), protocol);
if (index !== -1) {
this.handlers.splice(index, 1);
}
else {
throw new Error('Unable to find a route matching the specified path and protocol.');
}
};
WebSocketRouter.prototype.findHandlerIndex = function(pathString, protocol) {
protocol = protocol.toLocaleLowerCase();
for (var i=0, len=this.handlers.length; i < len; i++) {
var handler = this.handlers[i];
if (handler.pathString === pathString && handler.protocol === protocol) {
return i;
}
}
return -1;
};
WebSocketRouter.prototype.pathToRegExp = function(path) {
if (typeof(path) === 'string') {
if (path === '*') {
path = /^.*$/;
}
else {
path = path.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
path = new RegExp('^' + path + '$');
}
}
return path;
};
WebSocketRouter.prototype.handleRequest = function(request) {
var requestedProtocols = request.requestedProtocols;
if (requestedProtocols.length === 0) {
requestedProtocols = ['____no_protocol____'];
}
// Find a handler with the first requested protocol first
for (var i=0; i < requestedProtocols.length; i++) {
var requestedProtocol = requestedProtocols[i].toLocaleLowerCase();
// find the first handler that can process this request
for (var j=0, len=this.handlers.length; j < len; j++) {
var handler = this.handlers[j];
if (handler.path.test(request.resourceURL.pathname)) {
if (requestedProtocol === handler.protocol ||
handler.protocol === '*')
{
var routerRequest = new WebSocketRouterRequest(request, requestedProtocol);
handler.callback(routerRequest);
return;
}
}
}
}
// If we get here we were unable to find a suitable handler.
request.reject(404, 'No handler is available for the given request.');
};
module.exports = WebSocketRouter;