/************************************************************************ * 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;