diff options
author | Max Romanov <max.romanov@nginx.com> | 2019-08-20 16:32:05 +0300 |
---|---|---|
committer | Max Romanov <max.romanov@nginx.com> | 2019-08-20 16:32:05 +0300 |
commit | e291841b3379f8787a10ad4f91e4aeae2ae323a4 (patch) | |
tree | 49a2f4629e5b8d6cd48f7436d7eeba4c99905675 /src/nodejs/unit-http/websocket_router.js | |
parent | e501c74ddceab86e48c031ca9b5e154f52dcdae0 (diff) | |
download | unit-e291841b3379f8787a10ad4f91e4aeae2ae323a4.tar.gz unit-e291841b3379f8787a10ad4f91e4aeae2ae323a4.tar.bz2 |
Node.js: introducing websocket support.
Diffstat (limited to 'src/nodejs/unit-http/websocket_router.js')
-rw-r--r-- | src/nodejs/unit-http/websocket_router.js | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/src/nodejs/unit-http/websocket_router.js b/src/nodejs/unit-http/websocket_router.js new file mode 100644 index 00000000..4efa35d2 --- /dev/null +++ b/src/nodejs/unit-http/websocket_router.js @@ -0,0 +1,157 @@ +/************************************************************************ + * 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; |