39c8b14f |
1 | /************************************************************************ |
2 | * Copyright 2010-2011 Worlize Inc. |
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 WebSocketRequest = require('./WebSocketRequest'); |
21 | var Constants = require('./Constants'); |
22 | |
23 | var WebSocketServer = function WebSocketServer(config) { |
24 | this._handlers = { |
25 | upgrade: this.handleUpgrade.bind(this), |
26 | requestAccepted: this.handleRequestAccepted.bind(this) |
27 | }; |
28 | this.connections = []; |
29 | if (config) { |
30 | this.mount(config); |
31 | } |
32 | }; |
33 | |
34 | util.inherits(WebSocketServer, EventEmitter); |
35 | |
36 | WebSocketServer.prototype.mount = function(config) { |
37 | this.config = { |
38 | // The http server instance to attach to. Required. |
39 | httpServer: null, |
40 | |
41 | // 64KiB max frame size. |
42 | maxReceivedFrameSize: 0x10000, |
43 | |
44 | // 1MiB max message size, only applicable if |
45 | // assembleFragments is true |
46 | maxReceivedMessageSize: 0x100000, |
47 | |
48 | // Outgoing messages larger than fragmentationThreshold will be |
49 | // split into multiple fragments. |
50 | fragmentOutgoingMessages: true, |
51 | |
52 | // Outgoing frames are fragmented if they exceed this threshold. |
53 | // Default is 16KiB |
54 | fragmentationThreshold: 0x4000, |
55 | |
56 | // If true, the server will automatically send a ping to all |
57 | // clients every 'keepaliveInterval' milliseconds. The timer is |
58 | // reset on any received data from the client. |
59 | keepalive: true, |
60 | |
61 | // The interval to send keepalive pings to connected clients if the |
62 | // connection is idle. Any received data will reset the counter. |
63 | keepaliveInterval: 20000, |
64 | |
65 | // If true, the server will consider any connection that has not |
66 | // received any data within the amount of time specified by |
67 | // 'keepaliveGracePeriod' after a keepalive ping has been sent. |
68 | // Ignored if keepalive is false. |
69 | dropConnectionOnKeepaliveTimeout: true, |
70 | |
71 | // The amount of time to wait after sending a keepalive ping before |
72 | // closing the connection if the connected peer does not respond. |
73 | // Ignored if keepalive is false. |
74 | keepaliveGracePeriod: 10000, |
75 | |
76 | // If true, fragmented messages will be automatically assembled |
77 | // and the full message will be emitted via a 'message' event. |
78 | // If false, each frame will be emitted via a 'frame' event and |
79 | // the application will be responsible for aggregating multiple |
80 | // fragmented frames. Single-frame messages will emit a 'message' |
81 | // event in addition to the 'frame' event. |
82 | // Most users will want to leave this set to 'true' |
83 | assembleFragments: true, |
84 | |
85 | // If this is true, websocket connections will be accepted |
86 | // regardless of the path and protocol specified by the client. |
87 | // The protocol accepted will be the first that was requested |
88 | // by the client. Clients from any origin will be accepted. |
89 | // This should only be used in the simplest of cases. You should |
90 | // probably leave this set to 'false' and inspect the request |
91 | // object to make sure it's acceptable before accepting it. |
92 | autoAcceptConnections: false, |
93 | |
94 | // The Nagle Algorithm makes more efficient use of network resources |
95 | // by introducing a small delay before sending small packets so that |
96 | // multiple messages can be batched together before going onto the |
97 | // wire. This however comes at the cost of latency, so the default |
98 | // is to disable it. If you don't need low latency and are streaming |
99 | // lots of small messages, you can change this to 'false' |
100 | disableNagleAlgorithm: true, |
101 | |
102 | // The number of milliseconds to wait after sending a close frame |
103 | // for an acknowledgement to come back before giving up and just |
104 | // closing the socket. |
105 | closeTimeout: 5000 |
106 | }; |
107 | extend(this.config, config); |
108 | |
109 | // this.httpServer = httpServer; |
110 | // if (typeof(pathRegExp) === 'string') { |
111 | // pathRegExp = new RegExp('^' + pathRegExp + '$'); |
112 | // } |
113 | // this.pathRegExp = pathRegExp; |
114 | // this.protocol = protocol; |
115 | if (this.config.httpServer) { |
116 | this.config.httpServer.on('upgrade', this._handlers.upgrade); |
117 | } |
118 | else { |
119 | throw new Error("You must specify an httpServer on which to mount the WebSocket server.") |
120 | } |
121 | }; |
122 | |
123 | WebSocketServer.prototype.unmount = function() { |
124 | this.config.httpServer.removeListener('upgrade', this._handlers.upgrade); |
125 | }; |
126 | |
127 | WebSocketServer.prototype.closeAllConnections = function() { |
128 | this.connections.forEach(function(connection) { |
129 | connection.close(); |
130 | }); |
131 | }; |
132 | |
133 | WebSocketServer.prototype.broadcast = function(data) { |
134 | if (Buffer.isBuffer(data)) { |
135 | this.broadcastBytes(data); |
136 | } |
137 | else if (typeof(data.toString) === 'function') { |
138 | this.broadcastUTF(data); |
139 | } |
140 | }; |
141 | |
142 | WebSocketServer.prototype.broadcastUTF = function(utfData) { |
143 | this.connections.forEach(function(connection) { |
144 | connection.sendUTF(utfData); |
145 | }); |
146 | }; |
147 | |
148 | WebSocketServer.prototype.broadcastBytes = function(binaryData) { |
149 | this.connections.forEach(function(connection) { |
150 | connection.sendBytes(binaryData); |
151 | }); |
152 | }; |
153 | |
154 | WebSocketServer.prototype.shutDown = function() { |
155 | this.unmount(); |
156 | this.closeAllConnections(); |
157 | }; |
158 | |
159 | WebSocketServer.prototype.handleUpgrade = function(request, socket, head) { |
160 | var wsRequest = new WebSocketRequest(socket, request, this.config); |
161 | try { |
162 | wsRequest.readHandshake(); |
163 | } |
164 | catch(e) { |
165 | wsRequest.reject( |
166 | e.httpCode ? e.httpCode : 400, |
167 | e.message, |
168 | e.headers |
169 | ); |
170 | if (Constants.DEBUG) { |
171 | console.error((new Date()) + " WebSocket: Invalid handshake: " + e.message); |
172 | } |
173 | return; |
174 | } |
175 | |
176 | wsRequest.once('requestAccepted', this._handlers.requestAccepted); |
177 | |
178 | if (!this.config.autoAcceptConnections && this.listeners('request').length > 0) { |
179 | this.emit('request', wsRequest); |
180 | } |
181 | else if (this.config.autoAcceptConnections) { |
182 | wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin); |
183 | } |
184 | else { |
185 | wsRequest.reject(404, "No handler is configured to accept the connection."); |
186 | } |
187 | }; |
188 | |
189 | WebSocketServer.prototype.handleRequestAccepted = function(connection) { |
190 | var self = this; |
191 | connection.once('close', function(closeReason, description) { |
192 | self.handleConnectionClose(connection, closeReason, description); |
193 | }); |
194 | this.connections.push(connection); |
195 | this.emit('connect', connection); |
196 | }; |
197 | |
198 | WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) { |
199 | var index = this.connections.indexOf(connection); |
200 | if (index !== -1) { |
201 | this.connections.splice(index, 1); |
202 | } |
203 | this.emit('close', connection, closeReason, description); |
204 | }; |
205 | |
206 | module.exports = WebSocketServer; |