├── .gitignore
├── LICENSE.md
├── Makefile
├── README.md
├── docs
├── index.html
└── style.css
├── lib
├── _events.js
├── _util.js
├── lang
│ └── mixin.js
└── ws
│ ├── connection.js
│ ├── manager.js
│ ├── parser.js
│ └── server.js
├── package.json
├── samples
└── handshake-packets
├── test
├── common.js
├── fixtures
│ └── .gitignore
├── manual
│ ├── chat-server.js
│ ├── chat.html
│ ├── client.html
│ ├── dev-server.js
│ ├── echo-server.js
│ ├── ssl
│ │ ├── cert.key
│ │ └── cert.pem
│ └── test-https-upgrade.js
├── pummel
│ └── .gitignore
└── simple
│ └── .gitignore
└── tools
└── release.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2010 Micheil Smith.
2 |
3 | All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to
7 | deal in the Software without restriction, including without limitation the
8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | sell copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | release:
2 | node tools/release.js
3 |
4 | publish: release
5 | npm publish .
6 |
7 | test:
8 | @echo "Running Simple Tests"
9 | @find -f test/simple/test-*.js | xargs -n 1 -t node
10 |
11 | test-all: test
12 | @echo "Running All Tests"
13 | @find -f test/pummel/test-*.js | xargs -n 1 -t node
14 |
15 | benchmark:
16 | @echo "Running Benchmarks"
17 | @find -f benchmark/simple/*.js | xargs -n 1 -t node
18 |
19 | doc:
20 | node tools/doctool/doctool.js
21 |
22 | GJSLINT = gjslint --unix_mode --strict --nojsdoc
23 |
24 | lint:
25 | @$(GJSLINT) -r lib/
26 |
27 | .PHONY: release publish test test-all benchmark doc lint
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-websocket-server #
2 |
3 | This is a server for drafts [75](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75) and [76](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76) of the WebSocket Protocol.
4 |
5 | ## Getting help:
6 |
7 | If you have an issues with this server, please check the [issue tracker](http://github.com/miksago/node-websocket-server/issues).
8 |
9 | - If you have an issue with a stacktrace / bug report, please submit an issue in the issue tracker, make sure to include details as to how to reproduce the issue.
10 | - If you have a feature request, create an issue on the bug tracker and specifically state that it is a feature request, also send an email to the mailing list referencing this feature request, discussion on feature requests should be done in the issue tracker.
11 | - If you need general help or want to share what you're using this project in, join & email the mailing list.
12 |
13 |
14 | ## Mailing List:
15 |
16 | We have a mailing list, it is hosted on google groups: [http://groups.google.com/group/node-websocket-server](http://groups.google.com/group/node-websocket-server)
17 |
18 | ## Documentation (outdated)
19 |
20 | See http://static.brandedcode.com/nws-docs/ for some slightly outdated
21 | documentation.
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
44 |
Node WebSocket Server
45 |
46 |
Hey there, this is an early release for Node Knockout, there'll be full documentation coming really soon, if anything's missing, try the source code.
47 |
48 |
49 | #
50 | Node WebSocket Server is a near specification compliant implementation of the server-side WebSocket Protocol. It is built on top of Node.js as a third-party module, and is designed to support various versions of the WebSocket protocol – which is currently still an IETF Draft.
51 |
52 |
53 | #
54 | The WebSockets protocol is a way of the browser providing a bi-directional, full-duplex communication channel which is accessible through the DOM.
55 |
56 |
57 |
58 |
59 |
Installation
60 |
61 | #
62 | Node WebSocket Server can be easily installed via NPM or cloned from github. This module requires Node to be compiled with SSL support (which is by default enabled, if it's available), the reason for the dependency on SSL being available in Node.js is to enable Draft 76 handshaking and Secure WebSocket connections.
63 |
64 |
With Node Package Manager
65 |
66 | #
67 | For the latest stable build:
68 |
69 |
npm install websocket-server
70 |
71 | #
72 | Or, alternatively, you can run with the latest bleeding edge build with:
73 |
74 |
npm install websocket-server@latest
75 |
76 |
With git submodules
77 |
78 | #
79 | Installing Node WebSocket Server as a git submodule is a recommended route for installing if you plan on deploying your application and don't want the hassle of having to check that both production and development environments are running the same module versions. Another benefit of installing as a submodule, is that the websocket server is then packaged with your code.
80 |
81 |
82 | #
83 | The installation is as follows:
84 |
85 |
86 | // Assumption that you are in a working git repository:
87 | $ mkdir vendor
88 | $ cd vendor
89 | $ git submodule add git://github.com/miksago/node-websocket-server.git websocket-server
90 | Initialized empty Git repository in ./websocket-server/.git/
91 | // ...repository gets cloned...
92 | $ git commit -m "Adding websocket-server submodule"
93 |
94 |
95 | #
96 | Later on, in order to update your projects copy of Node WebSocket Server, simply do:
97 |
98 |
$ git submodule update
99 |
100 | #
101 | For your collaborators, they can simply do a git pull
then a git submodule init
then a git submodule update
. You can learn more about Git Submodules from Chapter 6.6 of the Pro Git book, which you can read online.
102 |
103 |
104 |
Contributing
105 |
106 | #
107 | In order to contribute to this project, I ask that you follow these steps:
108 |
109 |
110 | - Fork the project.
111 | - Make your feature addition or bug fix.
112 |
113 | - Commit, do not mess with rakefile, version, or history.
114 | - Send me a pull request. Bonus points for topic branches.
115 |
116 |
117 |
Basic Usage
118 |
119 |
120 | #
121 | The API to Node WebSocket Server is fairly simple, and stays consistent with the Node.js API's you may already be familiar with. Firstly, there are three main parts to the server, these are: The constructor, the connection instance, and the connection manager. Each of these parts interact with each other, and end up providing a clean API with which you write your server-side logic.
122 |
123 |
124 | #
125 | An example server, that simply echoes anything that a client sends to all other connections, looks like the following:
126 |
127 |
128 | var
ws =
require("websocket-server"
);
129 |
130 | var
server =
ws.createServer();
131 |
132 | server.addListener("connection"
, function
(connection){
133 | connection.addListener("message"
, function
(msg){
134 | server.send
(msg);
135 | });
136 | });
137 |
138 | server.listen
(8080
);
139 |
140 |
141 |
142 | #
143 | So what does the above code actually do? First, we need to require in the websocket-server library — If you have installed this module via NPM then you can require it exactly like the above does. Next, we create a server instance, by default, this server will automatically choose which version of the protocol to use when communicating with the client and it will also act as a standard http.Server
instance.
144 |
145 |
146 |
147 | #
148 | After we have a instance of ws.Server
, which ws.createServer
returns, we can proceed to add event listeners to the various events it emits. We use the same API as that which node's Event.EventEmitter provides to do so. In this case, we only bind to one event on the server, the Connection event. Each time a user successfully connects to the server the connection is emitted. We then watch for the message
event on the emitted connection, this event occurs every time a user sends a message to the server.
149 |
150 |
151 |
API Documentation
152 |
153 | #
154 | The API to Node WebSocket Server is split into three main parts:
155 |
156 | - The require-time module, that lets you create a new websocket server
157 | - The connection instance
158 | - The connection manager
159 |
160 |
161 |
162 | Module Methods
163 |
164 |
165 | -
166 | #
167 |
ws.createServer( [options] );
168 |
169 | - Returns a new ws.Server instance.
170 | - This method passes it's arguments through to the ws.Server constructor, see constructor documentation for description of
options
.
171 | -
172 | #
173 |
ws.Server( [options] );
174 |
175 | - The constructor for a new WebSocket Server.
176 | options
is an object of options to pass to the server, these are as follows:
177 | -
178 |
179 | - debug:
180 | - Toggles the printing of debug messages from each component to
stdout
.
181 | Type: Boolean
182 |
183 | - version:
184 | - The WebSocket protocol version the server will accept connections from, connections not matching this version will be rejected.
185 | Type: String
186 | Values: "auto", "draft76", "draft75"
187 | Default: "auto"
188 |
189 | - storage:
190 | - Defines a storage model to be implemented as a member of each connection. If the value is false, no storage will be created. If the value is true, then an in memory store will be used. The value may also be an Object or Constructor, see the section on Connection.storage for more information.
191 | Type: Boolean, Object, Constructor
192 |
193 | - server:
194 | - Defines an existing http.Server instance for the WebSocket Server to bind to.
195 | Type: http.Server
196 |
197 |
203 |
204 | - origin:
205 | - A string or array of domains from which the server should accept connections.
206 | Type: String, String[]
207 | Note: This is not fully implemented
208 |
209 | - subprotocol:
210 | - A string or array of subprotocols which the server will use when communicating with clients. The server and client should negotiate which subprotocol to use if an Array of strings is passed.
211 | Type: String, String[]
212 | Note: This is not fully implemented
213 |
214 |
215 |
216 |
217 |
Server Instances
218 |
Throughout this section, the variable server
is an instant of a ws.Server.
219 |
220 | server.send( client_id, message )
221 | - Sends
message
to the client with id
of client_id
.
222 | client_id: Integer
223 | message: String
224 |
225 | server.broadcast( message )
226 | - Sends
message
to all connected clients.
227 | message: String
228 |
229 | server.listen( port, [host] )
230 | - Same as the http.Server listen method.
231 |
232 | server.setSecure( credentials )
233 | - Switches to using SSL to encrypt connections with the supplied
credentials
. Requires the client to connect using wss://
protocol, instead of the standard ws://
.
234 | credentials: crypto.Credentials
235 |
236 |
237 | server.close()
238 | - Same as the http.Server close method.
239 |
240 | Inherited Methods
241 | The following methods are inherited from Events.eventEmitter.
242 |
243 | server.addListener( event, callback )
244 | - Adds a listener for the specified event that is emitted from the server.
245 | event: String
246 | callback: Function
247 |
248 | server.removeListener( event, callback )
249 | - Remove a listener from the specified event that is emitted from the server.
250 | event: String
251 | callback: Function
252 |
253 | server.removeAllListeners( event )
254 | - Removes all listeners from the specified event that is emitted from the server.
255 | event: String
256 |
257 |
258 |
259 |
Events
260 |
A Server instance can emit various events during it's life cycle. The WebSocket Server will also emit each of the http.Server events, except "connection" and "close" which are used by the WebSocket Server. These events are described as follows:
261 |
262 | - connection
263 | - Emitted whenever a client connects to the server successfully.
264 | client: Connection
265 |
266 | - close
267 | - Emitted whenever a client disconnects from the server.
268 | client: Connection
269 |
270 | - shutdown
271 | - Emitted when the server is closed, this event is echoed from http.Server
272 |
273 |
274 |
Connection Instances
275 |
276 | #
277 | Each time a client connects to the server
, a new connection instance is created and registered with the connection manager.
278 |
279 |
280 | connection.id
281 | - A String, which is a unique identifier for the connection, this is based on the connection's remotePort.
282 |
283 | connection.state
284 | - An Integer representing the current state of the connection, states are as follows:
285 |
286 | - 0. unknown
287 | - 1. opening
288 | - 2. waiting
289 | - 3. handshaking
290 | - 4, connected
291 | - 5. closing
292 | - 6. closed
293 |
294 | - State changes can be listened for using the stateChange event.
295 |
296 | connection.version
297 | - A String, representing the protocol version the client is connecting from. Either draft75 or draft76.
298 |
299 | connection.headers
300 | - An Object, these are the HTTP headers that were sent with the initial WebSocket Handshake. See http.ServerRequest for details.
301 |
302 | connection.storage
303 | - This is an instance of your chosen Storage provider. By default, it is a in memory store with an API similar to redis. This can be disabled, in which case it will be undefined.
304 |
305 |
306 |
Methods
307 |
308 |
309 | connection.send( data )
310 | - Sends
data
from the current connection to all other connections on the server, including the sending connection. This method is aliased as connection.write
311 | data: String
312 |
313 | connection.broadcast( data )
314 | - Sends
data
from the current connection to all other connections on the server, excluding the sending connection.
315 | data: String
316 |
317 | connection.close()
318 | - Closes the current connection and emits the
"close"
.
319 |
320 | connection.reject( [reason] )
321 | - Emits the
"rejected"
event, then proceeds to close the connection using connection.close()
. The reason
is used only for logging purposes.
322 | reason: String
323 |
324 |
325 |
Inherited Methods
326 |
The following methods are inherited from Events.eventEmitter.
327 |
328 |
329 | connection.addListener( event, callback )
330 | - Adds a listener for the specified event that is emitted from the connection.
331 | event: String
332 | callback: Function
333 |
334 | connection.removeListener( event, callback )
335 | - Remove a listener from the specified event that is emitted from the connection.
336 | event: String
337 | callback: Function
338 |
339 | connection.removeAllListeners( event )
340 | - Removes all listeners from the specified event that is emitted from the connection.
341 | event: String
342 |
343 |
344 |
Events
345 |
Each connection comes with the following events that may be emitted through out it's life-cycle.
346 |
347 | - close
348 | - Emitted whenever a client or server closes the connection.
349 |
350 | - rejected
351 | - Emitted whenever the server rejects the connection.
352 |
353 | - message: function( data )
354 | - Emitted whenever a client sends a message to the server successfully.
355 |
356 | - stateChange: function( new_state, old_state )
357 | - Emitted whenever the internal connection status changes, this equates to the various of the Connection life-cycle.
358 |
359 |
360 |
361 |
Manager Instances
362 |
363 | #
364 | ...
365 |
366 |
367 | server.manager.length
368 | - Number of currently attached connections. This property is not writable, only readable.
369 |
370 | server.manager.find( id, callback, [thisArg] )
371 | - Finds the Connection Instance with the given
id
. If a match is found, the call
property of callback
is called with thisArg
as the value of this, with the only argument being the pointer to the Connection Instance that was matched.
372 |
373 | server.manager.forEach( callback, [thisArg] )
374 | - ... description ...
375 |
376 | server.manager.map( callback, [thisArg] )
377 | - ... description ...
378 |
379 | server.manager.filter( callback, [thisArg] )
380 | - ... description ...
381 |
382 |
383 |
Further Reading
384 |
385 |
386 |
387 |
--------------------------------------------------------------------------------
/docs/style.css:
--------------------------------------------------------------------------------
1 | /*--------------------- Layout and Typography ----------------------------*/
2 | body {
3 | // font-family: "Helvetica Neue", Helvetica, FreeSans, Arial, sans-serif;
4 | font-family: Georgia, FreeSerif, Times, serif;
5 | font-size: 0.9375em;
6 | line-height: 1.4667em;
7 | color: #222;
8 | margin: 0; padding: 0;
9 | }
10 | a {
11 | color: #0050c0;
12 | text-decoration: underline;
13 | }
14 | a:visited {
15 | color: #b950b7;
16 | text-decoration: underline;
17 | }
18 | a:hover, a:focus {
19 | text-decoration: none;
20 | }
21 |
22 | code a:hover {
23 | background: none;
24 | color: #b950b7;
25 | }
26 |
27 | .notice {
28 | display: block;
29 | padding: 1em;
30 | margin: 1.4667em 0 2.9334em;
31 | background:#FFF6BF;
32 | color:#514721;
33 | border:1px solid #FFD324;
34 | }
35 | .notice p {
36 | margin: 0;
37 | }
38 |
39 | ul.plain {
40 | list-style: none;
41 | }
42 |
43 | abbr {
44 | border-bottom: 1px dotted #454545;
45 | }
46 |
47 | p {
48 | margin: 0 0 1.4667em 0;
49 | position: relative;
50 | text-rendering: optimizeLegibility;
51 | }
52 |
53 | ol, ul, dl {
54 | margin: 0 0 1em 0;
55 | padding: 0;
56 | }
57 |
58 | ul, ol {
59 | margin-left: 2em;
60 | }
61 |
62 |
63 | dl dt {
64 | position: relative;
65 | margin: 1.5em 0 0;
66 | }
67 |
68 | dl dd {
69 | position: relative;
70 | margin: 0 1em 0;
71 | }
72 |
73 | dd + dt.pre {
74 | margin-top: 1.6em;
75 | }
76 |
77 | h1, h2, h3, h4, h5, h6 {
78 | font-family: Georgia, FreeSerif, Times, serif;
79 | color: #000;
80 | text-rendering: optimizeLegibility;
81 | position: relative;
82 | }
83 |
84 | h1 {
85 | font-size: 2.55em;
86 | line-height: 1.375em;
87 | margin: 1.25em -0.5em 1.3em;
88 | padding: 0 0.5em 0.225em;
89 | border-bottom: 1px solid #ccc;
90 | }
91 |
92 | h2 {
93 | font-size: 1.8em;
94 | line-height: 1.227em;
95 | margin: 2.125em 0 0.75em;
96 | font-style: italic;
97 | }
98 |
99 | h3 {
100 | font-size: 1.6em;
101 | line-height: 1.0909em;
102 | margin: 1.6em 0 1em;
103 | }
104 |
105 | h4 {
106 | font-size: 1.3em;
107 | line-height: 1.1282em;
108 | margin: 2.2em 0 1.25em;
109 | }
110 |
111 | h5 {
112 | font-size: 1.125em;
113 | line-height: 1.4em;
114 | }
115 |
116 | h6 {
117 | font-size: 1em;
118 | line-height: 1.4667em;
119 | }
120 |
121 | pre, tt, code {
122 | font-size: 0.95em;
123 | line-height: 1.5438em;
124 | font-family: Monaco, Consolas, "Lucida Console", monospace;
125 | margin: 0; padding: 0;
126 | }
127 |
128 | .pre {
129 | font-family: Monaco, Consolas, "Lucida Console", monospace;
130 | line-height: 1.5438em;
131 | font-size: 0.95em;
132 | }
133 |
134 | pre {
135 | padding: 2em 1.6em 2em 1.2em;
136 | vertical-align: top;
137 | background: #f8f8f8;
138 | border: 1px solid #e8e8e8;
139 | border-width: 1px 1px 1px 6px;
140 | margin: -0.5em 0 1.1em;
141 | }
142 |
143 | pre + h3 {
144 | margin-top: 2.225em;
145 | }
146 |
147 | code.pre {
148 | white-space: pre;
149 | }
150 |
151 | #container {
152 | position: relative;
153 | padding: 6em 6em 1200px;
154 | width: 50em;
155 | text-align: justify;
156 | }
157 |
158 | #jump_to, #jump_page {
159 | background: white;
160 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
161 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
162 | font: 10px Arial;
163 | text-transform: uppercase;
164 | cursor: pointer;
165 | text-align: right;
166 | }
167 | #jump_to, #jump_wrapper {
168 | position: fixed;
169 | right: 0; top: 0;
170 | padding: 5px 10px;
171 | }
172 | #jump_wrapper {
173 | padding: 0;
174 | display: none;
175 | }
176 | #jump_to:hover #jump_wrapper {
177 | display: block;
178 | }
179 | #jump_page {
180 | padding: 5px 0 3px;
181 | margin: 0 0 25px 25px;
182 | }
183 | #jump_page .source {
184 | display: block;
185 | padding: 5px 10px;
186 | text-decoration: none;
187 | border-top: 1px solid #eee;
188 | }
189 | #jump_page .source:hover {
190 | background: #f5f5ff;
191 | }
192 | #jump_page .source:first-child {
193 | }
194 |
195 | p tt, p code {
196 | background: #f8f8ff;
197 | border: 1px solid #dedede;
198 | padding: 0 0.2em;
199 | }
200 |
201 | a.octothorpe {
202 | text-decoration: none;
203 | color: #777;
204 | position: absolute;
205 | top: 0; left: -1.4em;
206 | padding: 1px 2px;
207 | opacity: 0;
208 | -webkit-transition: opacity 0.2s linear;
209 | }
210 | p:hover > a.octothorpe,
211 | dt:hover > a.octothorpe,
212 | dd:hover > a.octothorpe,
213 | h1:hover > a.octothorpe,
214 | h2:hover > a.octothorpe,
215 | h3:hover > a.octothorpe,
216 | h4:hover > a.octothorpe,
217 | h5:hover > a.octothorpe,
218 | h6:hover > a.octothorpe {
219 | opacity: 1;
220 | }
221 |
222 |
223 |
224 | /*---------------------- Syntax Highlighting -----------------------------*/
225 | td.linenos { background-color: #f0f0f0; padding-right: 10px; }
226 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
227 | body .hll { background-color: #ffffcc }
228 | body .c { color: #408080; font-style: italic } /* Comment */
229 | body .err, body .e { color: #FF0000 } /* Error */
230 | body .k { color: #ff6922 } /* Keyword */
231 | body .o { color: #666666 } /* Operator */
232 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
233 | body .cp { color: #BC7A00 } /* Comment.Preproc */
234 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */
235 | body .cs { color: #408080; font-style: italic } /* Comment.Special */
236 | body .gd { color: #A00000 } /* Generic.Deleted */
237 | body .ge { font-style: italic } /* Generic.Emph */
238 | body .gr { color: #FF0000 } /* Generic.Error */
239 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
240 | body .gi { color: #00A000 } /* Generic.Inserted */
241 | body .go { color: #808080 } /* Generic.Output */
242 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
243 | body .gs { font-weight: bold } /* Generic.Strong */
244 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
245 | body .gt { color: #0040D0 } /* Generic.Traceback */
246 | body .kc { color: #954121 } /* Keyword.Constant */
247 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
248 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
249 | body .kp { color: #954121 } /* Keyword.Pseudo */
250 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
251 | body .kt { color: #B00040 } /* Keyword.Type */
252 | body .m { color: #009999 } /* Literal.Number */
253 | body .s { color: #00af5c } /* Literal.String */
254 | body .na { color: #7D9029 } /* Name.Attribute */
255 | body .nb { color: #954121 } /* Name.Builtin */
256 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
257 | body .no { color: #880000 } /* Name.Constant */
258 | body .nd { color: #AA22FF } /* Name.Decorator */
259 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */
260 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
261 | body .nf { color: #b950b7 } /* Name.Function */
262 | body .nl { color: #A0A000 } /* Name.Label */
263 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
264 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */
265 | body .nv { color: #19469D } /* Name.Variable */
266 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
267 | body .w { color: #bbbbbb } /* Text.Whitespace */
268 | body .mf { color: #666666 } /* Literal.Number.Float */
269 | body .mh { color: #666666 } /* Literal.Number.Hex */
270 | body .mi { color: #666666 } /* Literal.Number.Integer */
271 | body .mo { color: #666666 } /* Literal.Number.Oct */
272 | body .sb { color: #219161 } /* Literal.String.Backtick */
273 | body .sc { color: #219161 } /* Literal.String.Char */
274 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
275 | body .s2 { color: #219161 } /* Literal.String.Double */
276 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
277 | body .sh { color: #219161 } /* Literal.String.Heredoc */
278 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
279 | body .sx { color: #954121 } /* Literal.String.Other */
280 | body .sr { color: #BB6688 } /* Literal.String.Regex */
281 | body .s1 { color: #219161 } /* Literal.String.Single */
282 | body .ss { color: #19469D } /* Literal.String.Symbol */
283 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */
284 | body .vc { color: #19469D } /* Name.Variable.Class */
285 | body .vg { color: #19469D } /* Name.Variable.Global */
286 | body .vi { color: #19469D } /* Name.Variable.Instance */
287 | body .il { color: #666666 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/lib/_events.js:
--------------------------------------------------------------------------------
1 | var util = require('./_util'),
2 | events = require('events');
3 |
4 | EventEmitter = exports.EventEmitter = function() {
5 | events.EventEmitter.call(this);
6 | };
7 |
8 | util.inherits(EventEmitter, events.EventEmitter);
9 |
10 | EventEmitter.prototype.emit = function(type) {
11 | if (type !== 'newListener' && (!this._events || !this._events[type]) &&
12 | this._bubbleTarget && this._bubbleTarget[type]) {
13 | this._bubbleTarget[type].emit.apply(this._bubbleTarget[type], arguments);
14 | } else {
15 | events.EventEmitter.prototype.emit.apply(this, arguments);
16 | }
17 | };
18 |
19 | EventEmitter.prototype.bubbleEvent = function(type, target) {
20 | if (!this._bubbleTarget) this._bubbleTarget = {};
21 | this._bubbleTarget[type] = target;
22 | };
23 |
24 | EventEmitter.prototype.removeBubbleEvent = function(type) {
25 | delete this._bubbleTarget[type];
26 | };
27 |
--------------------------------------------------------------------------------
/lib/_util.js:
--------------------------------------------------------------------------------
1 | module.exports = require(process.binding('natives').util ? 'util' : 'sys');
2 |
--------------------------------------------------------------------------------
/lib/lang/mixin.js:
--------------------------------------------------------------------------------
1 | module.exports = function Mixin(target, source) {
2 | if (source) {
3 | for (var key, keys = Object.keys(source), l = keys.length; l--; ) {
4 | key = keys[l];
5 |
6 | if (source.hasOwnProperty(key)) {
7 | target[key] = source[key];
8 | }
9 | }
10 | }
11 | return target;
12 | };
13 |
--------------------------------------------------------------------------------
/lib/ws/connection.js:
--------------------------------------------------------------------------------
1 | /*-----------------------------------------------
2 | Requirements:
3 | -----------------------------------------------*/
4 | var debug = function() {};
5 |
6 | var util = require('../_util'),
7 | events = require('events'),
8 | Url = require('url'),
9 | Buffer = require('buffer').Buffer,
10 | Crypto = require('crypto'),
11 | Constants = require('constants');
12 |
13 | var _events = require('../_events');
14 | var Mixin = require('../lang/mixin');
15 |
16 | var CLOSE_FRAME = new Buffer(2);
17 |
18 | CLOSE_FRAME[0] = 0xFF;
19 | CLOSE_FRAME[1] = 0x00;
20 |
21 | /*-----------------------------------------------
22 | The Connection:
23 | -----------------------------------------------*/
24 | module.exports = Connection;
25 |
26 | // Our connection instance:
27 | function Connection(manager, options, req, socket, upgradeHead) {
28 | var _firstFrame, connection = this;
29 |
30 | _events.EventEmitter.call(this);
31 |
32 | this._req = req;
33 | this._socket = socket;
34 | this._manager = manager;
35 | this.id = manager.createId(socket.remotePort);
36 |
37 | this._options = Mixin({
38 | version: 'auto', // String: Value must be either: draft75, draft76, auto
39 | origin: '*', // String,Array: Valid connection origins.
40 | subprotocol: '*', // String,Array: Valid connection subprotocols.
41 | debug: true
42 | }, options);
43 |
44 | if (connection._options.debug) {
45 | debug = function() {
46 | util.error(
47 | '\033[90mWS: ' +
48 | Array.prototype.join.call(arguments, ' ') +
49 | '\033[39m'
50 | );
51 | process.stdout.flush();
52 | };
53 | }
54 |
55 | Object.defineProperties(this, {
56 | version: {
57 | get: function() {
58 | if (req.headers['sec-websocket-key1'] &&
59 | req.headers['sec-websocket-key2']) {
60 | return 'draft76';
61 | }
62 | return 'draft75';
63 | }
64 | }
65 | });
66 |
67 | // Close timeout, for browsers that don't send the close packet.
68 | connection._closeTimer = undefined;
69 |
70 | // Set the initial connecting state.
71 | connection.state(1);
72 | // Setup the connection manager's state change listeners:
73 | connection.on('stateChange', function(state, laststate) {
74 | if (connection._options.debug) {
75 | debug(connection.id, 'stateChange: ', laststate, '->', state);
76 | }
77 |
78 | if (state === 4) {
79 | manager.attach(connection);
80 | // Handle first frame breakages.
81 | if (_firstFrame) {
82 | parser.write(_firstFrame);
83 | delete _firstFrame;
84 | }
85 | } else if (state === 5 && laststate !== 6 && laststate !== 5) {
86 | close(connection);
87 | } else if (state === 6 && laststate === 5) {
88 | manager.detach(connection);
89 | connection.emit('close');
90 | }
91 | });
92 |
93 |
94 | // Start to process the connection
95 | if (!checkVersion(this)) {
96 | this.reject('Invalid version.');
97 | } else {
98 | // Let the debug mode know that we have a connection:
99 | debug(this.id, this.version + ' connection');
100 |
101 | socket.setTimeout(0);
102 | socket.setNoDelay(true);
103 | socket.setKeepAlive(true, 0);
104 |
105 | // Handle incoming data:
106 | var parser = new Parser(this);
107 |
108 | parser.on('message', function(message) {
109 | debug(connection.id, 'recv: ' + message);
110 | connection.emit('message', message);
111 | });
112 |
113 | parser.on('close', function() {
114 | debug(connection.id, 'requested close');
115 |
116 | // Timer to catch clients that don't send close packets.
117 | // I'm looking at you safari and chrome.
118 | if (connection._closeTimer) {
119 | clearTimeout(connection._closeTimer);
120 | }
121 | connection.state(5);
122 | });
123 |
124 | socket.on('data', function(data) {
125 | parser.write(data);
126 | });
127 |
128 | // Handle the end of the stream, and set the state
129 | // appropriately to notify the correct events.
130 | socket.on('end', function() {
131 | debug(connection.id, 'end');
132 | connection.state(5);
133 | });
134 |
135 | socket.on('timeout', function() {
136 | debug(connection.id, 'timed out');
137 | connection.emit('timeout');
138 | });
139 |
140 | socket.on('error', function(e) {
141 | debug(connection.id, 'error', e);
142 | if (e.errno != Constants.EPIPE ||
143 | e.errno != connection.ECONNRESET) {
144 | connection.emit('error', e);
145 | }
146 | connection.state(5);
147 | });
148 |
149 | // Bubble errors up to the manager.
150 | connection.bubbleEvent('error', manager);
151 |
152 | // Carry out the handshaking.
153 | // - Draft75: There's no upgradeHead, goto Then.
154 | // Draft76: If there's an upgradeHead of the right length, goto Then.
155 | // Then: carry out the handshake.
156 | //
157 | // - Currently no browsers to my knowledge split the upgradeHead off
158 | // the request,
159 | // but in the case it does happen, then the state is set to waiting for
160 | // the upgradeHead.
161 | //
162 | // This switch is sorted in order of probably of occurence.
163 | switch (this.version) {
164 | case 'draft76':
165 | if (upgradeHead.length >= 8) {
166 | if (upgradeHead.length > 8) {
167 | _firstFrame = upgradeHead.slice(8, upgradeHead.length);
168 | }
169 |
170 | handshakes.draft76(connection, upgradeHead.slice(0, 8));
171 | } else {
172 | connection.reject('Missing key3');
173 | }
174 | break;
175 | case 'draft75':
176 | handshakes.draft75(connection);
177 | break;
178 | default:
179 | connection.reject('Unknown version: ' + this.version);
180 | break;
181 | }
182 | }
183 | }
184 |
185 | util.inherits(Connection, _events.EventEmitter);
186 |
187 | /*-----------------------------------------------
188 | Various utility style functions:
189 | -----------------------------------------------*/
190 | function write(connection, data) {
191 | debug(connection.id, 'write: ', data.inspect());
192 | if (connection._socket.writable) {
193 | return connection._socket.write(data);
194 | }
195 | return false;
196 | }
197 |
198 | function close(connection) {
199 | connection._socket.end();
200 | connection._socket.destroy();
201 | debug(connection.id, 'socket closed');
202 | connection.state(6);
203 | }
204 |
205 | function checkVersion(connection) {
206 | var server_version = connection._options.version.toLowerCase();
207 |
208 | return (server_version == 'auto' || server_version == connection.version);
209 | }
210 |
211 |
212 | function pack(num) {
213 | var result = '';
214 | result += String.fromCharCode(num >> 24 & 0xFF);
215 | result += String.fromCharCode(num >> 16 & 0xFF);
216 | result += String.fromCharCode(num >> 8 & 0xFF);
217 | result += String.fromCharCode(num & 0xFF);
218 | return result;
219 | }
220 |
221 |
222 | /*-----------------------------------------------
223 | Formatters for the urls
224 | -----------------------------------------------*/
225 |
226 | // TODO: Properly handle origin headers.
227 | function websocket_origin(connection) {
228 | var origin = connection._options.origin || '*';
229 |
230 | if (origin == '*' || Array.isArray(origin)) {
231 | origin = connection._req.headers.origin;
232 | }
233 |
234 | return origin;
235 | }
236 |
237 | function websocket_location(connection) {
238 | if (connection._req.headers['host'] === undefined) {
239 | connection.reject('Missing host header');
240 | return;
241 | }
242 |
243 | var location = '',
244 | secure = connection._socket.secure,
245 | host = connection._req.headers.host.split(':'),
246 | port = host[1] !== undefined ? host[1] : (secure ? 443 : 80);
247 |
248 | location += secure ? 'wss://' : 'ws://';
249 | location += host[0];
250 |
251 | if (!secure && port != 80 || secure && port != 443) {
252 | location += ':' + port;
253 | }
254 |
255 | location += connection._req.url;
256 |
257 | return location;
258 | }
259 |
260 |
261 | /*-----------------------------------------------
262 | 0. unknown
263 | 1. opening
264 | 2. waiting
265 | 3. handshaking
266 | 4, connected
267 | 5. closing
268 | 6. closed
269 | -----------------------------------------------*/
270 | Connection.prototype._state = 0;
271 |
272 |
273 | /*-----------------------------------------------
274 | Connection Public API
275 | -----------------------------------------------*/
276 | Connection.prototype.state = function(state) {
277 | if (state !== undefined && typeof state === 'number') {
278 | var oldstate = this._state;
279 | this._state = state;
280 | this.emit('stateChange', this._state, oldstate);
281 | }
282 | };
283 |
284 | Connection.prototype.inspect = function() {
285 | return '