Websocket
[VSoRC/.git] / node_modules / websocket / lib / WebSocketRouter.js
1 /************************************************************************
2  *  Copyright 2010-2015 Brian McKelvey.
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  ***********************************************************************/
16
17 var extend = require('./utils').extend;
18 var util = require('util');
19 var EventEmitter = require('events').EventEmitter;
20 var WebSocketRouterRequest = require('./WebSocketRouterRequest');
21
22 function WebSocketRouter(config) {
23     // Superclass Constructor
24     EventEmitter.call(this);
25
26     this.config = {
27         // The WebSocketServer instance to attach to.
28         server: null
29     };
30     if (config) {
31         extend(this.config, config);
32     }
33     this.handlers = [];
34
35     this._requestHandler = this.handleRequest.bind(this);
36     if (this.config.server) {
37         this.attachServer(this.config.server);
38     }
39 }
40
41 util.inherits(WebSocketRouter, EventEmitter);
42
43 WebSocketRouter.prototype.attachServer = function(server) {
44     if (server) {
45         this.server = server;
46         this.server.on('request', this._requestHandler);
47     }
48     else {
49         throw new Error('You must specify a WebSocketServer instance to attach to.');
50     }
51 };
52
53 WebSocketRouter.prototype.detachServer = function() {
54     if (this.server) {
55         this.server.removeListener('request', this._requestHandler);
56         this.server = null;
57     }
58     else {
59         throw new Error('Cannot detach from server: not attached.');
60     }
61 };
62
63 WebSocketRouter.prototype.mount = function(path, protocol, callback) {
64     if (!path) {
65         throw new Error('You must specify a path for this handler.');
66     }
67     if (!protocol) {
68         protocol = '____no_protocol____';
69     }
70     if (!callback) {
71         throw new Error('You must specify a callback for this handler.');
72     }
73
74     path = this.pathToRegExp(path);
75     if (!(path instanceof RegExp)) {
76         throw new Error('Path must be specified as either a string or a RegExp.');
77     }
78     var pathString = path.toString();
79
80     // normalize protocol to lower-case
81     protocol = protocol.toLocaleLowerCase();
82
83     if (this.findHandlerIndex(pathString, protocol) !== -1) {
84         throw new Error('You may only mount one handler per path/protocol combination.');
85     }
86
87     this.handlers.push({
88         'path': path,
89         'pathString': pathString,
90         'protocol': protocol,
91         'callback': callback
92     });
93 };
94 WebSocketRouter.prototype.unmount = function(path, protocol) {
95     var index = this.findHandlerIndex(this.pathToRegExp(path).toString(), protocol);
96     if (index !== -1) {
97         this.handlers.splice(index, 1);
98     }
99     else {
100         throw new Error('Unable to find a route matching the specified path and protocol.');
101     }
102 };
103
104 WebSocketRouter.prototype.findHandlerIndex = function(pathString, protocol) {
105     protocol = protocol.toLocaleLowerCase();
106     for (var i=0, len=this.handlers.length; i < len; i++) {
107         var handler = this.handlers[i];
108         if (handler.pathString === pathString && handler.protocol === protocol) {
109             return i;
110         }
111     }
112     return -1;
113 };
114
115 WebSocketRouter.prototype.pathToRegExp = function(path) {
116     if (typeof(path) === 'string') {
117         if (path === '*') {
118             path = /^.*$/;
119         }
120         else {
121             path = path.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
122             path = new RegExp('^' + path + '$');
123         }
124     }
125     return path;
126 };
127
128 WebSocketRouter.prototype.handleRequest = function(request) {
129     var requestedProtocols = request.requestedProtocols;
130     if (requestedProtocols.length === 0) {
131         requestedProtocols = ['____no_protocol____'];
132     }
133
134     // Find a handler with the first requested protocol first
135     for (var i=0; i < requestedProtocols.length; i++) {
136         var requestedProtocol = requestedProtocols[i].toLocaleLowerCase();
137
138         // find the first handler that can process this request
139         for (var j=0, len=this.handlers.length; j < len; j++) {
140             var handler = this.handlers[j];
141             if (handler.path.test(request.resourceURL.pathname)) {
142                 if (requestedProtocol === handler.protocol ||
143                     handler.protocol === '*')
144                 {
145                     var routerRequest = new WebSocketRouterRequest(request, requestedProtocol);
146                     handler.callback(routerRequest);
147                     return;
148                 }
149             }
150         }
151     }
152
153     // If we get here we were unable to find a suitable handler.
154     request.reject(404, 'No handler is available for the given request.');
155 };
156
157 module.exports = WebSocketRouter;