├── .github
└── workflows
│ └── websocket-tests.yml
├── .gitignore
├── .jshintrc
├── .npmignore
├── .npmrc
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── docs
├── W3CWebSocket.md
├── WebSocketClient.md
├── WebSocketConnection.md
├── WebSocketFrame.md
├── WebSocketRequest.md
├── WebSocketServer.md
└── index.md
├── example
└── whiteboard
│ ├── README
│ ├── index.ejs
│ ├── package.json
│ ├── public
│ ├── client.css
│ └── client.js
│ └── whiteboard.js
├── gulpfile.js
├── index.js
├── lib
├── Deprecation.js
├── W3CWebSocket.js
├── WebSocketClient.js
├── WebSocketConnection.js
├── WebSocketFrame.js
├── WebSocketRequest.js
├── WebSocketRouter.js
├── WebSocketRouterRequest.js
├── WebSocketServer.js
├── browser.js
├── utils.js
├── version.js
└── websocket.js
├── package.json
├── test
├── autobahn
│ ├── README.md
│ ├── config
│ │ └── fuzzingclient.json
│ └── run-wstest.sh
├── scripts
│ ├── autobahn-test-client.js
│ ├── certificate.pem
│ ├── echo-server.js
│ ├── fragmentation-test-client.js
│ ├── fragmentation-test-page.html
│ ├── fragmentation-test-server.js
│ ├── libwebsockets-test-client.js
│ ├── libwebsockets-test-server.js
│ ├── libwebsockets-test.html
│ ├── memoryleak-client.js
│ ├── memoryleak-server.js
│ └── privatekey.pem
├── shared
│ ├── start-echo-server.js
│ └── test-server.js
└── unit
│ ├── dropBeforeAccept.js
│ ├── regressions.js
│ ├── request.js
│ ├── w3cwebsocket.js
│ └── websocketFrame.js
└── vendor
└── FastBufferList.js
/.github/workflows/websocket-tests.yml:
--------------------------------------------------------------------------------
1 | name: websocket-tests
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/setup-node@v1
8 | with:
9 | node-version: 10.x
10 |
11 | - uses: actions/checkout@v2
12 |
13 | - run: npm install
14 |
15 | - run: npm run test
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | .lock-*
4 | build/
5 | builderror.log
6 | npm-debug.log
7 | test/autobahn/reports*/*
8 | test/scripts/heapdump/*
9 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // JSHint Default Configuration File (as on JSHint website)
3 | // See http://jshint.com/docs/ for more details
4 |
5 | "maxerr" : 50, // {int} Maximum error before stopping
6 |
7 | // Enforcing
8 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.)
9 | "camelcase" : false, // true: Identifiers must be in camelCase
10 | "curly" : true, // true: Require {} for every new block or scope
11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison
12 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
13 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty()
14 | "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
15 | "latedef" : "nofunc", // true: Require variables/functions to be defined before being used
16 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
18 | "noempty" : true, // true: Prohibit use of empty blocks
19 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
20 | "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
21 | "plusplus" : false, // true: Prohibit use of `++` & `--`
22 | "quotmark" : "single", // Quotation mark consistency:
23 | // false : do nothing (default)
24 | // true : ensure whatever is used is consistent
25 | // "single" : require single quotes
26 | // "double" : require double quotes
27 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
28 | "unused" : "vars", // vars: Require all defined variables be used, ignore function params
29 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode
30 | "maxparams" : false, // {int} Max number of formal params allowed per function
31 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
32 | "maxstatements" : false, // {int} Max number statements per function
33 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
34 | "maxlen" : false, // {int} Max number of characters per line
35 |
36 | // Relaxing
37 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
38 | "boss" : false, // true: Tolerate assignments where comparisons would be expected
39 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
40 | "eqnull" : false, // true: Tolerate use of `== null`
41 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
42 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
43 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
44 | // (ex: `for each`, multiple try/catch, function expression…)
45 | "evil" : false, // true: Tolerate use of `eval` and `new Function()`
46 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
47 | "funcscope" : false, // true: Tolerate defining variables inside control statements
48 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
49 | "iterator" : false, // true: Tolerate using the `__iterator__` property
50 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
51 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
52 | "laxcomma" : false, // true: Tolerate comma-first style coding
53 | "loopfunc" : false, // true: Tolerate functions being defined in loops
54 | "multistr" : false, // true: Tolerate multi-line strings
55 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them.
56 | "notypeof" : false, // true: Tolerate invalid typeof operator values
57 | "proto" : false, // true: Tolerate using the `__proto__` property
58 | "scripturl" : false, // true: Tolerate script-targeted URLs
59 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
60 | "sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
61 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
62 | "validthis" : false, // true: Tolerate using this in a non-constructor function
63 |
64 | // Environments
65 | "browser" : true, // Web Browser (window, document, etc)
66 | "browserify" : true, // Browserify (node.js code in the browser)
67 | "couch" : false, // CouchDB
68 | "devel" : true, // Development/debugging (alert, confirm, etc)
69 | "dojo" : false, // Dojo Toolkit
70 | "jasmine" : false, // Jasmine
71 | "jquery" : false, // jQuery
72 | "mocha" : false, // Mocha
73 | "mootools" : false, // MooTools
74 | "node" : true, // Node.js
75 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
76 | "prototypejs" : false, // Prototype and Scriptaculous
77 | "qunit" : false, // QUnit
78 | "rhino" : false, // Rhino
79 | "shelljs" : false, // ShellJS
80 | "worker" : false, // Web Workers
81 | "wsh" : false, // Windows Scripting Host
82 | "yui" : false, // Yahoo User Interface
83 |
84 | // Custom Globals
85 | "globals" : { // additional predefined global variables
86 | "WebSocket": true
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .npmignore
2 | .gitignore
3 | .git/
4 | .DS_Store
5 | .lock-*
6 | node_modules/
7 | docs/
8 | example/
9 | build/
10 | builderror.log
11 | npm-debug.log
12 | test/
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | Version 1.0.34
5 | --------------
6 | *Released 2021-04-14*
7 |
8 | * Updated browser shim to use the native `globalThis` property when available. See [this MDN page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) for context. Resolves [#415](https://github.com/theturtle32/WebSocket-Node/issues/415)
9 |
10 | Version 1.0.33
11 | --------------
12 | *Released 2020-12-08*
13 |
14 | * Added new configuration options to WebSocketServer allowing implementors to bypass parsing WebSocket extensions and HTTP Cookies if they are not needed. (Thanks, [@aetheon](https://github.com/aetheon))
15 | * Added new `upgradeError` event to WebSocketServer to allow for visibility into and logging of any parsing errors that might occur during the HTTP Upgrade phase. (Thanks, [@aetheon](https://github.com/aetheon))
16 |
17 | Version 1.0.32
18 | --------------
19 | *Released 2020-08-28*
20 |
21 | * Refactor to use [N-API modules](https://nodejs.org/api/n-api.html) from [ws project](https://github.com/websockets). (Thanks, [@andreek](https://github.com/andreek))
22 | * Specifically:
23 | * [utf-8-validate](https://github.com/websockets/utf-8-validate)
24 | * [bufferutil](https://github.com/websockets/bufferutil)
25 | * Removed some documentation notations about very old browsers and very old Websocket protocol drafts that are no longer relevant today in 2020.
26 | * Removed outdated notations and instructions about building native extensions, since those functions are now delegated to dependencies.
27 | * Add automated unit test executionn via Github Actions (Thanks, [@nebojsa94](https://github.com/nebojsa94))
28 | * Accept new connection close code `1015` ("TLS Handshake"). (More information at the [WebSocket Close Code Number Registry](https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number))
29 |
30 | Version 1.0.31
31 | --------------
32 | *Released 2019-12-06*
33 |
34 | * Fix [infinite loop in error handling](https://github.com/theturtle32/WebSocket-Node/issues/329) (Thanks, [@apirila](https://github.com/apirila))
35 | * Fix [memory leak with multiple WebSocket servers on the same HTTP server](https://github.com/theturtle32/WebSocket-Node/pull/339) (Thanks, [@nazar-pc](https://github.com/nazar-pc))
36 | * [Use es5-ext/global as a more robust way to resolve browser's window object](https://github.com/theturtle32/WebSocket-Node/pull/362) (Thanks, [@michaelsbradleyjr](https://github.com/michaelsbradleyjr))
37 | * [adding compatibility with V8 release greater than v7.6 (node and electron engines)](https://github.com/theturtle32/WebSocket-Node/pull/376) (Thanks, [@artynet](https://github.com/artynet))
38 |
39 | Version 1.0.30
40 | --------------
41 | *Released 2019-09-12*
42 |
43 | * Moved gulp back to devDependencies
44 |
45 | Version 1.0.29
46 | --------------
47 | *Released 2019-07-03*
48 |
49 | * Updated some dependencies and updated the .gitignore and .npmignore files
50 |
51 | Version 1.0.28
52 | --------------
53 | *Released 2018-09-19*
54 |
55 | * Updated to latest version of [nan](https://github.com/nodejs/nan)
56 |
57 | Version 1.0.27
58 | --------------
59 | *Released 2018-09-19*
60 |
61 | * Allowing additional request `headers` to be specified in the `tlsOptions` config parameter for WebSocketClient. See pull request #323
62 | * Resolving deprecation warnings relating to usage of `new Buffer`
63 |
64 | Version 1.0.26
65 | --------------
66 | *Released 2018-04-27*
67 |
68 | * No longer using the deprecated `noAssert` parameter for functions reading and writing binary numeric data. (Thanks, [@BridgeAR](https://github.com/BridgeAR))
69 |
70 | Version 1.0.25
71 | --------------
72 | *Released 2017-10-18*
73 |
74 | * Bumping minimum supported node version specified in package.json to v0.10.x because some upstream libraries no longer install on v0.8.x
75 | * [Allowing use of close codes 1012, 1013, 1014](https://www.iana.org/assignments/websocket/websocket.xml)
76 | * [Allowing the `Host` header to be overridden.](https://github.com/theturtle32/WebSocket-Node/pull/291) (Thanks, [@Juneil](https://github.com/Juneil))
77 | * [Mitigating infinite loop for broken connections](https://github.com/theturtle32/WebSocket-Node/pull/289) (Thanks, [@tvkit](https://github.com/tvkit))
78 | * [Fixed Markdown Typos](https://github.com/theturtle32/WebSocket-Node/pull/281) (Thanks, [@teramotodaiki](https://github.com/teramotodaiki))
79 | * [Adding old readyState constants for W3CWebSocket interface](https://github.com/theturtle32/WebSocket-Node/pull/282) (Thanks, [@thechriswalker](https://github.com/thechriswalker))
80 |
81 |
82 | Version 1.0.24
83 | --------------
84 | *Released 2016-12-28*
85 |
86 | * Fixed a bug when using native keepalive on Node >= 6.0. (Thanks, [@prossin](https://github.com/prossin))
87 | * Upgrading outdated dependencies
88 |
89 | Version 1.0.23
90 | --------------
91 | *Released 2016-05-18*
92 |
93 | * Official support for Node 6.x
94 | * Updating dependencies. Specifically, updating nan to ^2.3.3
95 |
96 | Version 1.0.22
97 | --------------
98 | *Released 2015-09-28*
99 |
100 | * Updating to work with nan 2.x
101 |
102 | Version 1.0.21
103 | --------------
104 | *Released 2015-07-22*
105 |
106 | * Incremented and re-published to work around an aborted npm publish of v1.0.20.
107 |
108 | Version 1.0.20
109 | --------------
110 | *Released 2015-07-22*
111 |
112 | * Added EventTarget to the W3CWebSocket interface (Thanks, [@ibc](https://github.com/ibc)!)
113 | * Corrected an inaccurate error message. (Thanks, [@lekoaf](https://github.com/lekoaf)!)
114 |
115 | Version 1.0.19
116 | --------------
117 | *Released 2015-05-28*
118 |
119 | * Updated to nan v1.8.x (tested with v1.8.4)
120 | * Added `"license": "Apache-2.0"` to package.json via [pull request #199](https://github.com/theturtle32/WebSocket-Node/pull/199) by [@pgilad](https://github.com/pgilad). See [npm1k.org](http://npm1k.org/).
121 |
122 |
123 | Version 1.0.18
124 | --------------
125 | *Released 2015-03-19*
126 |
127 | * Resolves [issue #195](https://github.com/theturtle32/WebSocket-Node/pull/179) - passing number to connection.send() causes crash
128 | * [Added close code/reason arguments to W3CWebSocket#close()](https://github.com/theturtle32/WebSocket-Node/issues/184)
129 |
130 |
131 | Version 1.0.17
132 | --------------
133 | *Released 2015-01-17*
134 |
135 | * Resolves [issue #179](https://github.com/theturtle32/WebSocket-Node/pull/179) - Allow toBuffer to work with empty data
136 |
137 |
138 | Version 1.0.16
139 | --------------
140 | *Released 2015-01-16*
141 |
142 | * Resolves [issue #178](https://github.com/theturtle32/WebSocket-Node/issues/178) - Ping Frames with no data
143 |
144 |
145 | Version 1.0.15
146 | --------------
147 | *Released 2015-01-13*
148 |
149 | * Resolves [issue #177](https://github.com/theturtle32/WebSocket-Node/issues/177) - WebSocketClient ignores options unless it has a tlsOptions property
150 |
151 |
152 | Version 1.0.14
153 | --------------
154 | *Released 2014-12-03*
155 |
156 | * Resolves [issue #173](https://github.com/theturtle32/WebSocket-Node/issues/173) - To allow the W3CWebSocket interface to accept an optional non-standard configuration object as its third parameter, which will be ignored when running in a browser context.
157 |
158 |
159 | Version 1.0.13
160 | --------------
161 | *Released 2014-11-29*
162 |
163 | * Fixes [issue #171](https://github.com/theturtle32/WebSocket-Node/issues/171) - Code to prevent calling req.accept/req.reject multiple times breaks sanity checks in req.accept
164 |
165 |
166 | Version 1.0.12
167 | --------------
168 | *Released 2014-11-28*
169 |
170 | * Fixes [issue #170](https://github.com/theturtle32/WebSocket-Node/issues/170) - Non-native XOR implementation broken after making JSHint happy
171 |
172 |
173 | Version 1.0.11
174 | --------------
175 | *Released 2014-11-25*
176 |
177 | * Fixes some undefined behavior surrounding closing WebSocket connections and more reliably handles edge cases.
178 | * Adds an implementation of the W3C WebSocket API for browsers to facilitate sharing code between client and server via browserify. (Thanks, [@ibc](https://github.com/ibc)!)
179 | * `WebSocketConnection.prototype.close` now accepts optional `reasonCode` and `description` parameters.
180 | * Calling `accept` or `reject` more than once on a `WebSocketRequest` will now throw an error. [Issue #149](https://github.com/theturtle32/WebSocket-Node/issues/149)
181 | * Handling connections dropped by client before accepted by server [Issue #167](https://github.com/theturtle32/WebSocket-Node/issues/167)
182 | * Integrating Gulp and JSHint (Thanks, [@ibc](https://github.com/ibc)!)
183 | * Starting to add individual unit tests (using substack's [tape](github.com/substack/tape) and [faucet](github.com/substack/faucet))
184 |
185 |
186 | Version 1.0.10
187 | --------------
188 | *Released 2014-10-22*
189 |
190 | * Fixed Issue [#146](https://github.com/theturtle32/WebSocket-Node/issues/146) that was causing WebSocketClient to throw errors when instantiated if passed `tlsOptions`.
191 |
192 | Version 1.0.9
193 | -------------
194 | *Released 2014-10-20*
195 |
196 | * Fixing an insidious corner-case bug that prevented `WebSocketConnection` from firing the `close` event in certain cases when there was an error on the underlying `Socket`, leading to connections sticking around forever, stuck erroneously in the `connected` state. These "ghost" connections would cause an error event when trying to write to them.
197 | * Removed deprecated `websocketVersion` property. Use `webSocketVersion` instead (case difference).
198 | * Allowing user to specify all properties for `tlsOptions` in WebSocketClient, not just a few whitelisted properties. This keeps us from having to constantly add new config properties for new versions of Node. (Thanks, [jesusprubio](https://github.com/jesusprubio))
199 | * Removing support for Node 0.4.x and 0.6.x.
200 | * Adding `fuzzingclient.json` spec file for the Autobahn Test Suite.
201 | * Now more fairly emitting `message` events from the `WebSocketConnection`. Previously, all buffered frames for a connection would be processed and all `message` events emitted before moving on to processing the next connection with available data. Now We process one frame per connection (most of the time) in a more fair round-robin fashion.
202 | * Now correctly calling the `EventEmitter` superclass constructor during class instance initialization.
203 | * `WebSocketClient.prototype.connect` now accepts the empty string (`''`) to mean "no subprotocol requested." Previously either `null` or an empty array (`[]`) was required.
204 | * Fixing a `TypeError` bug in `WebSocketRouter` (Thanks, [a0000778](https://github.com/a0000778))
205 | * Fixing a potential race condition when attaching event listeners to the underlying `Socket`. (Thanks [RichardBsolut](https://github.com/RichardBsolut))
206 | * `WebSocketClient` now accepts an optional options hash to be passed to `(http|https).request`. (Thanks [mildred](https://github.com/mildred) and [aus](https://github.com/aus)) This enables the following new abilities, amongst others:
207 | * Use WebSocket-Node from behind HTTP/HTTPS proxy servers using [koichik/node-tunnel](https://github.com/koichik/node-tunnel) or similar.
208 | * Specify the local port and local address to bind the outgoing request socket to.
209 | * Adding option to ignore `X-Forwarded-For` headers when accepting connections from untrusted clients.
210 | * Adding ability to mount a `WebSocketServer` instance to an arbitrary number of Node http/https servers.
211 | * Adding browser shim so Browserify won't blow up when trying to package up code that uses WebSocket-Node. The shim is a no-op, it ***does not implement a wrapper*** providing the WebSocket-Node API in the browser.
212 | * Incorporating upstream enhancements for the native C++ UTF-8 validation and xor masking functions. (Thanks [einaros](https://github.com/einaros) and [kkoopa](https://github.com/kkoopa))
213 |
214 |
215 | Version 1.0.8
216 | -------------
217 | *Released 2012-12-26*
218 |
219 | * Fixed remaining naming inconsistency of "websocketVersion" as opposed to "webSocketVersion" throughout the code, and added deprecation warnings for use of the old casing throughout.
220 | * Fixed an issue with our case-insensitive handling of WebSocket subprotocols. Clients that requested a mixed-case subprotocol would end up failing the connection when the server accepted the connection, returning a lower-case version of the subprotocol name. Now we return the subprotocol name in the exact casing that was requested by the client, while still maintaining the case-insensitive verification logic for convenience and practicality.
221 | * Making sure that any socket-level activity timeout that may have been set on a TCP socket is removed when initializing a connection.
222 | * Added support for native TCP Keep-Alive instead of using the WebSocket ping/pong packets to serve that function.
223 | * Fixed cookie parsing to be compliant with RFC 2109
224 |
225 | Version 1.0.7
226 | -------------
227 | *Released 2012-08-12*
228 |
229 | * ***Native modules are now optional!*** If they fail to compile, WebSocket-Node will still work but will not verify that received UTF-8 data is valid, and xor masking/unmasking of payload data for security purposes will not be as efficient as it is performed in JavaScript instead of native code.
230 | * Reduced Node.JS version requirement back to v0.6.10
231 |
232 | Version 1.0.6
233 | -------------
234 | *Released 2012-05-22*
235 |
236 | * Now requires Node v0.6.13 since that's the first version that I can manage to successfully build the native UTF-8 validator with node-gyp through npm.
237 |
238 | Version 1.0.5
239 | -------------
240 | *Released 2012-05-21*
241 |
242 | * Fixes the issues that users were having building the native UTF-8 validator on Windows platforms. Special Thanks to:
243 | * [zerodivisi0n](https://github.com/zerodivisi0n)
244 | * [andreasbotsikas](https://github.com/andreasbotsikas)
245 | * Fixed accidental global variable usage (Thanks, [hakobera](https://github.com/hakobera)!)
246 | * Added callbacks to the send* methods that provide notification of messages being sent on the wire and any socket errors that may occur when sending a message. (Thanks, [zerodivisi0n](https://github.com/zerodivisi0n)!)
247 | * Added option to disable logging in the echo-server in the test folder (Thanks, [oberstet](https://github.com/oberstet)!)
248 |
249 |
250 | Version 1.0.4
251 | -------------
252 | *Released 2011-12-18*
253 |
254 | * Now validates that incoming UTF-8 messages do, in fact, contain valid UTF-8 data. The connection is dropped with prejudice if invalid data is received. This strict behavior conforms to the WebSocket RFC and is verified by the Autobahn Test Suite. This is accomplished in a performant way by using a native C++ Node module created by [einaros](https://github.com/einaros).
255 | * Updated handling of connection closure to pass more of the Autobahn Test Suite.
256 |
257 | Version 1.0.3
258 | -------------
259 | *Released 2011-12-18*
260 |
261 | * Substantial speed increase (~150% on my machine, depending on the circumstances) due to an optimization in FastBufferList.js that drastically reduces the number of memory alloctions and buffer copying. ([kazuyukitanimura](https://github.com/kazuyukitanimura))
262 |
263 |
264 | Version 1.0.2
265 | -------------
266 | *Released 2011-11-28*
267 |
268 | * Fixing whiteboard example to work under Node 0.6.x ([theturtle32](https://github.com/theturtle32))
269 | * Now correctly emitting a `close` event with a 1006 error code if there is a TCP error while writing to the socket during the handshake. ([theturtle32](https://github.com/theturtle32))
270 | * Catching errors when writing to the TCP socket during the handshake. ([justoneplanet](https://github.com/justoneplanet))
271 | * No longer outputting console.warn messages when there is an error writing to the TCP socket ([justoneplanet](https://github.com/justoneplanet))
272 | * Fixing some formatting errors, commas, semicolons, etc. ([kaisellgren](https://github.com/kaisellgren))
273 |
274 |
275 | Version 1.0.1
276 | -------------
277 | *Released 2011-11-21*
278 |
279 | * Now works with Node 0.6.2 as well as 0.4.12
280 | * Support TLS in WebSocketClient
281 | * Added support for setting and reading cookies
282 | * Added WebSocketServer.prototype.broadcast(data) convenience method
283 | * Added `resourceURL` property to WebSocketRequest objects. It is a Node URL object with the `resource` and any query string params already parsed.
284 | * The WebSocket request router no longer includes the entire query string when trying to match the path name of the request.
285 | * WebSocketRouterRequest objects now include all the properties and events of WebSocketRequest objects.
286 | * Removed more console.log statements. Please rely on the various events emitted to be notified of error conditions. I decided that it is not a library's place to spew information to the console.
287 | * Renamed the `websocketVersion` property to `webSocketVersion` throughout the code to fix inconsistent capitalization. `websocketVersion` has been kept for compatibility but is deprecated and may be removed in the future.
288 | * Now outputting the sanitized version of custom header names rather than the raw value. This prevents invalid HTTP from being put onto the wire if given an illegal header name.
289 |
290 |
291 | I decided it's time to start maintaining a changelog now, starting with version 1.0.1.
292 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | autobahn:
2 | @NODE_PATH=lib node test/autobahn-test-client.js --host=127.0.0.1 --port=9000
3 |
4 | autobahn-server:
5 | @NODE_PATH=lib node test/echo-server.js
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebSocket Client & Server Implementation for Node
2 | =================================================
3 |
4 | [](http://badge.fury.io/js/websocket)
5 |
6 | [](https://www.npmjs.com/package/websocket)
7 |
8 | [ ](https://codeship.com/projects/61106)
9 |
10 | Overview
11 | --------
12 | This is a (mostly) pure JavaScript implementation of the WebSocket protocol versions 8 and 13 for Node. There are some example client and server applications that implement various interoperability testing protocols in the "test/scripts" folder.
13 |
14 |
15 | Documentation
16 | =============
17 |
18 | [You can read the full API documentation in the docs folder.](docs/index.md)
19 |
20 |
21 | Changelog
22 | ---------
23 |
24 | ***Current Version: 1.0.34*** - Release 2021-04-14
25 |
26 | * Updated browser shim to use the native `globalThis` property when available. See [this MDN page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) for context. Resolves [#415](https://github.com/theturtle32/WebSocket-Node/issues/415)
27 |
28 | [View the full changelog](CHANGELOG.md)
29 |
30 | Browser Support
31 | ---------------
32 |
33 | All current browsers are fully* supported.
34 |
35 | * Firefox 7-9 (Old) (Protocol Version 8)
36 | * Firefox 10+ (Protocol Version 13)
37 | * Chrome 14,15 (Old) (Protocol Version 8)
38 | * Chrome 16+ (Protocol Version 13)
39 | * Internet Explorer 10+ (Protocol Version 13)
40 | * Safari 6+ (Protocol Version 13)
41 |
42 | (Not all W3C WebSocket features are supported by browsers. More info in the [Full API documentation](docs/index.md))
43 |
44 | Benchmarks
45 | ----------
46 | There are some basic benchmarking sections in the Autobahn test suite. I've put up a [benchmark page](http://theturtle32.github.com/WebSocket-Node/benchmarks/) that shows the results from the Autobahn tests run against AutobahnServer 0.4.10, WebSocket-Node 1.0.2, WebSocket-Node 1.0.4, and ws 0.3.4.
47 |
48 | (These benchmarks are quite a bit outdated at this point, so take them with a grain of salt. Anyone up for running new benchmarks? I'll link to your report.)
49 |
50 | Autobahn Tests
51 | --------------
52 | The very complete [Autobahn Test Suite](http://autobahn.ws/testsuite/) is used by most WebSocket implementations to test spec compliance and interoperability.
53 |
54 | - [View Server Test Results](http://theturtle32.github.com/WebSocket-Node/test-report/servers/)
55 |
56 | Installation
57 | ------------
58 |
59 | In your project root:
60 |
61 | $ npm install websocket
62 |
63 | Then in your code:
64 |
65 | ```javascript
66 | var WebSocketServer = require('websocket').server;
67 | var WebSocketClient = require('websocket').client;
68 | var WebSocketFrame = require('websocket').frame;
69 | var WebSocketRouter = require('websocket').router;
70 | var W3CWebSocket = require('websocket').w3cwebsocket;
71 | ```
72 |
73 | Current Features:
74 | -----------------
75 | - Licensed under the Apache License, Version 2.0
76 | - Protocol version "8" and "13" (Draft-08 through the final RFC) framing and handshake
77 | - Can handle/aggregate received fragmented messages
78 | - Can fragment outgoing messages
79 | - Router to mount multiple applications to various path and protocol combinations
80 | - TLS supported for outbound connections via WebSocketClient
81 | - TLS supported for server connections (use https.createServer instead of http.createServer)
82 | - Thanks to [pors](https://github.com/pors) for confirming this!
83 | - Cookie setting and parsing
84 | - Tunable settings
85 | - Max Receivable Frame Size
86 | - Max Aggregate ReceivedMessage Size
87 | - Whether to fragment outgoing messages
88 | - Fragmentation chunk size for outgoing messages
89 | - Whether to automatically send ping frames for the purposes of keepalive
90 | - Keep-alive ping interval
91 | - Whether or not to automatically assemble received fragments (allows application to handle individual fragments directly)
92 | - How long to wait after sending a close frame for acknowledgment before closing the socket.
93 | - [W3C WebSocket API](http://www.w3.org/TR/websockets/) for applications running on both Node and browsers (via the `W3CWebSocket` class).
94 |
95 |
96 | Known Issues/Missing Features:
97 | ------------------------------
98 | - No API for user-provided protocol extensions.
99 |
100 |
101 | Usage Examples
102 | ==============
103 |
104 | Server Example
105 | --------------
106 |
107 | Here's a short example showing a server that echos back anything sent to it, whether utf-8 or binary.
108 |
109 | ```javascript
110 | #!/usr/bin/env node
111 | var WebSocketServer = require('websocket').server;
112 | var http = require('http');
113 |
114 | var server = http.createServer(function(request, response) {
115 | console.log((new Date()) + ' Received request for ' + request.url);
116 | response.writeHead(404);
117 | response.end();
118 | });
119 | server.listen(8080, function() {
120 | console.log((new Date()) + ' Server is listening on port 8080');
121 | });
122 |
123 | wsServer = new WebSocketServer({
124 | httpServer: server,
125 | // You should not use autoAcceptConnections for production
126 | // applications, as it defeats all standard cross-origin protection
127 | // facilities built into the protocol and the browser. You should
128 | // *always* verify the connection's origin and decide whether or not
129 | // to accept it.
130 | autoAcceptConnections: false
131 | });
132 |
133 | function originIsAllowed(origin) {
134 | // put logic here to detect whether the specified origin is allowed.
135 | return true;
136 | }
137 |
138 | wsServer.on('request', function(request) {
139 | if (!originIsAllowed(request.origin)) {
140 | // Make sure we only accept requests from an allowed origin
141 | request.reject();
142 | console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
143 | return;
144 | }
145 |
146 | var connection = request.accept('echo-protocol', request.origin);
147 | console.log((new Date()) + ' Connection accepted.');
148 | connection.on('message', function(message) {
149 | if (message.type === 'utf8') {
150 | console.log('Received Message: ' + message.utf8Data);
151 | connection.sendUTF(message.utf8Data);
152 | }
153 | else if (message.type === 'binary') {
154 | console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
155 | connection.sendBytes(message.binaryData);
156 | }
157 | });
158 | connection.on('close', function(reasonCode, description) {
159 | console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
160 | });
161 | });
162 | ```
163 |
164 | Client Example
165 | --------------
166 |
167 | This is a simple example client that will print out any utf-8 messages it receives on the console, and periodically sends a random number.
168 |
169 | *This code demonstrates a client in Node.js, not in the browser*
170 |
171 | ```javascript
172 | #!/usr/bin/env node
173 | var WebSocketClient = require('websocket').client;
174 |
175 | var client = new WebSocketClient();
176 |
177 | client.on('connectFailed', function(error) {
178 | console.log('Connect Error: ' + error.toString());
179 | });
180 |
181 | client.on('connect', function(connection) {
182 | console.log('WebSocket Client Connected');
183 | connection.on('error', function(error) {
184 | console.log("Connection Error: " + error.toString());
185 | });
186 | connection.on('close', function() {
187 | console.log('echo-protocol Connection Closed');
188 | });
189 | connection.on('message', function(message) {
190 | if (message.type === 'utf8') {
191 | console.log("Received: '" + message.utf8Data + "'");
192 | }
193 | });
194 |
195 | function sendNumber() {
196 | if (connection.connected) {
197 | var number = Math.round(Math.random() * 0xFFFFFF);
198 | connection.sendUTF(number.toString());
199 | setTimeout(sendNumber, 1000);
200 | }
201 | }
202 | sendNumber();
203 | });
204 |
205 | client.connect('ws://localhost:8080/', 'echo-protocol');
206 | ```
207 |
208 | Client Example using the *W3C WebSocket API*
209 | --------------------------------------------
210 |
211 | Same example as above but using the [W3C WebSocket API](http://www.w3.org/TR/websockets/).
212 |
213 | ```javascript
214 | var W3CWebSocket = require('websocket').w3cwebsocket;
215 |
216 | var client = new W3CWebSocket('ws://localhost:8080/', 'echo-protocol');
217 |
218 | client.onerror = function() {
219 | console.log('Connection Error');
220 | };
221 |
222 | client.onopen = function() {
223 | console.log('WebSocket Client Connected');
224 |
225 | function sendNumber() {
226 | if (client.readyState === client.OPEN) {
227 | var number = Math.round(Math.random() * 0xFFFFFF);
228 | client.send(number.toString());
229 | setTimeout(sendNumber, 1000);
230 | }
231 | }
232 | sendNumber();
233 | };
234 |
235 | client.onclose = function() {
236 | console.log('echo-protocol Client Closed');
237 | };
238 |
239 | client.onmessage = function(e) {
240 | if (typeof e.data === 'string') {
241 | console.log("Received: '" + e.data + "'");
242 | }
243 | };
244 | ```
245 |
246 | Request Router Example
247 | ----------------------
248 |
249 | For an example of using the request router, see `libwebsockets-test-server.js` in the `test` folder.
250 |
251 |
252 | Resources
253 | ---------
254 |
255 | A presentation on the state of the WebSockets protocol that I gave on July 23, 2011 at the LA Hacker News meetup. [WebSockets: The Real-Time Web, Delivered](http://www.scribd.com/doc/60898569/WebSockets-The-Real-Time-Web-Delivered)
256 |
--------------------------------------------------------------------------------
/docs/W3CWebSocket.md:
--------------------------------------------------------------------------------
1 | W3CWebSocket
2 | ============
3 |
4 | * [Constructor](#constructor)
5 | * [Limitations](#limitations)
6 |
7 | `var W3CWebSocket = require('websocket').w3cwebsocket`
8 |
9 | Implementation of the [W3C WebSocket API](http://www.w3.org/TR/websockets/) for browsers.
10 |
11 | The exposed class lets the developer use the browser *W3C WebSocket API* in Node:
12 |
13 | ```javascript
14 | var WS = require('websocket').w3cwebsocket;
15 |
16 | WS === window.WebSocket
17 | // => true when in the browser
18 |
19 | var ws = new WS('ws://example.com/resource', 'foo', 'http://example.com');
20 | // - In Node it creates an instance of websocket.W3CWebSocket.
21 | // - In the browser it creates an instance of window.WebSocket (third parameter
22 | // is ignored by the native WebSocket constructor).
23 |
24 | ws.onopen = function() { console.log('ws open'); };
25 | // etc.
26 | ```
27 |
28 |
29 | Constructor
30 | -----------
31 |
32 | ```javascript
33 | new W3CWebSocket(requestUrl, requestedProtocols, [[[[origin], headers], requestOptions], clientConfig])
34 | ```
35 |
36 | **clientConfig** is the parameter of the [WebSocketClient](./WebSocketClient.md) constructor.
37 |
38 | **requestUrl**, **requestedProtocols**, **origin**, **headers** and **requestOptions** are parameters to be used in the `connect()` method of [WebSocketClient](./WebSocketClient.md).
39 |
40 | This constructor API makes it possible to use the W3C API and "browserify" the Node application into a valid browser library.
41 |
42 | When running in a browser (for example by using [browserify](http://browserify.org/)) the browser's native `WebSocket` implementation is used, and thus just the first and second arguments (`requestUrl` and `requestedProtocols`) are used (those allowed by the *W3C WebSocket API*).
43 |
44 |
45 | Limitations
46 | -----------
47 |
48 | * `bufferedAmount` attribute is always 0.
49 | * `binaryType` is "arraybuffer" by default given that "blob" is not supported (Node does not implement the `Blob` class).
50 | * `send()` method allows arguments of type `DOMString`, `ArrayBuffer`, `ArrayBufferView` (`Int8Array`, etc) or Node `Buffer`, but does not allow `Blob`.
51 |
--------------------------------------------------------------------------------
/docs/WebSocketClient.md:
--------------------------------------------------------------------------------
1 | WebSocketClient
2 | ===============
3 |
4 | * [Constructor](#constructor)
5 | * [Config Options](#client-config-options)
6 | * [Methods](#methods)
7 | * [Events](#events)
8 | * **Examples**
9 | * [Connect using a Proxy Server](#connect-using-a-proxy-server)
10 |
11 | `var WebSocketClient = require('websocket').client`
12 |
13 | This object allows you to make client connections to a WebSocket server.
14 |
15 | Constructor
16 | -----------
17 | ```javascript
18 | new WebSocketClient([clientConfig]);
19 | ```
20 |
21 | Client Config Options
22 | ---------------------
23 | **webSocketVersion** - uint - *Default: 13*
24 | Which version of the WebSocket protocol to use when making the connection. Currently supported values are 8 and 13.
25 | This option will be removed once the protocol is finalized by the IETF It is only available to ease the transition through the intermediate draft protocol versions. The only thing this affects the name of the Origin header.
26 |
27 | **maxReceivedFrameSize** - uint - *Default: 1MiB*
28 | The maximum allowed received frame size in bytes. Single frame messages will also be limited to this maximum.
29 |
30 | **maxReceivedMessageSize** - uint - *Default: 8MiB*
31 | The maximum allowed aggregate message size (for fragmented messages) in bytes.
32 |
33 | **fragmentOutgoingMessages** - Boolean - *Default: true*
34 | Whether or not to fragment outgoing messages. If true, messages will be automatically fragmented into chunks of up to `fragmentationThreshold` bytes.
35 |
36 | **fragmentationThreshold** - uint - *Default: 16KiB*
37 | The maximum size of a frame in bytes before it is automatically fragmented.
38 |
39 | **assembleFragments** - boolean - *Default: true*
40 | If true, fragmented messages will be automatically assembled and the full message will be emitted via a `message` event. If false, each frame will be emitted on the WebSocketConnection object via a `frame` event and the application will be responsible for aggregating multiple fragmented frames. Single-frame messages will emit a `message` event in addition to the `frame` event. Most users will want to leave this set to `true`.
41 |
42 | **closeTimeout** - uint - *Default: 5000*
43 | The number of milliseconds to wait after sending a close frame for an acknowledgement to come back before giving up and just closing the socket.
44 |
45 | **tlsOptions** - object - *Default: {}*
46 | Options to pass to `https.request` if connecting via TLS. See [Node's HTTPS documentation](http://nodejs.org/api/https.html#https_https_request_options_callback)
47 |
48 |
49 | Methods
50 | -------
51 | ### connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions])
52 |
53 | Will establish a connection to the given `requestUrl`. `requestedProtocols` indicates a list of multiple subprotocols supported by the client. The remote server will select the best subprotocol that it supports and send that back when establishing the connection. `origin` is an optional field that can be used in user-agent scenarios to identify the page containing any scripting content that caused the connection to be requested. (This seems unlikely in node.. probably should leave it null most of the time.) `requestUrl` should be a standard websocket url, such as:
54 | `ws://www.mygreatapp.com:1234/websocketapp/`
55 |
56 | `headers` should be either `null` or an object specifying additional arbitrary HTTP request headers to send along with the request. This may be used to pass things like access tokens, etc. so that the server can verify authentication/authorization before deciding to accept and open the full WebSocket connection.
57 |
58 | `requestOptions` should be either `null` or an object specifying additional configuration options to be passed to `http.request` or `https.request`. This can be used to pass a custom `agent` to enable `WebSocketClient` usage from behind an HTTP or HTTPS proxy server using [koichik/node-tunnel](https://github.com/koichik/node-tunnel) or similar.
59 |
60 | `origin` must be specified if you want to pass `headers`, and both `origin` and `headers` must be specified if you want to pass `requestOptions`. The `origin` and `headers` parameters may be passed as `null`.
61 |
62 | ### abort()
63 |
64 | Will cancel an in-progress connection request before either the `connect` event or the `connectFailed` event has been emitted. If the `connect` or `connectFailed` event has already been emitted, calling `abort()` will do nothing.
65 |
66 |
67 | Events
68 | ------
69 | ### connect
70 | `function(webSocketConnection)`
71 |
72 | Emitted upon successfully negotiating the WebSocket handshake with the remote server. `webSocketConnection` is an instance of `WebSocketConnection` that can be used to send and receive messages with the remote server.
73 |
74 | ### connectFailed
75 | `function(errorDescription)`
76 |
77 | Emitted when there is an error connecting to the remote host or the handshake response sent by the server is invalid.
78 |
79 | ### httpResponse
80 | `function(response, webSocketClient)`
81 |
82 | Emitted when the server replies with anything other then "101 Switching Protocols". Provides an opportunity to handle redirects for example. The `response` parameter is an instance of the [http.IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) class. This is not suitable for handling receiving of large response bodies, as the underlying socket will be immediately closed by WebSocket-Node as soon as all handlers for this event are executed.
83 |
84 | Normally, if the remote server sends an HTTP response with a response code other than 101, the `WebSocketClient` will automatically emit the `connectFailed` event with a description of what was received from the remote server. However, if there are one or more listeners attached to the `httpResponse` event, then the `connectFailed` event will not be emitted for non-101 responses received. `connectFailed` will still be emitted for non-HTTP errors, such as when the remote server is unreachable or not accepting TCP connections.
85 |
86 |
87 | Examples
88 | ========
89 |
90 | Connect using a Proxy Server
91 | ----------------------------
92 |
93 | Using [koichik/node-tunnel](https://github.com/koichik/node-tunnel):
94 |
95 | ```javascript
96 | var WebSocketClient = require('websocket').client;
97 | var client = new WebSocketClient();
98 | var tunnel = require('tunnel');
99 |
100 | var tunnelingAgent = tunnel.httpOverHttp({
101 | proxy: {
102 | host: 'proxy.host.com',
103 | port: 8080
104 | }
105 | });
106 |
107 | var requestOptions = {
108 | agent: tunnelingAgent
109 | };
110 |
111 | client.connect('ws://echo.websocket.org/', null, null, null, requestOptions);
112 | ```
113 |
--------------------------------------------------------------------------------
/docs/WebSocketConnection.md:
--------------------------------------------------------------------------------
1 | WebSocketConnection
2 | ===================
3 |
4 | * [Constructor](#constructor)
5 | * [Properties](#properties)
6 | * [Methods](#methods)
7 | * [Events](#events)
8 |
9 | This object provides the interface through which you can communicate with connected peers. It is used in both WebSocketServer and WebSocketClient situations.
10 |
11 | Constructor
12 | -----------
13 | This object is created internally by `WebSocketRequest`.
14 |
15 | Properties
16 | ----------
17 |
18 | ### closeDescription
19 |
20 | After the connection is closed, contains a textual description of the reason for the connection closure, or `null` if the connection is still open.
21 |
22 | ### closeReasonCode
23 |
24 | After the connection is closed, contains the numeric close reason status code, or `-1` if the connection is still open.
25 |
26 | ### socket
27 |
28 | The underlying net.Socket instance for the connection.
29 |
30 | ### protocol
31 |
32 | The subprotocol that was chosen to be spoken on this connection. This field will have been converted to lower case.
33 |
34 | ### extensions
35 |
36 | An array of extensions that were negotiated for this connection. Currently unused, will always be an empty array.
37 |
38 | ### remoteAddress
39 |
40 | The IP address of the remote peer as a string. In the case of a server, the `X-Forwarded-For` header will be respected and preferred for the purposes of populating this field. If you need to get to the actual remote IP address, `webSocketConnection.socket.remoteAddress` will provide it.
41 |
42 | ### webSocketVersion
43 |
44 | A number indicating the version of the WebSocket protocol being spoken on this connection.
45 |
46 | ### connected
47 |
48 | A boolean value indicating whether or not the connection is still connected. *Read-only*
49 |
50 | Methods
51 | -------
52 | ### close([reasonCode], [description])
53 |
54 | Will gracefully close the connection. A close frame will be sent to the remote peer with the provided `reasonCode` and `description` indicating that we wish to close the connection, and we will then wait for up to `config.closeTimeout` milliseconds for an acknowledgment from the remote peer before terminating the underlying socket connection. The `closeTimeout` is passed as part of the `serverOptions` or `clientOptions` hashes to either the `WebSocketServer` or `WebSocketClient` constructors. Most of the time, you should call `close()` without arguments to initiate a normal connection closure. If you specify a `reasonCode` that is defined as one of the standard codes in the WebSocket protocol specification and do not provide a `description`, the default description for the given code will be used. If you would prefer not to send a description at all, pass an empty string `''`as the description parameter.
55 |
56 | ### drop([reasonCode], [description])
57 |
58 | Will send a close frame to the remote peer with the provided `reasonCode` and `description` and will immediately close the socket without waiting for a response. This should generally be used only in error conditions. The default `reasonCode` is 1002 (Protocol Error). Close reasons defined by the WebSocket protocol draft include:
59 |
60 | ```javascript
61 | WebSocketConnection.CLOSE_REASON_NORMAL = 1000;
62 | WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
63 | WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
64 | WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
65 | WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning.
66 | WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire
67 | WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire
68 | WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
69 | WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
70 | WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
71 | WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
72 | ```
73 | ### sendUTF(string)
74 |
75 | Immediately sends the specified string as a UTF-8 WebSocket message to the remote peer. If `config.fragmentOutgoingMessages` is `true` the message may be sent as multiple fragments if it exceeds `config.fragmentationThreshold` bytes. Any object that implements the `toString()` method may be passed to `sendUTF()`
76 |
77 | ### sendBytes(buffer)
78 |
79 | Immediately sends the specified Node `Buffer` object as a Binary WebSocket message to the remote peer. If `config.fragmentOutgoingMessages` is `true` the message may be sent as multiple fragments if it exceeds `config.fragmentationThreshold` bytes.
80 |
81 | ### send(data)
82 |
83 | A convenience function that will auto-detect the data type and send the appropriate WebSocket message accordingly. Immediately sends the specified data as either a UTF-8 or Binary message. If `data` is a Node Buffer, a binary message will be sent. Otherwise, the object provided must implement the `toString()` method, and the result of calling `toString()` on the `data` object will be sent as a UTF-8 message.
84 |
85 | ### ping(data)
86 |
87 | Sends a ping frame to the remote peer. `data` can be a Node `Buffer` or any object that implements `toString()`, such as a `string` or `number`. Ping frames must not exceed 125 bytes in length.
88 |
89 | ### pong(buffer)
90 |
91 | Sends a pong frame to the remote peer. Pong frames may be sent unsolicited and such pong frames will trigger no action on the receiving peer. Pong frames sent in response to a ping frame must mirror the payload data of the ping frame exactly. The `WebSocketConnection` object handles this internally for you, so there should be no need to use this method to respond to pings unless you explicitly cancel() this internal behavior (see ping event below). Pong frames must not exceed 125 bytes in length.
92 |
93 | ### sendFrame(webSocketFrame)
94 |
95 | Serializes a `WebSocketFrame` object into binary data and immediately sends it to the remote peer. This is an advanced function, requiring you to manually compose your own `WebSocketFrame`. You should probably use `sendUTF` or `sendBytes` instead.
96 |
97 | Events
98 | ------
99 | ### message
100 | `function(message)`
101 |
102 | Emitted whenever a complete single-frame message is received, or if `config.assembleFragments` is `true` (the default), it will also be emitted with a complete message assembled from multiple fragmented frames. This is the primary event to listen for to receive messages from the remote peer. The `message` object looks like the following:
103 |
104 | ```javascript
105 | // For Text Frames:
106 | {
107 | type: "utf8",
108 | utf8Data: "A string containing the received message."
109 | }
110 |
111 | // For Binary Frames:
112 | {
113 | type: "binary",
114 | binaryData: binaryDataBuffer // a Buffer object containing the binary message payload
115 | }
116 | ```
117 |
118 | ### frame
119 | `function(webSocketFrame)`
120 |
121 | This event is emitted only if `config.assembleFragments` is `false` (default is `true`). This allows you to handle individual fragments as they are received without waiting on `WebSocketConnection` to buffer them into a single `message` event for you. This may be desirable if you are working with streaming data, as it is possible to send fragments continually without ever stopping. `webSocketFrame` is an instance of `WebSocketFrame` which has properties that represent all the individual fields in WebSocket's binary framing protocol.
122 |
123 | ### close
124 | `function(reasonCode, description)`
125 |
126 | This event is emitted when the connection has been fully closed and the socket is no longer connected. `reasonCode` is the numeric reason code for the connection closure. `description` is a textual explanation for the connection closure, if available.
127 |
128 | ### error
129 | `function(error)`
130 |
131 | This event is emitted when there has been a socket error. If this occurs, a `close` event will also be emitted.
132 |
133 | ### ping
134 | `function(cancel, data)`
135 |
136 | This event is emitted when the connection receives a `ping` from the peer. `cancel` is a function taking no arguments that when called prevents the WebSocketConnection object from automatically replying with a `pong`. `data` is the binary payload contained in the ping frame.
137 |
138 | ### pong
139 | `function(data)`
140 |
141 | This event is emitted when the connection receives a `pong` from the peer. `data` is the binary data contained in the pong frame.
142 |
--------------------------------------------------------------------------------
/docs/WebSocketFrame.md:
--------------------------------------------------------------------------------
1 | WebSocketFrame
2 | ==============
3 |
4 | * [Constructor](#constructor)
5 | * [Properties](#properties)
6 |
7 | `var WebSocketFrame = require('websocket').frame`
8 |
9 | This object represents the low level individual frame and is used to drive how the bytes are serialized onto the wire.
10 |
11 | Constructor
12 | -----------
13 | ```javascript
14 | new WebSocketFrame();
15 | ```
16 |
17 | Properties
18 | ----------
19 |
20 | ### fin
21 | *Boolean*
22 |
23 | Indicates that this is either the only frame in a message, or the last frame in a fragmentation sequence.
24 |
25 | ### rsv1
26 | *Boolean*
27 |
28 | Represents the RSV1 field in the framing, which is currently not used. Setting this to true will result in a Protocol Error on the receiving peer.
29 |
30 | ### rsv2
31 | *Boolean*
32 |
33 | Represents the RSV2 field in the framing, which is currently not used. Setting this to true will result in a Protocol Error on the receiving peer.
34 |
35 | ### rsv3
36 | *Boolean*
37 |
38 | Represents the RSV3 field in the framing, which is currently not used. Setting this to true will result in a Protocol Error on the receiving peer.
39 |
40 | ### mask
41 | *uint*
42 |
43 | Whether or not this frame is (or should be) masked. For outgoing frames, when connected as a client, this flag is automatically forced to `true` by WebSocketConnection. Outgoing frames sent from the server-side of a connection are not masked.
44 |
45 | ### opcode
46 | *uint*
47 |
48 | Identifies which kind of frame this is. List of Opcodes:
49 |
50 | Hex - Dec - Description
51 | 0x00 - 0 - Continuation
52 | 0x01 - 1 - Text Frame
53 | 0x02 - 2 - Binary Frame
54 | 0x08 - 8 - Close Frame
55 | 0x09 - 9 - Ping Frame
56 | 0x0A - 10 - Pong Frame
57 |
58 | ### length
59 | *Read-only, uint*
60 |
61 | Identifies the length of the payload data on a received frame. When sending a frame, the length will be automatically calculated from the `binaryPayload` object.
62 |
63 | ### binaryPayload
64 | *Buffer object*
65 |
66 | The binary payload data. **NOTE**: Even text frames are sent with a Buffer providing the binary payload data. When sending a UTF-8 Text Frame, you must serialize your string into a Buffer object before constructing your frame, and when receiving a UTF-8 Text Frame, you must deserialize the string from the provided Buffer object. Do not read UTF-8 data from fragmented Text Frames, as it may have fragmented the data in the middle of a UTF-8 encoded character. You should buffer all fragments of a text message before attempting to decode the UTF-8 data.
67 |
--------------------------------------------------------------------------------
/docs/WebSocketRequest.md:
--------------------------------------------------------------------------------
1 | WebSocketRequest
2 | ================
3 |
4 | * [Constructor](#constructor)
5 | * [Properties](#properties)
6 | * [Methods](#methods)
7 | * [Events](#events)
8 |
9 | This object represents a client requesting to connect to the server, and allows you to accept or reject the connection based on whatever criteria you decide.
10 |
11 | Constructor
12 | -----------
13 | This object is created internally by `WebSocketServer`.
14 |
15 | However if you need to integrate WebSocket support without mounting an instance of `WebSocketServer` to your http server directly, you can handle the `upgrade` event yourself and pass the appropriate parameters to the `WebSocketRequest` constructor. **NOTE:** You *must* pass a complete set of config options to the constructor. See the section *'Server Config Options'* above. The only option that isn't required in this context is `httpServer`.
16 |
17 | ```javascript
18 | new WebSocketRequest(socket, httpRequest, config);
19 | ```
20 |
21 | The constructor won't immediately parse and validate the handshake from the client, so you need to call `readHandshake()`, which will `throw` an error if the handshake from the client is invalid or if an error is encountered, so it must always be wrapped in a try/catch block.
22 |
23 | Properties
24 | ----------
25 | ### httpRequest
26 |
27 | A reference to the original Node HTTP request object. This may be useful in combination with some other Node-based web server, such as Express, for accessing cookies or session data.
28 |
29 |
30 | ### host
31 |
32 | A string containing the contents of the `Host` header passed by the client. This will include the port number if a non-standard port is used.
33 |
34 | Examples:
35 | ```
36 | www.example.com
37 | www.example.com:8080
38 | 127.0.0.1:3000
39 | ```
40 |
41 | ### resource
42 |
43 | A string containing the path that was requested by the client.
44 |
45 | ### resourceURL
46 |
47 | A Node URL object containing the parsed `resource`, including the query string parameters.
48 |
49 | ### remoteAddress
50 |
51 | The remote client's IP Address as a string. If an `X-Forwarded-For` header is present, the value will be taken from that header to facilitate WebSocket servers that live behind a reverse-proxy.
52 |
53 | ### websocketVersion
54 |
55 | **Deprecated, renamed to webSocketVersion**
56 |
57 | ### webSocketVersion
58 |
59 | A number indicating the version of the WebSocket protocol requested by the client.
60 |
61 | ### origin
62 |
63 | If the client is a web browser, `origin` will be a string containing the URL of the page containing the script that opened the connection. If the client is **not** a web browser, `origin` may be `null` or "*".
64 |
65 | ### requestedExtensions
66 |
67 | An array containing a list of extensions requested by the client. This is not currently used for anything. **Example:**
68 |
69 | ```javascript
70 | [
71 | {
72 | name: "simple-extension";
73 | },
74 | {
75 | name: "my-great-compression-extension",
76 | params: [
77 | {
78 | name: "compressionLevel",
79 | value: "10";
80 | }
81 | ]
82 | }
83 | ]
84 | ```
85 |
86 | ### requestedProtocols
87 |
88 | An array containing a list of strings that indicate the subprotocols the client would like to speak. The server should select the best one that it can support from the list and pass it to the accept() function when accepting the connection. Note that all the strings in the `requestedProtocols` array will have been converted to lower case, so that acceptance of a subprotocol can be case-insensitive.
89 |
90 | Methods
91 | -------
92 |
93 | ### accept(acceptedProtocol, allowedOrigin)
94 | *Returns: WebSocketConnection instance*
95 |
96 | After inspecting the WebSocketRequest's properties, call this function on the request object to accept the connection. If you don't have a particular subprotocol you wish to speak, you may pass `null` for the `acceptedProtocol` parameter. Note that the `acceptedProtocol` parameter is *case-insensitive*, and you must either pass a value that was originally requested by the client or `null`. For browser clients (in which the `origin` property would be non-null) you must pass that user's origin as the `allowedOrigin` parameter to confirm that you wish to accept connections from the given origin. The return value contains the established `WebSocketConnection` instance that can be used to communicate with the connected client.
97 |
98 | ### reject([httpStatus], [reason])
99 |
100 | If you decide to reject the connection, you must call `reject`. You may optionally pass in an HTTP Status code (such as 404) and a textual description that will be sent to the client in the form of an "X-WebSocket-Reject-Reason" header. The connection will then be closed.
101 |
102 | Events
103 | ------
104 |
105 | ### requestAccepted
106 | `function(webSocketConnection)`
107 |
108 | Emitted by the WebSocketRequest object when the `accept` method has been called and the connection has been established. `webSocketConnection` is the established `WebSocketConnection` instance that can be used to communicate with the connected client.
109 |
110 | ### requestRejected
111 | `function()`
112 |
113 | Emitted by the WebSocketRequest object when the `reject` method has been called and the connection has been terminated.
114 |
--------------------------------------------------------------------------------
/docs/WebSocketServer.md:
--------------------------------------------------------------------------------
1 | WebSocketServer
2 | ===============
3 |
4 | * [Constructor](#constructor)
5 | * [Config Options](#server-config-options)
6 | * [Properties](#properties)
7 | * [Methods](#methods)
8 | * [Events](#events)
9 |
10 | `var WebSocketServer = require('websocket').server`
11 |
12 | Constructor
13 | -----------
14 |
15 | ```javascript
16 | new WebSocketServer([serverConfig]);
17 | ```
18 |
19 | Methods
20 | -------
21 |
22 | ### mount(serverConfig)
23 |
24 | `mount` will attach the WebSocketServer instance to a Node http.Server instance. `serverConfig` is required, and is an object with configuration values. For those values, see **Server Config Options** below. If you passed `serverConfig` to the constructor, this function will automatically be invoked.
25 |
26 | ### unmount()
27 |
28 | `unmount` will detach the WebSocketServer instance from the Node http.Server instance. All existing connections are left alone and will not be affected, but no new WebSocket connections will be accepted.
29 |
30 | ### closeAllConnections()
31 |
32 | Will gracefully close all open WebSocket connections.
33 |
34 | ### shutDown()
35 |
36 | Gracefully closes all open WebSocket connections and unmounts the server from the Node http.Server instance.
37 |
38 | Server Config Options
39 | ---------------------
40 | **httpServer** - (http.Server instance) **Required**.
41 | The Node http or https server instance(s) to attach to. You can pass a single instance directly, or pass an array of instances to attach to multiple http/https servers. Passing an array is particularly useful when you want to accept encrypted and unencrypted WebSocket connections on both ws:// and wss:// protocols using the same WebSocketServer instance.
42 |
43 | **maxReceivedFrameSize** - uint - *Default: 64KiB*
44 | The maximum allowed received frame size in bytes. Single frame messages will also be limited to this maximum.
45 |
46 | **maxReceivedMessageSize** - uint - *Default: 1MiB*
47 | The maximum allowed aggregate message size (for fragmented messages) in bytes.
48 |
49 | **fragmentOutgoingMessages** - Boolean - *Default: true*
50 | Whether or not to fragment outgoing messages. If true, messages will be automatically fragmented into chunks of up to `fragmentationThreshold` bytes.
51 |
52 | **fragmentationThreshold** - uint - *Default: 16KiB*
53 | The maximum size of a frame in bytes before it is automatically fragmented.
54 |
55 | **keepalive** - boolean - *Default: true*
56 | If true, the server will automatically send a ping to all clients every `keepaliveInterval` milliseconds. Each client has an independent keepalive timer, which is reset when any data is received from that client.
57 |
58 | **keepaliveInterval** - uint - *Default: 20000*
59 | The interval in milliseconds to send keepalive pings to connected clients.
60 |
61 | **dropConnectionOnKeepaliveTimeout** - boolean - *Default: true*
62 | If true, the server will consider any connection that has not received any data within the amount of time specified by `keepaliveGracePeriod` after a keepalive ping has been sent to be dead and will drop the connection once that grace period has elapsed without any incoming data. Ignored if `keepalive` is false.
63 |
64 | **keepaliveGracePeriod** - uint - *Default: 10000*
65 | The amount of time to wait after sending a keepalive ping before closing the connection if the connected peer does not respond. Ignored if `keepalive` or `dropConnectionOnKeepaliveTimeout` are false. The grace period timer is reset when any data is received from the client.
66 |
67 | **assembleFragments** - boolean - *Default: true*
68 | If true, fragmented messages will be automatically assembled and the full message will be emitted via a `message` event. If false, each frame will be emitted on the WebSocketConnection object via a `frame` event and the application will be responsible for aggregating multiple fragmented frames. Single-frame messages will emit a `message` event in addition to the `frame` event. Most users will want to leave this set to `true`.
69 |
70 | **autoAcceptConnections** - boolean - *Default: false*
71 | If this is true, websocket connections will be accepted regardless of the path and protocol specified by the client. The protocol accepted will be the first that was requested by the client. Clients from any origin will be accepted. This should only be used in the simplest of cases. You should probably leave this set to `false`; and inspect the request object to make sure it's acceptable before accepting it.
72 |
73 | **closeTimeout** - uint - *Default: 5000*
74 | The number of milliseconds to wait after sending a close frame for an acknowledgement to come back before giving up and just closing the socket.
75 |
76 | **disableNagleAlgorithm** - boolean - *Default: true*
77 | The Nagle Algorithm makes more efficient use of network resources by introducing a small delay before sending small packets so that multiple messages can be batched together before going onto the wire. This however comes at the cost of latency, so the default is to disable it. If you don't need low latency and are streaming lots of small messages, you can change this to 'false';
78 |
79 | **ignoreXForwardedFor** - Boolean - *Default: false*
80 | Whether or not the `X-Forwarded-For` header should be respected.
81 | It's important to set this to 'true' when accepting connections
82 | from untrusted clients, as a malicious client could spoof its
83 | IP address by simply setting this header. It's meant to be added
84 | by a trusted proxy or other intermediary within your own
85 | infrastructure.
86 | More info: [X-Forwarded-For on Wikipedia](http://en.wikipedia.org/wiki/X-Forwarded-For)
87 |
88 | **parseExtensions** - Boolean - *Default: true*
89 | Whether or not to parse 'sec-websocket-extension' headers. Array is exposed to WebSocketRequest.requestedExtensions.
90 |
91 | **parseCookies** - Boolean - *Default: true*
92 | Whether or not to parse 'cookie' headers. Array is exposed to WebSocketRequest.cookies.
93 |
94 | Events
95 | ------
96 | There are three events emitted by a WebSocketServer instance that allow you to handle incoming requests, establish connections, and detect when a connection has been closed.
97 |
98 | ### request
99 | `function(webSocketRequest)`
100 |
101 | If `autoAcceptConnections` is set to `false`, a `request` event will be emitted by the server whenever a new WebSocket request is made. You should inspect the requested protocols and the user's origin to verify the connection, and then accept or reject it by calling webSocketRequest.accept('chosen-protocol', 'accepted-origin') or webSocketRequest.reject()
102 |
103 | ### connect
104 | `function(webSocketConnection)`
105 |
106 | Emitted whenever a new WebSocket connection is accepted.
107 |
108 | ### close
109 | `function(webSocketConnection, closeReason, description)`
110 |
111 | Whenever a connection is closed for any reason, the WebSocketServer instance will emit a `close` event, passing a reference to the WebSocketConnection instance that was closed. `closeReason` is the numeric reason status code for the connection closure, and `description` is a textual description of the close reason, if available.
112 |
113 | ### upgradeError
114 | `function(error)`
115 |
116 | Emitted whenever a WebSocket error happens during the connection upgrade phase. Note that the WS connection is automatically rejected with a 400 error code, this event just exposes the error.
117 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | WebSocket-Node Documentation
2 | ============================
3 |
4 | WebSocket-Node includes both client and server functionality, available through WebSocketClient and WebSocketServer respectively. Once a connection is established, the API for sending and receiving messages is identical whether you're acting as a client or server.
5 |
6 | Click on one of the classes below to view its API documentation.
7 |
8 | * [WebSocketClient](./WebSocketClient.md)
9 | * [WebSocketConnection](./WebSocketConnection.md)
10 | * [WebSocketFrame](./WebSocketFrame.md)
11 | * [WebSocketRequest](./WebSocketRequest.md)
12 | * [WebSocketServer](./WebSocketServer.md)
13 | * [W3CWebSocket](./W3CWebSocket.md)
14 |
--------------------------------------------------------------------------------
/example/whiteboard/README:
--------------------------------------------------------------------------------
1 | To run the whiteboard example, make sure to run..
2 |
3 | npm install "express@2.3.11" "ejs@0.4.3"
4 |
5 | ..from within the 'whiteboard' folder, then fire up the example server with..
6 |
7 | node ./whiteboard.js
8 |
9 | ..and navigate to http://localhost:8080 from a browser supporting draft-09
10 | of the WebSockets specification.
11 |
--------------------------------------------------------------------------------
/example/whiteboard/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
30 |
31 |
32 |
33 |
34 |
36 |
37 |
39 |
40 |
42 |
43 |
45 |
46 |
47 |
48 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/example/whiteboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "websocket-whiteboard-example",
3 | "description": "Whiteboard example for websocket-node.",
4 | "author": "Brian McKelvey (https://www.worlize.com/)",
5 | "version": "1.0.0",
6 | "dependencies": {
7 | "express": "~2",
8 | "ejs": "*"
9 | }
10 | }
--------------------------------------------------------------------------------
/example/whiteboard/public/client.css:
--------------------------------------------------------------------------------
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 | h1 {
18 | font-size: 16pt;
19 | margin: 0px;
20 | }
21 |
22 | p {
23 | margin: 0px;
24 | }
25 |
26 | canvas {
27 | border: 1px solid black;
28 | cursor: crosshair;
29 | }
30 |
--------------------------------------------------------------------------------
/example/whiteboard/public/client.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 | function Whiteboard(canvasId) {
18 | this.initCanvas(canvasId);
19 |
20 | // Define accepted commands
21 | this.messageHandlers = {
22 | initCommands: this.initCommands.bind(this),
23 | drawLine: this.drawLine.bind(this),
24 | clear: this.clear.bind(this)
25 | };
26 |
27 | // Initial state
28 | this.lastPoint = null;
29 | this.mouseDown = false;
30 | this.color = {
31 | r: 0,
32 | g: 0,
33 | b: 0
34 | };
35 | };
36 |
37 | Whiteboard.prototype.connect = function() {
38 | var url = "ws://" + document.URL.substr(7).split('/')[0];
39 |
40 | var wsCtor = window['MozWebSocket'] ? MozWebSocket : WebSocket;
41 | this.socket = new wsCtor(url, 'whiteboard-example');
42 |
43 | this.socket.onmessage = this.handleWebsocketMessage.bind(this);
44 | this.socket.onclose = this.handleWebsocketClose.bind(this);
45 |
46 | this.addCanvasEventListeners();
47 | };
48 |
49 | Whiteboard.prototype.handleWebsocketMessage = function(message) {
50 | try {
51 | var command = JSON.parse(message.data);
52 | }
53 | catch(e) { /* do nothing */ }
54 |
55 | if (command) {
56 | this.dispatchCommand(command);
57 | }
58 | };
59 |
60 | Whiteboard.prototype.handleWebsocketClose = function() {
61 | alert("WebSocket Connection Closed.");
62 | };
63 |
64 | Whiteboard.prototype.dispatchCommand = function(command) {
65 | // Do we have a handler function for this command?
66 | var handler = this.messageHandlers[command.msg];
67 | if (typeof(handler) === 'function') {
68 | // If so, call it and pass the parameter data
69 | handler.call(this, command.data);
70 | }
71 | };
72 |
73 | Whiteboard.prototype.initCommands = function(commandList) {
74 | /* Upon connection, the contents of the whiteboard
75 | are drawn by replaying all commands since the
76 | last time it was cleared */
77 | commandList.forEach(function(command) {
78 | this.dispatchCommand(command);
79 | }.bind(this));
80 | };
81 |
82 | Whiteboard.prototype.sendClear = function() {
83 | this.socket.send(JSON.stringify({ msg: 'clear' }));
84 | };
85 |
86 | Whiteboard.prototype.setColor = function(r,g,b) {
87 | this.color = {
88 | r: r,
89 | g: g,
90 | b: b
91 | };
92 | };
93 |
94 | Whiteboard.prototype.drawLine = function(data) {
95 | // Set the color
96 | var color = data.color;
97 | this.ctx.strokeStyle = 'rgb(' + color.r + "," + color.g + "," + color.b +')';
98 |
99 | this.ctx.beginPath();
100 |
101 | var points = data.points;
102 | // Starting point
103 | this.ctx.moveTo(points[0]+0.5, points[1]+0.5);
104 |
105 | // Ending point
106 | this.ctx.lineTo(points[2]+0.5, points[3]+0.5);
107 |
108 | this.ctx.stroke();
109 | };
110 |
111 | Whiteboard.prototype.clear = function() {
112 | this.canvas.width = this.canvas.width;
113 | };
114 |
115 | Whiteboard.prototype.handleMouseDown = function(event) {
116 | this.mouseDown = true;
117 | this.lastPoint = this.resolveMousePosition(event);
118 | };
119 |
120 | Whiteboard.prototype.handleMouseUp = function(event) {
121 | this.mouseDown = false;
122 | this.lastPoint = null;
123 | };
124 |
125 | Whiteboard.prototype.handleMouseMove = function(event) {
126 | if (!this.mouseDown) { return; }
127 |
128 | var currentPoint = this.resolveMousePosition(event);
129 |
130 | // Send a draw command to the server.
131 | // The actual line is drawn when the command
132 | // is received back from the server.
133 | this.socket.send(JSON.stringify({
134 | msg: 'drawLine',
135 | data: {
136 | color: this.color,
137 | points: [
138 | this.lastPoint.x,
139 | this.lastPoint.y,
140 | currentPoint.x,
141 | currentPoint.y
142 | ]
143 | }
144 | }));
145 |
146 | this.lastPoint = currentPoint;
147 | };
148 |
149 | Whiteboard.prototype.initCanvas = function(canvasId) {
150 | this.canvasId = canvasId;
151 | this.canvas = document.getElementById(canvasId);
152 | this.ctx = this.canvas.getContext('2d');
153 | this.initCanvasOffset();
154 | };
155 |
156 | Whiteboard.prototype.initCanvasOffset = function() {
157 | this.offsetX = this.offsetY = 0;
158 | var element = this.canvas;
159 | if (element.offsetParent) {
160 | do {
161 | this.offsetX += element.offsetLeft;
162 | this.offsetY += element.offsetTop;
163 | }
164 | while ((element = element.offsetParent));
165 | }
166 | };
167 |
168 | Whiteboard.prototype.addCanvasEventListeners = function() {
169 | this.canvas.addEventListener(
170 | 'mousedown', this.handleMouseDown.bind(this), false);
171 |
172 | window.document.addEventListener(
173 | 'mouseup', this.handleMouseUp.bind(this), false);
174 |
175 | this.canvas.addEventListener(
176 | 'mousemove', this.handleMouseMove.bind(this), false);
177 | };
178 |
179 | Whiteboard.prototype.resolveMousePosition = function(event) {
180 | var x, y;
181 | if (event.offsetX) {
182 | x = event.offsetX;
183 | y = event.offsetY;
184 | } else {
185 | x = event.layerX - this.offsetX;
186 | y = event.layerY - this.offsetY;
187 | }
188 | return { x: x, y: y };
189 | };
190 |
--------------------------------------------------------------------------------
/example/whiteboard/whiteboard.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 | var WebSocketServer = require('../../lib/websocket').server;
19 | var express = require('express');
20 |
21 | var app = express.createServer();
22 |
23 | app.configure(function() {
24 | app.use(express.static(__dirname + "/public"));
25 | app.set('views', __dirname);
26 | app.set('view engine', 'ejs');
27 | });
28 | app.get('/', function(req, res) {
29 | res.render('index', { layout: false });
30 | });
31 | app.listen(8080);
32 |
33 |
34 | var wsServer = new WebSocketServer({
35 | httpServer: app,
36 |
37 | // Firefox 7 alpha has a bug that drops the
38 | // connection on large fragmented messages
39 | fragmentOutgoingMessages: false
40 | });
41 |
42 | var connections = [];
43 | var canvasCommands = [];
44 |
45 | wsServer.on('request', function(request) {
46 | var connection = request.accept('whiteboard-example', request.origin);
47 | connections.push(connection);
48 |
49 | console.log(connection.remoteAddress + " connected - Protocol Version " + connection.webSocketVersion);
50 |
51 | // Send all the existing canvas commands to the new client
52 | connection.sendUTF(JSON.stringify({
53 | msg: "initCommands",
54 | data: canvasCommands
55 | }));
56 |
57 | // Handle closed connections
58 | connection.on('close', function() {
59 | console.log(connection.remoteAddress + " disconnected");
60 |
61 | var index = connections.indexOf(connection);
62 | if (index !== -1) {
63 | // remove the connection from the pool
64 | connections.splice(index, 1);
65 | }
66 | });
67 |
68 | // Handle incoming messages
69 | connection.on('message', function(message) {
70 | if (message.type === 'utf8') {
71 | try {
72 | var command = JSON.parse(message.utf8Data);
73 |
74 | if (command.msg === 'clear') {
75 | canvasCommands = [];
76 | }
77 | else {
78 | canvasCommands.push(command);
79 | }
80 |
81 | // rebroadcast command to all clients
82 | connections.forEach(function(destination) {
83 | destination.sendUTF(message.utf8Data);
84 | });
85 | }
86 | catch(e) {
87 | // do nothing if there's an error.
88 | }
89 | }
90 | });
91 | });
92 |
93 | console.log("Whiteboard test app ready");
94 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Dependencies.
3 | */
4 | var gulp = require('gulp');
5 | var jshint = require('gulp-jshint');
6 |
7 | gulp.task('lint', function() {
8 | return gulp.src(['gulpfile.js', 'lib/**/*.js', 'test/**/*.js'])
9 | .pipe(jshint('.jshintrc'))
10 | .pipe(jshint.reporter('jshint-stylish', {verbose: true}))
11 | .pipe(jshint.reporter('fail'));
12 | });
13 |
14 | gulp.task('default', gulp.series('lint'));
15 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/websocket');
--------------------------------------------------------------------------------
/lib/Deprecation.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 Deprecation = {
18 | disableWarnings: false,
19 |
20 | deprecationWarningMap: {
21 |
22 | },
23 |
24 | warn: function(deprecationName) {
25 | if (!this.disableWarnings && this.deprecationWarningMap[deprecationName]) {
26 | console.warn('DEPRECATION WARNING: ' + this.deprecationWarningMap[deprecationName]);
27 | this.deprecationWarningMap[deprecationName] = false;
28 | }
29 | }
30 | };
31 |
32 | module.exports = Deprecation;
33 |
--------------------------------------------------------------------------------
/lib/W3CWebSocket.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 WebSocketClient = require('./WebSocketClient');
18 | var toBuffer = require('typedarray-to-buffer');
19 | var yaeti = require('yaeti');
20 |
21 |
22 | const CONNECTING = 0;
23 | const OPEN = 1;
24 | const CLOSING = 2;
25 | const CLOSED = 3;
26 |
27 |
28 | module.exports = W3CWebSocket;
29 |
30 |
31 | function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientConfig) {
32 | // Make this an EventTarget.
33 | yaeti.EventTarget.call(this);
34 |
35 | // Sanitize clientConfig.
36 | clientConfig = clientConfig || {};
37 | clientConfig.assembleFragments = true; // Required in the W3C API.
38 |
39 | var self = this;
40 |
41 | this._url = url;
42 | this._readyState = CONNECTING;
43 | this._protocol = undefined;
44 | this._extensions = '';
45 | this._bufferedAmount = 0; // Hack, always 0.
46 | this._binaryType = 'arraybuffer'; // TODO: Should be 'blob' by default, but Node has no Blob.
47 |
48 | // The WebSocketConnection instance.
49 | this._connection = undefined;
50 |
51 | // WebSocketClient instance.
52 | this._client = new WebSocketClient(clientConfig);
53 |
54 | this._client.on('connect', function(connection) {
55 | onConnect.call(self, connection);
56 | });
57 |
58 | this._client.on('connectFailed', function() {
59 | onConnectFailed.call(self);
60 | });
61 |
62 | this._client.connect(url, protocols, origin, headers, requestOptions);
63 | }
64 |
65 |
66 | // Expose W3C read only attributes.
67 | Object.defineProperties(W3CWebSocket.prototype, {
68 | url: { get: function() { return this._url; } },
69 | readyState: { get: function() { return this._readyState; } },
70 | protocol: { get: function() { return this._protocol; } },
71 | extensions: { get: function() { return this._extensions; } },
72 | bufferedAmount: { get: function() { return this._bufferedAmount; } }
73 | });
74 |
75 |
76 | // Expose W3C write/read attributes.
77 | Object.defineProperties(W3CWebSocket.prototype, {
78 | binaryType: {
79 | get: function() {
80 | return this._binaryType;
81 | },
82 | set: function(type) {
83 | // TODO: Just 'arraybuffer' supported.
84 | if (type !== 'arraybuffer') {
85 | throw new SyntaxError('just "arraybuffer" type allowed for "binaryType" attribute');
86 | }
87 | this._binaryType = type;
88 | }
89 | }
90 | });
91 |
92 |
93 | // Expose W3C readyState constants into the WebSocket instance as W3C states.
94 | [['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) {
95 | Object.defineProperty(W3CWebSocket.prototype, property[0], {
96 | get: function() { return property[1]; }
97 | });
98 | });
99 |
100 | // Also expose W3C readyState constants into the WebSocket class (not defined by the W3C,
101 | // but there are so many libs relying on them).
102 | [['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) {
103 | Object.defineProperty(W3CWebSocket, property[0], {
104 | get: function() { return property[1]; }
105 | });
106 | });
107 |
108 |
109 | W3CWebSocket.prototype.send = function(data) {
110 | if (this._readyState !== OPEN) {
111 | throw new Error('cannot call send() while not connected');
112 | }
113 |
114 | // Text.
115 | if (typeof data === 'string' || data instanceof String) {
116 | this._connection.sendUTF(data);
117 | }
118 | // Binary.
119 | else {
120 | // Node Buffer.
121 | if (data instanceof Buffer) {
122 | this._connection.sendBytes(data);
123 | }
124 | // If ArrayBuffer or ArrayBufferView convert it to Node Buffer.
125 | else if (data.byteLength || data.byteLength === 0) {
126 | data = toBuffer(data);
127 | this._connection.sendBytes(data);
128 | }
129 | else {
130 | throw new Error('unknown binary data:', data);
131 | }
132 | }
133 | };
134 |
135 |
136 | W3CWebSocket.prototype.close = function(code, reason) {
137 | switch(this._readyState) {
138 | case CONNECTING:
139 | // NOTE: We don't have the WebSocketConnection instance yet so no
140 | // way to close the TCP connection.
141 | // Artificially invoke the onConnectFailed event.
142 | onConnectFailed.call(this);
143 | // And close if it connects after a while.
144 | this._client.on('connect', function(connection) {
145 | if (code) {
146 | connection.close(code, reason);
147 | } else {
148 | connection.close();
149 | }
150 | });
151 | break;
152 | case OPEN:
153 | this._readyState = CLOSING;
154 | if (code) {
155 | this._connection.close(code, reason);
156 | } else {
157 | this._connection.close();
158 | }
159 | break;
160 | case CLOSING:
161 | case CLOSED:
162 | break;
163 | }
164 | };
165 |
166 |
167 | /**
168 | * Private API.
169 | */
170 |
171 |
172 | function createCloseEvent(code, reason) {
173 | var event = new yaeti.Event('close');
174 |
175 | event.code = code;
176 | event.reason = reason;
177 | event.wasClean = (typeof code === 'undefined' || code === 1000);
178 |
179 | return event;
180 | }
181 |
182 |
183 | function createMessageEvent(data) {
184 | var event = new yaeti.Event('message');
185 |
186 | event.data = data;
187 |
188 | return event;
189 | }
190 |
191 |
192 | function onConnect(connection) {
193 | var self = this;
194 |
195 | this._readyState = OPEN;
196 | this._connection = connection;
197 | this._protocol = connection.protocol;
198 | this._extensions = connection.extensions;
199 |
200 | this._connection.on('close', function(code, reason) {
201 | onClose.call(self, code, reason);
202 | });
203 |
204 | this._connection.on('message', function(msg) {
205 | onMessage.call(self, msg);
206 | });
207 |
208 | this.dispatchEvent(new yaeti.Event('open'));
209 | }
210 |
211 |
212 | function onConnectFailed() {
213 | destroy.call(this);
214 | this._readyState = CLOSED;
215 |
216 | try {
217 | this.dispatchEvent(new yaeti.Event('error'));
218 | } finally {
219 | this.dispatchEvent(createCloseEvent(1006, 'connection failed'));
220 | }
221 | }
222 |
223 |
224 | function onClose(code, reason) {
225 | destroy.call(this);
226 | this._readyState = CLOSED;
227 |
228 | this.dispatchEvent(createCloseEvent(code, reason || ''));
229 | }
230 |
231 |
232 | function onMessage(message) {
233 | if (message.utf8Data) {
234 | this.dispatchEvent(createMessageEvent(message.utf8Data));
235 | }
236 | else if (message.binaryData) {
237 | // Must convert from Node Buffer to ArrayBuffer.
238 | // TODO: or to a Blob (which does not exist in Node!).
239 | if (this.binaryType === 'arraybuffer') {
240 | var buffer = message.binaryData;
241 | var arraybuffer = new ArrayBuffer(buffer.length);
242 | var view = new Uint8Array(arraybuffer);
243 | for (var i=0, len=buffer.length; i', '@',
30 | ',', ';', ':', '\\', '\"',
31 | '/', '[', ']', '?', '=',
32 | '{', '}', ' ', String.fromCharCode(9)
33 | ];
34 |
35 | var excludedTlsOptions = ['hostname','port','method','path','headers'];
36 |
37 | function WebSocketClient(config) {
38 | // Superclass Constructor
39 | EventEmitter.call(this);
40 |
41 | // TODO: Implement extensions
42 |
43 | this.config = {
44 | // 1MiB max frame size.
45 | maxReceivedFrameSize: 0x100000,
46 |
47 | // 8MiB max message size, only applicable if
48 | // assembleFragments is true
49 | maxReceivedMessageSize: 0x800000,
50 |
51 | // Outgoing messages larger than fragmentationThreshold will be
52 | // split into multiple fragments.
53 | fragmentOutgoingMessages: true,
54 |
55 | // Outgoing frames are fragmented if they exceed this threshold.
56 | // Default is 16KiB
57 | fragmentationThreshold: 0x4000,
58 |
59 | // Which version of the protocol to use for this session. This
60 | // option will be removed once the protocol is finalized by the IETF
61 | // It is only available to ease the transition through the
62 | // intermediate draft protocol versions.
63 | // At present, it only affects the name of the Origin header.
64 | webSocketVersion: 13,
65 |
66 | // If true, fragmented messages will be automatically assembled
67 | // and the full message will be emitted via a 'message' event.
68 | // If false, each frame will be emitted via a 'frame' event and
69 | // the application will be responsible for aggregating multiple
70 | // fragmented frames. Single-frame messages will emit a 'message'
71 | // event in addition to the 'frame' event.
72 | // Most users will want to leave this set to 'true'
73 | assembleFragments: true,
74 |
75 | // The Nagle Algorithm makes more efficient use of network resources
76 | // by introducing a small delay before sending small packets so that
77 | // multiple messages can be batched together before going onto the
78 | // wire. This however comes at the cost of latency, so the default
79 | // is to disable it. If you don't need low latency and are streaming
80 | // lots of small messages, you can change this to 'false'
81 | disableNagleAlgorithm: true,
82 |
83 | // The number of milliseconds to wait after sending a close frame
84 | // for an acknowledgement to come back before giving up and just
85 | // closing the socket.
86 | closeTimeout: 5000,
87 |
88 | // Options to pass to https.connect if connecting via TLS
89 | tlsOptions: {}
90 | };
91 |
92 | if (config) {
93 | var tlsOptions;
94 | if (config.tlsOptions) {
95 | tlsOptions = config.tlsOptions;
96 | delete config.tlsOptions;
97 | }
98 | else {
99 | tlsOptions = {};
100 | }
101 | extend(this.config, config);
102 | extend(this.config.tlsOptions, tlsOptions);
103 | }
104 |
105 | this._req = null;
106 |
107 | switch (this.config.webSocketVersion) {
108 | case 8:
109 | case 13:
110 | break;
111 | default:
112 | throw new Error('Requested webSocketVersion is not supported. Allowed values are 8 and 13.');
113 | }
114 | }
115 |
116 | util.inherits(WebSocketClient, EventEmitter);
117 |
118 | WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, headers, extraRequestOptions) {
119 | var self = this;
120 |
121 | if (typeof(protocols) === 'string') {
122 | if (protocols.length > 0) {
123 | protocols = [protocols];
124 | }
125 | else {
126 | protocols = [];
127 | }
128 | }
129 | if (!(protocols instanceof Array)) {
130 | protocols = [];
131 | }
132 | this.protocols = protocols;
133 | this.origin = origin;
134 |
135 | if (typeof(requestUrl) === 'string') {
136 | this.url = url.parse(requestUrl);
137 | }
138 | else {
139 | this.url = requestUrl; // in case an already parsed url is passed in.
140 | }
141 | if (!this.url.protocol) {
142 | throw new Error('You must specify a full WebSocket URL, including protocol.');
143 | }
144 | if (!this.url.host) {
145 | throw new Error('You must specify a full WebSocket URL, including hostname. Relative URLs are not supported.');
146 | }
147 |
148 | this.secure = (this.url.protocol === 'wss:');
149 |
150 | // validate protocol characters:
151 | this.protocols.forEach(function(protocol) {
152 | for (var i=0; i < protocol.length; i ++) {
153 | var charCode = protocol.charCodeAt(i);
154 | var character = protocol.charAt(i);
155 | if (charCode < 0x0021 || charCode > 0x007E || protocolSeparators.indexOf(character) !== -1) {
156 | throw new Error('Protocol list contains invalid character "' + String.fromCharCode(charCode) + '"');
157 | }
158 | }
159 | });
160 |
161 | var defaultPorts = {
162 | 'ws:': '80',
163 | 'wss:': '443'
164 | };
165 |
166 | if (!this.url.port) {
167 | this.url.port = defaultPorts[this.url.protocol];
168 | }
169 |
170 | var nonce = bufferAllocUnsafe(16);
171 | for (var i=0; i < 16; i++) {
172 | nonce[i] = Math.round(Math.random()*0xFF);
173 | }
174 | this.base64nonce = nonce.toString('base64');
175 |
176 | var hostHeaderValue = this.url.hostname;
177 | if ((this.url.protocol === 'ws:' && this.url.port !== '80') ||
178 | (this.url.protocol === 'wss:' && this.url.port !== '443')) {
179 | hostHeaderValue += (':' + this.url.port);
180 | }
181 |
182 | var reqHeaders = {};
183 | if (this.secure && this.config.tlsOptions.hasOwnProperty('headers')) {
184 | // Allow for additional headers to be provided when connecting via HTTPS
185 | extend(reqHeaders, this.config.tlsOptions.headers);
186 | }
187 | if (headers) {
188 | // Explicitly provided headers take priority over any from tlsOptions
189 | extend(reqHeaders, headers);
190 | }
191 | extend(reqHeaders, {
192 | 'Upgrade': 'websocket',
193 | 'Connection': 'Upgrade',
194 | 'Sec-WebSocket-Version': this.config.webSocketVersion.toString(10),
195 | 'Sec-WebSocket-Key': this.base64nonce,
196 | 'Host': reqHeaders.Host || hostHeaderValue
197 | });
198 |
199 | if (this.protocols.length > 0) {
200 | reqHeaders['Sec-WebSocket-Protocol'] = this.protocols.join(', ');
201 | }
202 | if (this.origin) {
203 | if (this.config.webSocketVersion === 13) {
204 | reqHeaders['Origin'] = this.origin;
205 | }
206 | else if (this.config.webSocketVersion === 8) {
207 | reqHeaders['Sec-WebSocket-Origin'] = this.origin;
208 | }
209 | }
210 |
211 | // TODO: Implement extensions
212 |
213 | var pathAndQuery;
214 | // Ensure it begins with '/'.
215 | if (this.url.pathname) {
216 | pathAndQuery = this.url.path;
217 | }
218 | else if (this.url.path) {
219 | pathAndQuery = '/' + this.url.path;
220 | }
221 | else {
222 | pathAndQuery = '/';
223 | }
224 |
225 | function handleRequestError(error) {
226 | self._req = null;
227 | self.emit('connectFailed', error);
228 | }
229 |
230 | var requestOptions = {
231 | agent: false
232 | };
233 | if (extraRequestOptions) {
234 | extend(requestOptions, extraRequestOptions);
235 | }
236 | // These options are always overridden by the library. The user is not
237 | // allowed to specify these directly.
238 | extend(requestOptions, {
239 | hostname: this.url.hostname,
240 | port: this.url.port,
241 | method: 'GET',
242 | path: pathAndQuery,
243 | headers: reqHeaders
244 | });
245 | if (this.secure) {
246 | var tlsOptions = this.config.tlsOptions;
247 | for (var key in tlsOptions) {
248 | if (tlsOptions.hasOwnProperty(key) && excludedTlsOptions.indexOf(key) === -1) {
249 | requestOptions[key] = tlsOptions[key];
250 | }
251 | }
252 | }
253 |
254 | var req = this._req = (this.secure ? https : http).request(requestOptions);
255 | req.on('upgrade', function handleRequestUpgrade(response, socket, head) {
256 | self._req = null;
257 | req.removeListener('error', handleRequestError);
258 | self.socket = socket;
259 | self.response = response;
260 | self.firstDataChunk = head;
261 | self.validateHandshake();
262 | });
263 | req.on('error', handleRequestError);
264 |
265 | req.on('response', function(response) {
266 | self._req = null;
267 | if (utils.eventEmitterListenerCount(self, 'httpResponse') > 0) {
268 | self.emit('httpResponse', response, self);
269 | if (response.socket) {
270 | response.socket.end();
271 | }
272 | }
273 | else {
274 | var headerDumpParts = [];
275 | for (var headerName in response.headers) {
276 | headerDumpParts.push(headerName + ': ' + response.headers[headerName]);
277 | }
278 | self.failHandshake(
279 | 'Server responded with a non-101 status: ' +
280 | response.statusCode + ' ' + response.statusMessage +
281 | '\nResponse Headers Follow:\n' +
282 | headerDumpParts.join('\n') + '\n'
283 | );
284 | }
285 | });
286 | req.end();
287 | };
288 |
289 | WebSocketClient.prototype.validateHandshake = function() {
290 | var headers = this.response.headers;
291 |
292 | if (this.protocols.length > 0) {
293 | this.protocol = headers['sec-websocket-protocol'];
294 | if (this.protocol) {
295 | if (this.protocols.indexOf(this.protocol) === -1) {
296 | this.failHandshake('Server did not respond with a requested protocol.');
297 | return;
298 | }
299 | }
300 | else {
301 | this.failHandshake('Expected a Sec-WebSocket-Protocol header.');
302 | return;
303 | }
304 | }
305 |
306 | if (!(headers['connection'] && headers['connection'].toLocaleLowerCase() === 'upgrade')) {
307 | this.failHandshake('Expected a Connection: Upgrade header from the server');
308 | return;
309 | }
310 |
311 | if (!(headers['upgrade'] && headers['upgrade'].toLocaleLowerCase() === 'websocket')) {
312 | this.failHandshake('Expected an Upgrade: websocket header from the server');
313 | return;
314 | }
315 |
316 | var sha1 = crypto.createHash('sha1');
317 | sha1.update(this.base64nonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
318 | var expectedKey = sha1.digest('base64');
319 |
320 | if (!headers['sec-websocket-accept']) {
321 | this.failHandshake('Expected Sec-WebSocket-Accept header from server');
322 | return;
323 | }
324 |
325 | if (headers['sec-websocket-accept'] !== expectedKey) {
326 | this.failHandshake('Sec-WebSocket-Accept header from server didn\'t match expected value of ' + expectedKey);
327 | return;
328 | }
329 |
330 | // TODO: Support extensions
331 |
332 | this.succeedHandshake();
333 | };
334 |
335 | WebSocketClient.prototype.failHandshake = function(errorDescription) {
336 | if (this.socket && this.socket.writable) {
337 | this.socket.end();
338 | }
339 | this.emit('connectFailed', new Error(errorDescription));
340 | };
341 |
342 | WebSocketClient.prototype.succeedHandshake = function() {
343 | var connection = new WebSocketConnection(this.socket, [], this.protocol, true, this.config);
344 |
345 | connection.webSocketVersion = this.config.webSocketVersion;
346 | connection._addSocketEventListeners();
347 |
348 | this.emit('connect', connection);
349 | if (this.firstDataChunk.length > 0) {
350 | connection.handleSocketData(this.firstDataChunk);
351 | }
352 | this.firstDataChunk = null;
353 | };
354 |
355 | WebSocketClient.prototype.abort = function() {
356 | if (this._req) {
357 | this._req.abort();
358 | }
359 | };
360 |
361 | module.exports = WebSocketClient;
362 |
--------------------------------------------------------------------------------
/lib/WebSocketFrame.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 bufferUtil = require('bufferutil');
18 | var bufferAllocUnsafe = require('./utils').bufferAllocUnsafe;
19 |
20 | const DECODE_HEADER = 1;
21 | const WAITING_FOR_16_BIT_LENGTH = 2;
22 | const WAITING_FOR_64_BIT_LENGTH = 3;
23 | const WAITING_FOR_MASK_KEY = 4;
24 | const WAITING_FOR_PAYLOAD = 5;
25 | const COMPLETE = 6;
26 |
27 | // WebSocketConnection will pass shared buffer objects for maskBytes and
28 | // frameHeader into the constructor to avoid tons of small memory allocations
29 | // for each frame we have to parse. This is only used for parsing frames
30 | // we receive off the wire.
31 | function WebSocketFrame(maskBytes, frameHeader, config) {
32 | this.maskBytes = maskBytes;
33 | this.frameHeader = frameHeader;
34 | this.config = config;
35 | this.maxReceivedFrameSize = config.maxReceivedFrameSize;
36 | this.protocolError = false;
37 | this.frameTooLarge = false;
38 | this.invalidCloseFrameLength = false;
39 | this.parseState = DECODE_HEADER;
40 | this.closeStatus = -1;
41 | }
42 |
43 | WebSocketFrame.prototype.addData = function(bufferList) {
44 | if (this.parseState === DECODE_HEADER) {
45 | if (bufferList.length >= 2) {
46 | bufferList.joinInto(this.frameHeader, 0, 0, 2);
47 | bufferList.advance(2);
48 | var firstByte = this.frameHeader[0];
49 | var secondByte = this.frameHeader[1];
50 |
51 | this.fin = Boolean(firstByte & 0x80);
52 | this.rsv1 = Boolean(firstByte & 0x40);
53 | this.rsv2 = Boolean(firstByte & 0x20);
54 | this.rsv3 = Boolean(firstByte & 0x10);
55 | this.mask = Boolean(secondByte & 0x80);
56 |
57 | this.opcode = firstByte & 0x0F;
58 | this.length = secondByte & 0x7F;
59 |
60 | // Control frame sanity check
61 | if (this.opcode >= 0x08) {
62 | if (this.length > 125) {
63 | this.protocolError = true;
64 | this.dropReason = 'Illegal control frame longer than 125 bytes.';
65 | return true;
66 | }
67 | if (!this.fin) {
68 | this.protocolError = true;
69 | this.dropReason = 'Control frames must not be fragmented.';
70 | return true;
71 | }
72 | }
73 |
74 | if (this.length === 126) {
75 | this.parseState = WAITING_FOR_16_BIT_LENGTH;
76 | }
77 | else if (this.length === 127) {
78 | this.parseState = WAITING_FOR_64_BIT_LENGTH;
79 | }
80 | else {
81 | this.parseState = WAITING_FOR_MASK_KEY;
82 | }
83 | }
84 | }
85 | if (this.parseState === WAITING_FOR_16_BIT_LENGTH) {
86 | if (bufferList.length >= 2) {
87 | bufferList.joinInto(this.frameHeader, 2, 0, 2);
88 | bufferList.advance(2);
89 | this.length = this.frameHeader.readUInt16BE(2);
90 | this.parseState = WAITING_FOR_MASK_KEY;
91 | }
92 | }
93 | else if (this.parseState === WAITING_FOR_64_BIT_LENGTH) {
94 | if (bufferList.length >= 8) {
95 | bufferList.joinInto(this.frameHeader, 2, 0, 8);
96 | bufferList.advance(8);
97 | var lengthPair = [
98 | this.frameHeader.readUInt32BE(2),
99 | this.frameHeader.readUInt32BE(2+4)
100 | ];
101 |
102 | if (lengthPair[0] !== 0) {
103 | this.protocolError = true;
104 | this.dropReason = 'Unsupported 64-bit length frame received';
105 | return true;
106 | }
107 | this.length = lengthPair[1];
108 | this.parseState = WAITING_FOR_MASK_KEY;
109 | }
110 | }
111 |
112 | if (this.parseState === WAITING_FOR_MASK_KEY) {
113 | if (this.mask) {
114 | if (bufferList.length >= 4) {
115 | bufferList.joinInto(this.maskBytes, 0, 0, 4);
116 | bufferList.advance(4);
117 | this.parseState = WAITING_FOR_PAYLOAD;
118 | }
119 | }
120 | else {
121 | this.parseState = WAITING_FOR_PAYLOAD;
122 | }
123 | }
124 |
125 | if (this.parseState === WAITING_FOR_PAYLOAD) {
126 | if (this.length > this.maxReceivedFrameSize) {
127 | this.frameTooLarge = true;
128 | this.dropReason = 'Frame size of ' + this.length.toString(10) +
129 | ' bytes exceeds maximum accepted frame size';
130 | return true;
131 | }
132 |
133 | if (this.length === 0) {
134 | this.binaryPayload = bufferAllocUnsafe(0);
135 | this.parseState = COMPLETE;
136 | return true;
137 | }
138 | if (bufferList.length >= this.length) {
139 | this.binaryPayload = bufferList.take(this.length);
140 | bufferList.advance(this.length);
141 | if (this.mask) {
142 | bufferUtil.unmask(this.binaryPayload, this.maskBytes);
143 | // xor(this.binaryPayload, this.maskBytes, 0);
144 | }
145 |
146 | if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE
147 | if (this.length === 1) {
148 | // Invalid length for a close frame. Must be zero or at least two.
149 | this.binaryPayload = bufferAllocUnsafe(0);
150 | this.invalidCloseFrameLength = true;
151 | }
152 | if (this.length >= 2) {
153 | this.closeStatus = this.binaryPayload.readUInt16BE(0);
154 | this.binaryPayload = this.binaryPayload.slice(2);
155 | }
156 | }
157 |
158 | this.parseState = COMPLETE;
159 | return true;
160 | }
161 | }
162 | return false;
163 | };
164 |
165 | WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {
166 | if (bufferList.length >= this.length) {
167 | bufferList.advance(this.length);
168 | this.parseState = COMPLETE;
169 | return true;
170 | }
171 | return false;
172 | };
173 |
174 | WebSocketFrame.prototype.toBuffer = function(nullMask) {
175 | var maskKey;
176 | var headerLength = 2;
177 | var data;
178 | var outputPos;
179 | var firstByte = 0x00;
180 | var secondByte = 0x00;
181 |
182 | if (this.fin) {
183 | firstByte |= 0x80;
184 | }
185 | if (this.rsv1) {
186 | firstByte |= 0x40;
187 | }
188 | if (this.rsv2) {
189 | firstByte |= 0x20;
190 | }
191 | if (this.rsv3) {
192 | firstByte |= 0x10;
193 | }
194 | if (this.mask) {
195 | secondByte |= 0x80;
196 | }
197 |
198 | firstByte |= (this.opcode & 0x0F);
199 |
200 | // the close frame is a special case because the close reason is
201 | // prepended to the payload data.
202 | if (this.opcode === 0x08) {
203 | this.length = 2;
204 | if (this.binaryPayload) {
205 | this.length += this.binaryPayload.length;
206 | }
207 | data = bufferAllocUnsafe(this.length);
208 | data.writeUInt16BE(this.closeStatus, 0);
209 | if (this.length > 2) {
210 | this.binaryPayload.copy(data, 2);
211 | }
212 | }
213 | else if (this.binaryPayload) {
214 | data = this.binaryPayload;
215 | this.length = data.length;
216 | }
217 | else {
218 | this.length = 0;
219 | }
220 |
221 | if (this.length <= 125) {
222 | // encode the length directly into the two-byte frame header
223 | secondByte |= (this.length & 0x7F);
224 | }
225 | else if (this.length > 125 && this.length <= 0xFFFF) {
226 | // Use 16-bit length
227 | secondByte |= 126;
228 | headerLength += 2;
229 | }
230 | else if (this.length > 0xFFFF) {
231 | // Use 64-bit length
232 | secondByte |= 127;
233 | headerLength += 8;
234 | }
235 |
236 | var output = bufferAllocUnsafe(this.length + headerLength + (this.mask ? 4 : 0));
237 |
238 | // write the frame header
239 | output[0] = firstByte;
240 | output[1] = secondByte;
241 |
242 | outputPos = 2;
243 |
244 | if (this.length > 125 && this.length <= 0xFFFF) {
245 | // write 16-bit length
246 | output.writeUInt16BE(this.length, outputPos);
247 | outputPos += 2;
248 | }
249 | else if (this.length > 0xFFFF) {
250 | // write 64-bit length
251 | output.writeUInt32BE(0x00000000, outputPos);
252 | output.writeUInt32BE(this.length, outputPos + 4);
253 | outputPos += 8;
254 | }
255 |
256 | if (this.mask) {
257 | maskKey = nullMask ? 0 : ((Math.random() * 0xFFFFFFFF) >>> 0);
258 | this.maskBytes.writeUInt32BE(maskKey, 0);
259 |
260 | // write the mask key
261 | this.maskBytes.copy(output, outputPos);
262 | outputPos += 4;
263 |
264 | if (data) {
265 | bufferUtil.mask(data, this.maskBytes, output, outputPos, this.length);
266 | }
267 | }
268 | else if (data) {
269 | data.copy(output, outputPos);
270 | }
271 |
272 | return output;
273 | };
274 |
275 | WebSocketFrame.prototype.toString = function() {
276 | return 'Opcode: ' + this.opcode + ', fin: ' + this.fin + ', length: ' + this.length + ', hasPayload: ' + Boolean(this.binaryPayload) + ', masked: ' + this.mask;
277 | };
278 |
279 |
280 | module.exports = WebSocketFrame;
281 |
--------------------------------------------------------------------------------
/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;
158 |
--------------------------------------------------------------------------------
/lib/WebSocketRouterRequest.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 util = require('util');
18 | var EventEmitter = require('events').EventEmitter;
19 |
20 | function WebSocketRouterRequest(webSocketRequest, resolvedProtocol) {
21 | // Superclass Constructor
22 | EventEmitter.call(this);
23 |
24 | this.webSocketRequest = webSocketRequest;
25 | if (resolvedProtocol === '____no_protocol____') {
26 | this.protocol = null;
27 | }
28 | else {
29 | this.protocol = resolvedProtocol;
30 | }
31 | this.origin = webSocketRequest.origin;
32 | this.resource = webSocketRequest.resource;
33 | this.resourceURL = webSocketRequest.resourceURL;
34 | this.httpRequest = webSocketRequest.httpRequest;
35 | this.remoteAddress = webSocketRequest.remoteAddress;
36 | this.webSocketVersion = webSocketRequest.webSocketVersion;
37 | this.requestedExtensions = webSocketRequest.requestedExtensions;
38 | this.cookies = webSocketRequest.cookies;
39 | }
40 |
41 | util.inherits(WebSocketRouterRequest, EventEmitter);
42 |
43 | WebSocketRouterRequest.prototype.accept = function(origin, cookies) {
44 | var connection = this.webSocketRequest.accept(this.protocol, origin, cookies);
45 | this.emit('requestAccepted', connection);
46 | return connection;
47 | };
48 |
49 | WebSocketRouterRequest.prototype.reject = function(status, reason, extraHeaders) {
50 | this.webSocketRequest.reject(status, reason, extraHeaders);
51 | this.emit('requestRejected', this);
52 | };
53 |
54 | module.exports = WebSocketRouterRequest;
55 |
--------------------------------------------------------------------------------
/lib/WebSocketServer.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 utils = require('./utils');
19 | var util = require('util');
20 | var debug = require('debug')('websocket:server');
21 | var EventEmitter = require('events').EventEmitter;
22 | var WebSocketRequest = require('./WebSocketRequest');
23 |
24 | var WebSocketServer = function WebSocketServer(config) {
25 | // Superclass Constructor
26 | EventEmitter.call(this);
27 |
28 | this._handlers = {
29 | upgrade: this.handleUpgrade.bind(this),
30 | requestAccepted: this.handleRequestAccepted.bind(this),
31 | requestResolved: this.handleRequestResolved.bind(this)
32 | };
33 | this.connections = [];
34 | this.pendingRequests = [];
35 | if (config) {
36 | this.mount(config);
37 | }
38 | };
39 |
40 | util.inherits(WebSocketServer, EventEmitter);
41 |
42 | WebSocketServer.prototype.mount = function(config) {
43 | this.config = {
44 | // The http server instance to attach to. Required.
45 | httpServer: null,
46 |
47 | // 64KiB max frame size.
48 | maxReceivedFrameSize: 0x10000,
49 |
50 | // 1MiB max message size, only applicable if
51 | // assembleFragments is true
52 | maxReceivedMessageSize: 0x100000,
53 |
54 | // Outgoing messages larger than fragmentationThreshold will be
55 | // split into multiple fragments.
56 | fragmentOutgoingMessages: true,
57 |
58 | // Outgoing frames are fragmented if they exceed this threshold.
59 | // Default is 16KiB
60 | fragmentationThreshold: 0x4000,
61 |
62 | // If true, the server will automatically send a ping to all
63 | // clients every 'keepaliveInterval' milliseconds. The timer is
64 | // reset on any received data from the client.
65 | keepalive: true,
66 |
67 | // The interval to send keepalive pings to connected clients if the
68 | // connection is idle. Any received data will reset the counter.
69 | keepaliveInterval: 20000,
70 |
71 | // If true, the server will consider any connection that has not
72 | // received any data within the amount of time specified by
73 | // 'keepaliveGracePeriod' after a keepalive ping has been sent to
74 | // be dead, and will drop the connection.
75 | // Ignored if keepalive is false.
76 | dropConnectionOnKeepaliveTimeout: true,
77 |
78 | // The amount of time to wait after sending a keepalive ping before
79 | // closing the connection if the connected peer does not respond.
80 | // Ignored if keepalive is false.
81 | keepaliveGracePeriod: 10000,
82 |
83 | // Whether to use native TCP keep-alive instead of WebSockets ping
84 | // and pong packets. Native TCP keep-alive sends smaller packets
85 | // on the wire and so uses bandwidth more efficiently. This may
86 | // be more important when talking to mobile devices.
87 | // If this value is set to true, then these values will be ignored:
88 | // keepaliveGracePeriod
89 | // dropConnectionOnKeepaliveTimeout
90 | useNativeKeepalive: false,
91 |
92 | // If true, fragmented messages will be automatically assembled
93 | // and the full message will be emitted via a 'message' event.
94 | // If false, each frame will be emitted via a 'frame' event and
95 | // the application will be responsible for aggregating multiple
96 | // fragmented frames. Single-frame messages will emit a 'message'
97 | // event in addition to the 'frame' event.
98 | // Most users will want to leave this set to 'true'
99 | assembleFragments: true,
100 |
101 | // If this is true, websocket connections will be accepted
102 | // regardless of the path and protocol specified by the client.
103 | // The protocol accepted will be the first that was requested
104 | // by the client. Clients from any origin will be accepted.
105 | // This should only be used in the simplest of cases. You should
106 | // probably leave this set to 'false' and inspect the request
107 | // object to make sure it's acceptable before accepting it.
108 | autoAcceptConnections: false,
109 |
110 | // Whether or not the X-Forwarded-For header should be respected.
111 | // It's important to set this to 'true' when accepting connections
112 | // from untrusted clients, as a malicious client could spoof its
113 | // IP address by simply setting this header. It's meant to be added
114 | // by a trusted proxy or other intermediary within your own
115 | // infrastructure.
116 | // See: http://en.wikipedia.org/wiki/X-Forwarded-For
117 | ignoreXForwardedFor: false,
118 |
119 | // If this is true, 'cookie' headers are parsed and exposed as WebSocketRequest.cookies
120 | parseCookies: true,
121 |
122 | // If this is true, 'sec-websocket-extensions' headers are parsed and exposed as WebSocketRequest.requestedExtensions
123 | parseExtensions: true,
124 |
125 | // The Nagle Algorithm makes more efficient use of network resources
126 | // by introducing a small delay before sending small packets so that
127 | // multiple messages can be batched together before going onto the
128 | // wire. This however comes at the cost of latency, so the default
129 | // is to disable it. If you don't need low latency and are streaming
130 | // lots of small messages, you can change this to 'false'
131 | disableNagleAlgorithm: true,
132 |
133 | // The number of milliseconds to wait after sending a close frame
134 | // for an acknowledgement to come back before giving up and just
135 | // closing the socket.
136 | closeTimeout: 5000
137 | };
138 | extend(this.config, config);
139 |
140 | if (this.config.httpServer) {
141 | if (!Array.isArray(this.config.httpServer)) {
142 | this.config.httpServer = [this.config.httpServer];
143 | }
144 | var upgradeHandler = this._handlers.upgrade;
145 | this.config.httpServer.forEach(function(httpServer) {
146 | httpServer.on('upgrade', upgradeHandler);
147 | });
148 | }
149 | else {
150 | throw new Error('You must specify an httpServer on which to mount the WebSocket server.');
151 | }
152 | };
153 |
154 | WebSocketServer.prototype.unmount = function() {
155 | var upgradeHandler = this._handlers.upgrade;
156 | this.config.httpServer.forEach(function(httpServer) {
157 | httpServer.removeListener('upgrade', upgradeHandler);
158 | });
159 | };
160 |
161 | WebSocketServer.prototype.closeAllConnections = function() {
162 | this.connections.forEach(function(connection) {
163 | connection.close();
164 | });
165 | this.pendingRequests.forEach(function(request) {
166 | process.nextTick(function() {
167 | request.reject(503); // HTTP 503 Service Unavailable
168 | });
169 | });
170 | };
171 |
172 | WebSocketServer.prototype.broadcast = function(data) {
173 | if (Buffer.isBuffer(data)) {
174 | this.broadcastBytes(data);
175 | }
176 | else if (typeof(data.toString) === 'function') {
177 | this.broadcastUTF(data);
178 | }
179 | };
180 |
181 | WebSocketServer.prototype.broadcastUTF = function(utfData) {
182 | this.connections.forEach(function(connection) {
183 | connection.sendUTF(utfData);
184 | });
185 | };
186 |
187 | WebSocketServer.prototype.broadcastBytes = function(binaryData) {
188 | this.connections.forEach(function(connection) {
189 | connection.sendBytes(binaryData);
190 | });
191 | };
192 |
193 | WebSocketServer.prototype.shutDown = function() {
194 | this.unmount();
195 | this.closeAllConnections();
196 | };
197 |
198 | WebSocketServer.prototype.handleUpgrade = function(request, socket) {
199 | var self = this;
200 | var wsRequest = new WebSocketRequest(socket, request, this.config);
201 | try {
202 | wsRequest.readHandshake();
203 | }
204 | catch(e) {
205 | wsRequest.reject(
206 | e.httpCode ? e.httpCode : 400,
207 | e.message,
208 | e.headers
209 | );
210 | debug('Invalid handshake: %s', e.message);
211 | this.emit('upgradeError', e);
212 | return;
213 | }
214 |
215 | this.pendingRequests.push(wsRequest);
216 |
217 | wsRequest.once('requestAccepted', this._handlers.requestAccepted);
218 | wsRequest.once('requestResolved', this._handlers.requestResolved);
219 | socket.once('close', function () {
220 | self._handlers.requestResolved(wsRequest);
221 | });
222 |
223 | if (!this.config.autoAcceptConnections && utils.eventEmitterListenerCount(this, 'request') > 0) {
224 | this.emit('request', wsRequest);
225 | }
226 | else if (this.config.autoAcceptConnections) {
227 | wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin);
228 | }
229 | else {
230 | wsRequest.reject(404, 'No handler is configured to accept the connection.');
231 | }
232 | };
233 |
234 | WebSocketServer.prototype.handleRequestAccepted = function(connection) {
235 | var self = this;
236 | connection.once('close', function(closeReason, description) {
237 | self.handleConnectionClose(connection, closeReason, description);
238 | });
239 | this.connections.push(connection);
240 | this.emit('connect', connection);
241 | };
242 |
243 | WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) {
244 | var index = this.connections.indexOf(connection);
245 | if (index !== -1) {
246 | this.connections.splice(index, 1);
247 | }
248 | this.emit('close', connection, closeReason, description);
249 | };
250 |
251 | WebSocketServer.prototype.handleRequestResolved = function(request) {
252 | var index = this.pendingRequests.indexOf(request);
253 | if (index !== -1) { this.pendingRequests.splice(index, 1); }
254 | };
255 |
256 | module.exports = WebSocketServer;
257 |
--------------------------------------------------------------------------------
/lib/browser.js:
--------------------------------------------------------------------------------
1 | var _globalThis;
2 | if (typeof globalThis === 'object') {
3 | _globalThis = globalThis;
4 | } else {
5 | try {
6 | _globalThis = require('es5-ext/global');
7 | } catch (error) {
8 | } finally {
9 | if (!_globalThis && typeof window !== 'undefined') { _globalThis = window; }
10 | if (!_globalThis) { throw new Error('Could not determine global this'); }
11 | }
12 | }
13 |
14 | var NativeWebSocket = _globalThis.WebSocket || _globalThis.MozWebSocket;
15 | var websocket_version = require('./version');
16 |
17 |
18 | /**
19 | * Expose a W3C WebSocket class with just one or two arguments.
20 | */
21 | function W3CWebSocket(uri, protocols) {
22 | var native_instance;
23 |
24 | if (protocols) {
25 | native_instance = new NativeWebSocket(uri, protocols);
26 | }
27 | else {
28 | native_instance = new NativeWebSocket(uri);
29 | }
30 |
31 | /**
32 | * 'native_instance' is an instance of nativeWebSocket (the browser's WebSocket
33 | * class). Since it is an Object it will be returned as it is when creating an
34 | * instance of W3CWebSocket via 'new W3CWebSocket()'.
35 | *
36 | * ECMAScript 5: http://bclary.com/2004/11/07/#a-13.2.2
37 | */
38 | return native_instance;
39 | }
40 | if (NativeWebSocket) {
41 | ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function(prop) {
42 | Object.defineProperty(W3CWebSocket, prop, {
43 | get: function() { return NativeWebSocket[prop]; }
44 | });
45 | });
46 | }
47 |
48 | /**
49 | * Module exports.
50 | */
51 | module.exports = {
52 | 'w3cwebsocket' : NativeWebSocket ? W3CWebSocket : null,
53 | 'version' : websocket_version
54 | };
55 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | var noop = exports.noop = function(){};
2 |
3 | exports.extend = function extend(dest, source) {
4 | for (var prop in source) {
5 | dest[prop] = source[prop];
6 | }
7 | };
8 |
9 | exports.eventEmitterListenerCount =
10 | require('events').EventEmitter.listenerCount ||
11 | function(emitter, type) { return emitter.listeners(type).length; };
12 |
13 | exports.bufferAllocUnsafe = Buffer.allocUnsafe ?
14 | Buffer.allocUnsafe :
15 | function oldBufferAllocUnsafe(size) { return new Buffer(size); };
16 |
17 | exports.bufferFromString = Buffer.from ?
18 | Buffer.from :
19 | function oldBufferFromString(string, encoding) {
20 | return new Buffer(string, encoding);
21 | };
22 |
23 | exports.BufferingLogger = function createBufferingLogger(identifier, uniqueID) {
24 | var logFunction = require('debug')(identifier);
25 | if (logFunction.enabled) {
26 | var logger = new BufferingLogger(identifier, uniqueID, logFunction);
27 | var debug = logger.log.bind(logger);
28 | debug.printOutput = logger.printOutput.bind(logger);
29 | debug.enabled = logFunction.enabled;
30 | return debug;
31 | }
32 | logFunction.printOutput = noop;
33 | return logFunction;
34 | };
35 |
36 | function BufferingLogger(identifier, uniqueID, logFunction) {
37 | this.logFunction = logFunction;
38 | this.identifier = identifier;
39 | this.uniqueID = uniqueID;
40 | this.buffer = [];
41 | }
42 |
43 | BufferingLogger.prototype.log = function() {
44 | this.buffer.push([ new Date(), Array.prototype.slice.call(arguments) ]);
45 | return this;
46 | };
47 |
48 | BufferingLogger.prototype.clear = function() {
49 | this.buffer = [];
50 | return this;
51 | };
52 |
53 | BufferingLogger.prototype.printOutput = function(logFunction) {
54 | if (!logFunction) { logFunction = this.logFunction; }
55 | var uniqueID = this.uniqueID;
56 | this.buffer.forEach(function(entry) {
57 | var date = entry[0].toLocaleString();
58 | var args = entry[1].slice();
59 | var formatString = args[0];
60 | if (formatString !== (void 0) && formatString !== null) {
61 | formatString = '%s - %s - ' + formatString.toString();
62 | args.splice(0, 1, formatString, date, uniqueID);
63 | logFunction.apply(global, args);
64 | }
65 | });
66 | };
67 |
--------------------------------------------------------------------------------
/lib/version.js:
--------------------------------------------------------------------------------
1 | module.exports = require('../package.json').version;
2 |
--------------------------------------------------------------------------------
/lib/websocket.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'server' : require('./WebSocketServer'),
3 | 'client' : require('./WebSocketClient'),
4 | 'router' : require('./WebSocketRouter'),
5 | 'frame' : require('./WebSocketFrame'),
6 | 'request' : require('./WebSocketRequest'),
7 | 'connection' : require('./WebSocketConnection'),
8 | 'w3cwebsocket' : require('./W3CWebSocket'),
9 | 'deprecation' : require('./Deprecation'),
10 | 'version' : require('./version')
11 | };
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "websocket",
3 | "description": "Websocket Client & Server Library implementing the WebSocket protocol as specified in RFC 6455.",
4 | "keywords": [
5 | "websocket",
6 | "websockets",
7 | "socket",
8 | "networking",
9 | "comet",
10 | "push",
11 | "RFC-6455",
12 | "realtime",
13 | "server",
14 | "client"
15 | ],
16 | "author": "Brian McKelvey (https://github.com/theturtle32)",
17 | "contributors": [
18 | "Iñaki Baz Castillo (http://dev.sipdoc.net)"
19 | ],
20 | "version": "1.0.34",
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/theturtle32/WebSocket-Node.git"
24 | },
25 | "homepage": "https://github.com/theturtle32/WebSocket-Node",
26 | "engines": {
27 | "node": ">=4.0.0"
28 | },
29 | "dependencies": {
30 | "bufferutil": "^4.0.1",
31 | "debug": "^2.2.0",
32 | "es5-ext": "^0.10.63",
33 | "typedarray-to-buffer": "^3.1.5",
34 | "utf-8-validate": "^5.0.2",
35 | "yaeti": "^0.0.6"
36 | },
37 | "devDependencies": {
38 | "buffer-equal": "^1.0.0",
39 | "gulp": "^4.0.2",
40 | "gulp-jshint": "^2.0.4",
41 | "jshint-stylish": "^2.2.1",
42 | "jshint": "^2.0.0",
43 | "tape": "^4.9.1"
44 | },
45 | "config": {
46 | "verbose": false
47 | },
48 | "scripts": {
49 | "test": "tape test/unit/*.js",
50 | "gulp": "gulp"
51 | },
52 | "main": "index",
53 | "directories": {
54 | "lib": "./lib"
55 | },
56 | "browser": "lib/browser.js",
57 | "license": "Apache-2.0"
58 | }
59 |
--------------------------------------------------------------------------------
/test/autobahn/README.md:
--------------------------------------------------------------------------------
1 | # Autobahn Test Suite
2 |
3 | I now run this using Docker Desktop for Mac. Unfortunately, the maintainer of the official Autobahn TestSuite Docker image has not published a new version recently, so we have to build the Docker image ourselves.
4 |
5 | Clone the autobahn repo, cd to the `docker` directory, and run:
6 | ```bash
7 | docker build \
8 | --build-arg AUTOBAHN_TESTSUITE_VCS_REF=c3655a9 \
9 | --build-arg BUILD_DATE=2020-08-28 \
10 | --build-arg AUTOBAHN_TESTSUITE_VERSION=0.8.1 \
11 | -t crossbario/autobahn-testsuite:latest \
12 | .
13 | ```
14 |
--------------------------------------------------------------------------------
/test/autobahn/config/fuzzingclient.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "options": {"failByDrop": false},
4 | "outdir": "./reports/servers",
5 |
6 | "servers": [
7 | {
8 | "agent": "WebSocket-Node 1.0.27",
9 | "url": "ws://host.docker.internal:8080",
10 | "options": {"version": 18}
11 | }
12 | ],
13 |
14 | "cases": ["*"],
15 | "exclude-cases": [],
16 | "exclude-agent-cases": {}
17 | }
18 |
--------------------------------------------------------------------------------
/test/autobahn/run-wstest.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # wstest -s ./fuzzingclient.json -m fuzzingclient
4 |
5 | docker run -it --rm \
6 | -v "${PWD}/config:/config" \
7 | -v "${PWD}/reports:/reports" \
8 | -p 9001:9001 \
9 | --name fuzzingclient \
10 | crossbario/autobahn-testsuite \
11 | wstest -m fuzzingclient --spec /config/fuzzingclient.json
--------------------------------------------------------------------------------
/test/scripts/autobahn-test-client.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the 'License');
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an 'AS IS' BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 | var WebSocketClient = require('../../lib/WebSocketClient');
19 | var wsVersion = require('../../lib/websocket').version;
20 | var querystring = require('querystring');
21 |
22 | var args = { /* defaults */
23 | secure: false,
24 | port: '9000',
25 | host: 'localhost'
26 | };
27 |
28 | /* Parse command line options */
29 | var pattern = /^--(.*?)(?:=(.*))?$/;
30 | process.argv.forEach(function(value) {
31 | var match = pattern.exec(value);
32 | if (match) {
33 | args[match[1]] = match[2] ? match[2] : true;
34 | }
35 | });
36 |
37 | args.protocol = args.secure ? 'wss:' : 'ws:';
38 |
39 | console.log('WebSocket-Node: Echo test client for running against the Autobahn test suite');
40 | console.log('Usage: ./libwebsockets-test-client.js --host=127.0.0.1 --port=9000 [--secure]');
41 | console.log('');
42 |
43 |
44 | console.log('Starting test run.');
45 |
46 | getCaseCount(function(caseCount) {
47 | var currentCase = 1;
48 | runNextTestCase();
49 |
50 | function runNextTestCase() {
51 | runTestCase(currentCase++, caseCount, function() {
52 | if (currentCase <= caseCount) {
53 | process.nextTick(runNextTestCase);
54 | }
55 | else {
56 | process.nextTick(function() {
57 | console.log('Test suite complete, generating report.');
58 | updateReport(function() {
59 | console.log('Report generated.');
60 | });
61 | });
62 | }
63 | });
64 | }
65 | });
66 |
67 |
68 | function runTestCase(caseIndex, caseCount, callback) {
69 | console.log('Running test ' + caseIndex + ' of ' + caseCount);
70 | var echoClient = new WebSocketClient({
71 | maxReceivedFrameSize: 64*1024*1024, // 64MiB
72 | maxReceivedMessageSize: 64*1024*1024, // 64MiB
73 | fragmentOutgoingMessages: false,
74 | keepalive: false,
75 | disableNagleAlgorithm: false
76 | });
77 |
78 | echoClient.on('connectFailed', function(error) {
79 | console.log('Connect Error: ' + error.toString());
80 | });
81 |
82 | echoClient.on('connect', function(connection) {
83 | connection.on('error', function(error) {
84 | console.log('Connection Error: ' + error.toString());
85 | });
86 | connection.on('close', function() {
87 | callback();
88 | });
89 | connection.on('message', function(message) {
90 | if (message.type === 'utf8') {
91 | connection.sendUTF(message.utf8Data);
92 | }
93 | else if (message.type === 'binary') {
94 | connection.sendBytes(message.binaryData);
95 | }
96 | });
97 | });
98 |
99 | var qs = querystring.stringify({
100 | case: caseIndex,
101 | agent: 'WebSocket-Node Client v' + wsVersion
102 | });
103 | echoClient.connect('ws://' + args.host + ':' + args.port + '/runCase?' + qs, []);
104 | }
105 |
106 | function getCaseCount(callback) {
107 | var client = new WebSocketClient();
108 | var caseCount = NaN;
109 | client.on('connect', function(connection) {
110 | connection.on('close', function() {
111 | callback(caseCount);
112 | });
113 | connection.on('message', function(message) {
114 | if (message.type === 'utf8') {
115 | console.log('Got case count: ' + message.utf8Data);
116 | caseCount = parseInt(message.utf8Data, 10);
117 | }
118 | else if (message.type === 'binary') {
119 | throw new Error('Unexpected binary message when retrieving case count');
120 | }
121 | });
122 | });
123 | client.connect('ws://' + args.host + ':' + args.port + '/getCaseCount', []);
124 | }
125 |
126 | function updateReport(callback) {
127 | var client = new WebSocketClient();
128 | var qs = querystring.stringify({
129 | agent: 'WebSocket-Node Client v' + wsVersion
130 | });
131 | client.on('connect', function(connection) {
132 | connection.on('close', callback);
133 | });
134 | client.connect('ws://localhost:9000/updateReports?' + qs);
135 | }
136 |
--------------------------------------------------------------------------------
/test/scripts/certificate.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICSzCCAbQCCQC5xbEo0fggtTANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJV
3 | UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxMLTG9zIEFuZ2VsZXMxDTAL
4 | BgNVBAoTBE5vbmUxDTALBgNVBAsTBE5vbmUxEjAQBgNVBAMTCWxvY2FsaG9zdDAe
5 | Fw0xNDEwMTkyMzM5MDJaFw0xNDExMTgyMzM5MDJaMGoxCzAJBgNVBAYTAlVTMRMw
6 | EQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtMb3MgQW5nZWxlczENMAsGA1UE
7 | ChMETm9uZTENMAsGA1UECxMETm9uZTESMBAGA1UEAxMJbG9jYWxob3N0MIGfMA0G
8 | CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCpX8/M7iA86+s0N91CRj1e83LEuJ48M6Mr
9 | CJBosYYWKz983sw0Kf1QviW5dhAzT5R1AdGyvddVd80z5J9tNZqje2vyrXssClq+
10 | iETxW8U71wfUZ/VmGhWcXwU7XD4JP33WbTIEHS2xgpMQNXQnQFM40+NjQBViK0b9
11 | mQtclOliqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAAyGak0l7boxJzJBYSw1CyaV
12 | Ibs9nnSW3HqhsEml+rPp+7+0G6wUYcMolPCUY13K4aIG0n7c6oDwvtcWWbjnVxAb
13 | i2wBL35mByrbZKiOoQTpCPsFE7fNK3vnvkXkhvTrAkX5vscug0XDwpl0rz5X8lC1
14 | /V6Bu4GqNme6p2UVcRIH
15 | -----END CERTIFICATE-----
16 |
--------------------------------------------------------------------------------
/test/scripts/echo-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the 'License');
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an 'AS IS' BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 | var WebSocketServer = require('../../lib/WebSocketServer');
19 | var http = require('http');
20 |
21 | var args = { /* defaults */
22 | port: '8080',
23 | debug: false
24 | };
25 |
26 | /* Parse command line options */
27 | var pattern = /^--(.*?)(?:=(.*))?$/;
28 | process.argv.forEach(function(value) {
29 | var match = pattern.exec(value);
30 | if (match) {
31 | args[match[1]] = match[2] ? match[2] : true;
32 | }
33 | });
34 |
35 | var port = parseInt(args.port, 10);
36 | var debug = args.debug;
37 |
38 | console.log('WebSocket-Node: echo-server');
39 | console.log('Usage: ./echo-server.js [--port=8080] [--debug]');
40 |
41 | var server = http.createServer(function(request, response) {
42 | if (debug) { console.log((new Date()) + ' Received request for ' + request.url); }
43 | response.writeHead(404);
44 | response.end();
45 | });
46 | server.listen(port, function() {
47 | console.log((new Date()) + ' Server is listening on port ' + port);
48 | });
49 |
50 | var wsServer = new WebSocketServer({
51 | httpServer: server,
52 | autoAcceptConnections: true,
53 | maxReceivedFrameSize: 64*1024*1024, // 64MiB
54 | maxReceivedMessageSize: 64*1024*1024, // 64MiB
55 | fragmentOutgoingMessages: false,
56 | keepalive: false,
57 | disableNagleAlgorithm: false
58 | });
59 |
60 | wsServer.on('connect', function(connection) {
61 | if (debug) { console.log((new Date()) + ' Connection accepted' +
62 | ' - Protocol Version ' + connection.webSocketVersion); }
63 | function sendCallback(err) {
64 | if (err) {
65 | console.error('send() error: ' + err);
66 | connection.drop();
67 | setTimeout(function() {
68 | process.exit(100);
69 | }, 100);
70 | }
71 | }
72 | connection.on('message', function(message) {
73 | if (message.type === 'utf8') {
74 | if (debug) { console.log('Received utf-8 message of ' + message.utf8Data.length + ' characters.'); }
75 | connection.sendUTF(message.utf8Data, sendCallback);
76 | }
77 | else if (message.type === 'binary') {
78 | if (debug) { console.log('Received Binary Message of ' + message.binaryData.length + ' bytes'); }
79 | connection.sendBytes(message.binaryData, sendCallback);
80 | }
81 | });
82 | connection.on('close', function(reasonCode, description) {
83 | if (debug) { console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.'); }
84 | connection._debug.printOutput();
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/test/scripts/fragmentation-test-client.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the 'License');
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an 'AS IS' BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 | var WebSocketClient = require('../../lib/WebSocketClient');
19 |
20 | console.log('WebSocket-Node: Test client for parsing fragmented messages.');
21 |
22 | var args = { /* defaults */
23 | secure: false,
24 | port: '8080',
25 | host: '127.0.0.1',
26 | 'no-defragment': false,
27 | binary: false
28 | };
29 |
30 | /* Parse command line options */
31 | var pattern = /^--(.*?)(?:=(.*))?$/;
32 | process.argv.forEach(function(value) {
33 | var match = pattern.exec(value);
34 | if (match) {
35 | args[match[1]] = match[2] ? match[2] : true;
36 | }
37 | });
38 |
39 | args.protocol = args.secure ? 'wss:' : 'ws:';
40 |
41 | if (args.help) {
42 | console.log('Usage: ./fragmentation-test-client.js [--host=127.0.0.1] [--port=8080] [--no-defragment] [--binary]');
43 | console.log('');
44 | return;
45 | }
46 | else {
47 | console.log('Use --help for usage information.');
48 | }
49 |
50 |
51 | var client = new WebSocketClient({
52 | maxReceivedMessageSize: 128*1024*1024, // 128 MiB
53 | maxReceivedFrameSize: 1*1024*1024, // 1 MiB
54 | assembleFragments: !args['no-defragment']
55 | });
56 |
57 | client.on('connectFailed', function(error) {
58 | console.log('Client Error: ' + error.toString());
59 | });
60 |
61 |
62 | var requestedLength = 100;
63 | var messageSize = 0;
64 | var startTime;
65 | var byteCounter;
66 |
67 | client.on('connect', function(connection) {
68 | console.log('Connected');
69 | startTime = new Date();
70 | byteCounter = 0;
71 |
72 | connection.on('error', function(error) {
73 | console.log('Connection Error: ' + error.toString());
74 | });
75 |
76 | connection.on('close', function() {
77 | console.log('Connection Closed');
78 | });
79 |
80 | connection.on('message', function(message) {
81 | if (message.type === 'utf8') {
82 | console.log('Received utf-8 message of ' + message.utf8Data.length + ' characters.');
83 | logThroughput(message.utf8Data.length);
84 | requestData();
85 | }
86 | else {
87 | console.log('Received binary message of ' + message.binaryData.length + ' bytes.');
88 | logThroughput(message.binaryData.length);
89 | requestData();
90 | }
91 | });
92 |
93 | connection.on('frame', function(frame) {
94 | console.log('Frame: 0x' + frame.opcode.toString(16) + '; ' + frame.length + ' bytes; Flags: ' + renderFlags(frame));
95 | messageSize += frame.length;
96 | if (frame.fin) {
97 | console.log('Total message size: ' + messageSize + ' bytes.');
98 | logThroughput(messageSize);
99 | messageSize = 0;
100 | requestData();
101 | }
102 | });
103 |
104 | function logThroughput(numBytes) {
105 | byteCounter += numBytes;
106 | var duration = (new Date()).valueOf() - startTime.valueOf();
107 | if (duration > 1000) {
108 | var kiloBytesPerSecond = Math.round((byteCounter / 1024) / (duration/1000));
109 | console.log(' Throughput: ' + kiloBytesPerSecond + ' KBps');
110 | startTime = new Date();
111 | byteCounter = 0;
112 | }
113 | }
114 |
115 | function sendUTFCallback(err) {
116 | if (err) { console.error('sendUTF() error: ' + err); }
117 | }
118 |
119 | function requestData() {
120 | if (args.binary) {
121 | connection.sendUTF('sendBinaryMessage|' + requestedLength, sendUTFCallback);
122 | }
123 | else {
124 | connection.sendUTF('sendMessage|' + requestedLength, sendUTFCallback);
125 | }
126 | requestedLength += Math.ceil(Math.random() * 1024);
127 | }
128 |
129 | function renderFlags(frame) {
130 | var flags = [];
131 | if (frame.fin) {
132 | flags.push('[FIN]');
133 | }
134 | if (frame.rsv1) {
135 | flags.push('[RSV1]');
136 | }
137 | if (frame.rsv2) {
138 | flags.push('[RSV2]');
139 | }
140 | if (frame.rsv3) {
141 | flags.push('[RSV3]');
142 | }
143 | if (frame.mask) {
144 | flags.push('[MASK]');
145 | }
146 | if (flags.length === 0) {
147 | return '---';
148 | }
149 | return flags.join(' ');
150 | }
151 |
152 | requestData();
153 | });
154 |
155 | if (args['no-defragment']) {
156 | console.log('Not automatically re-assembling fragmented messages.');
157 | }
158 | else {
159 | console.log('Maximum aggregate message size: ' + client.config.maxReceivedMessageSize + ' bytes.');
160 | }
161 | console.log('Connecting');
162 |
163 | client.connect(args.protocol + '//' + args.host + ':' + args.port + '/', 'fragmentation-test');
164 |
--------------------------------------------------------------------------------
/test/scripts/fragmentation-test-page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 | Firefox Bug
21 |
108 |
109 |
110 |
111 |
Controls
112 |
113 |
114 |
115 |
116 |
117 |
Output
118 |
Ready.
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/test/scripts/fragmentation-test-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the 'License');
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an 'AS IS' BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 |
19 | var WebSocketServer = require('../../lib/WebSocketServer');
20 | var WebSocketRouter = require('../../lib/WebSocketRouter');
21 | var bufferAllocUnsafe = require('../../lib/utils').bufferAllocUnsafe;
22 | var http = require('http');
23 | var fs = require('fs');
24 |
25 | console.log('WebSocket-Node: Test server to spit out fragmented messages.');
26 |
27 | var args = {
28 | 'no-fragmentation': false,
29 | 'fragment': '16384',
30 | 'port': '8080'
31 | };
32 |
33 | /* Parse command line options */
34 | var pattern = /^--(.*?)(?:=(.*))?$/;
35 | process.argv.forEach(function(value) {
36 | var match = pattern.exec(value);
37 | if (match) {
38 | args[match[1]] = match[2] ? match[2] : true;
39 | }
40 | });
41 |
42 | args.protocol = 'ws:';
43 |
44 | if (args.help) {
45 | console.log('Usage: ./fragmentation-test-server.js [--port=8080] [--fragment=n] [--no-fragmentation]');
46 | console.log('');
47 | return;
48 | }
49 | else {
50 | console.log('Use --help for usage information.');
51 | }
52 |
53 | var server = http.createServer(function(request, response) {
54 | console.log((new Date()) + ' Received request for ' + request.url);
55 | if (request.url === '/') {
56 | fs.readFile('fragmentation-test-page.html', 'utf8', function(err, data) {
57 | if (err) {
58 | response.writeHead(404);
59 | response.end();
60 | }
61 | else {
62 | response.writeHead(200, {
63 | 'Content-Type': 'text/html'
64 | });
65 | response.end(data);
66 | }
67 | });
68 | }
69 | else {
70 | response.writeHead(404);
71 | response.end();
72 | }
73 | });
74 | server.listen(args.port, function() {
75 | console.log((new Date()) + ' Server is listening on port ' + args.port);
76 | });
77 |
78 | var wsServer = new WebSocketServer({
79 | httpServer: server,
80 | fragmentOutgoingMessages: !args['no-fragmentation'],
81 | fragmentationThreshold: parseInt(args['fragment'], 10)
82 | });
83 |
84 | var router = new WebSocketRouter();
85 | router.attachServer(wsServer);
86 |
87 |
88 | var lorem = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.';
89 |
90 |
91 | router.mount('*', 'fragmentation-test', function(request) {
92 | var connection = request.accept(request.origin);
93 | console.log((new Date()) + ' connection accepted from ' + connection.remoteAddress);
94 |
95 |
96 | connection.on('message', function(message) {
97 | function sendCallback(err) {
98 | if (err) { console.error('send() error: ' + err); }
99 | }
100 | if (message.type === 'utf8') {
101 | var length = 0;
102 | var match = /sendMessage\|(\d+)/.exec(message.utf8Data);
103 | var requestedLength;
104 | if (match) {
105 | requestedLength = parseInt(match[1], 10);
106 | var longLorem = '';
107 | while (length < requestedLength) {
108 | longLorem += (' ' + lorem);
109 | length = Buffer.byteLength(longLorem);
110 | }
111 | longLorem = longLorem.slice(0,requestedLength);
112 | length = Buffer.byteLength(longLorem);
113 | if (length > 0) {
114 | connection.sendUTF(longLorem, sendCallback);
115 | console.log((new Date()) + ' sent ' + length + ' byte utf-8 message to ' + connection.remoteAddress);
116 | }
117 | return;
118 | }
119 |
120 | match = /sendBinaryMessage\|(\d+)/.exec(message.utf8Data);
121 | if (match) {
122 | requestedLength = parseInt(match[1], 10);
123 |
124 | // Generate random binary data.
125 | var buffer = bufferAllocUnsafe(requestedLength);
126 | for (var i=0; i < requestedLength; i++) {
127 | buffer[i] = Math.ceil(Math.random()*255);
128 | }
129 |
130 | connection.sendBytes(buffer, sendCallback);
131 | console.log((new Date()) + ' sent ' + buffer.length + ' byte binary message to ' + connection.remoteAddress);
132 | return;
133 | }
134 | }
135 | });
136 |
137 | connection.on('close', function(reasonCode, description) {
138 | console.log((new Date()) + ' peer ' + connection.remoteAddress + ' disconnected.');
139 | });
140 |
141 | connection.on('error', function(error) {
142 | console.log('Connection error for peer ' + connection.remoteAddress + ': ' + error);
143 | });
144 | });
145 |
146 | console.log('Point your WebSocket Protocol Version 8 compliant browser at http://localhost:' + args.port + '/');
147 | if (args['no-fragmentation']) {
148 | console.log('Fragmentation disabled.');
149 | }
150 | else {
151 | console.log('Fragmenting messages at ' + wsServer.config.fragmentationThreshold + ' bytes');
152 | }
153 |
--------------------------------------------------------------------------------
/test/scripts/libwebsockets-test-client.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the 'License');
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an 'AS IS' BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 | var WebSocketClient = require('../../lib/WebSocketClient');
19 |
20 | var args = { /* defaults */
21 | secure: false,
22 | version: 13
23 | };
24 |
25 | /* Parse command line options */
26 | var pattern = /^--(.*?)(?:=(.*))?$/;
27 | process.argv.forEach(function(value) {
28 | var match = pattern.exec(value);
29 | if (match) {
30 | args[match[1]] = match[2] ? match[2] : true;
31 | }
32 | });
33 |
34 | args.protocol = args.secure ? 'wss:' : 'ws:';
35 | args.version = parseInt(args.version, 10);
36 |
37 | if (!args.host || !args.port) {
38 | console.log('WebSocket-Node: Test client for Andy Green\'s libwebsockets-test-server');
39 | console.log('Usage: ./libwebsockets-test-client.js --host=127.0.0.1 --port=8080 [--version=8|13] [--secure]');
40 | console.log('');
41 | return;
42 | }
43 |
44 | var mirrorClient = new WebSocketClient({
45 | webSocketVersion: args.version
46 | });
47 |
48 | mirrorClient.on('connectFailed', function(error) {
49 | console.log('Connect Error: ' + error.toString());
50 | });
51 |
52 | mirrorClient.on('connect', function(connection) {
53 | console.log('lws-mirror-protocol connected');
54 | connection.on('error', function(error) {
55 | console.log('Connection Error: ' + error.toString());
56 | });
57 | connection.on('close', function() {
58 | console.log('lws-mirror-protocol Connection Closed');
59 | });
60 | function sendCallback(err) {
61 | if (err) { console.error('send() error: ' + err); }
62 | }
63 | function spamCircles() {
64 | if (connection.connected) {
65 | // c #7A9237 487 181 14;
66 | var color = 0x800000 + Math.round(Math.random() * 0x7FFFFF);
67 | var x = Math.round(Math.random() * 502);
68 | var y = Math.round(Math.random() * 306);
69 | var radius = Math.round(Math.random() * 30);
70 | connection.send('c #' + color.toString(16) + ' ' + x + ' ' + y + ' ' + radius + ';', sendCallback);
71 | setTimeout(spamCircles, 10);
72 | }
73 | }
74 | spamCircles();
75 | });
76 |
77 | mirrorClient.connect(args.protocol + '//' + args.host + ':' + args.port + '/', 'lws-mirror-protocol');
78 |
79 |
80 | var incrementClient = new WebSocketClient({
81 | webSocketVersion: args.version
82 | });
83 |
84 | incrementClient.on('connectFailed', function(error) {
85 | console.log('Connect Error: ' + error.toString());
86 | });
87 |
88 | incrementClient.on('connect', function(connection) {
89 | console.log('dumb-increment-protocol connected');
90 | connection.on('error', function(error) {
91 | console.log('Connection Error: ' + error.toString());
92 | });
93 | connection.on('close', function() {
94 | console.log('dumb-increment-protocol Connection Closed');
95 | });
96 | connection.on('message', function(message) {
97 | console.log('Number: \'' + message.utf8Data + '\'');
98 | });
99 | });
100 |
101 | incrementClient.connect(args.protocol + '//' + args.host + ':' + args.port + '/', 'dumb-increment-protocol');
102 |
--------------------------------------------------------------------------------
/test/scripts/libwebsockets-test-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /************************************************************************
3 | * Copyright 2010-2015 Brian McKelvey.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the 'License');
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an 'AS IS' BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***********************************************************************/
17 |
18 |
19 | var WebSocketServer = require('../../lib/WebSocketServer');
20 | var WebSocketRouter = require('../../lib/WebSocketRouter');
21 | var http = require('http');
22 | var fs = require('fs');
23 |
24 | var args = { /* defaults */
25 | secure: false
26 | };
27 |
28 | /* Parse command line options */
29 | var pattern = /^--(.*?)(?:=(.*))?$/;
30 | process.argv.forEach(function(value) {
31 | var match = pattern.exec(value);
32 | if (match) {
33 | args[match[1]] = match[2] ? match[2] : true;
34 | }
35 | });
36 |
37 | args.protocol = args.secure ? 'wss:' : 'ws:';
38 |
39 | if (!args.port) {
40 | console.log('WebSocket-Node: Test Server implementing Andy Green\'s');
41 | console.log('libwebsockets-test-server protocols.');
42 | console.log('Usage: ./libwebsockets-test-server.js --port=8080 [--secure]');
43 | console.log('');
44 | return;
45 | }
46 |
47 | if (args.secure) {
48 | console.log('WebSocket-Node: Test Server implementing Andy Green\'s');
49 | console.log('libwebsockets-test-server protocols.');
50 | console.log('ERROR: TLS is not yet supported.');
51 | console.log('');
52 | return;
53 | }
54 |
55 | var server = http.createServer(function(request, response) {
56 | console.log((new Date()) + ' Received request for ' + request.url);
57 | if (request.url === '/') {
58 | fs.readFile('libwebsockets-test.html', 'utf8', function(err, data) {
59 | if (err) {
60 | response.writeHead(404);
61 | response.end();
62 | }
63 | else {
64 | response.writeHead(200, {
65 | 'Content-Type': 'text/html'
66 | });
67 | response.end(data);
68 | }
69 | });
70 | }
71 | else {
72 | response.writeHead(404);
73 | response.end();
74 | }
75 | });
76 | server.listen(args.port, function() {
77 | console.log((new Date()) + ' Server is listening on port ' + args.port);
78 | });
79 |
80 | var wsServer = new WebSocketServer({
81 | httpServer: server
82 | });
83 |
84 | var router = new WebSocketRouter();
85 | router.attachServer(wsServer);
86 |
87 |
88 | var mirrorConnections = [];
89 |
90 | var mirrorHistory = [];
91 |
92 | function sendCallback(err) {
93 | if (err) { console.error('send() error: ' + err); }
94 | }
95 |
96 | router.mount('*', 'lws-mirror-protocol', function(request) {
97 | var cookies = [
98 | {
99 | name: 'TestCookie',
100 | value: 'CookieValue' + Math.floor(Math.random()*1000),
101 | path: '/',
102 | secure: false,
103 | maxage: 5000,
104 | httponly: true
105 | }
106 | ];
107 |
108 | // Should do origin verification here. You have to pass the accepted
109 | // origin into the accept method of the request.
110 | var connection = request.accept(request.origin, cookies);
111 | console.log((new Date()) + ' lws-mirror-protocol connection accepted from ' + connection.remoteAddress +
112 | ' - Protocol Version ' + connection.webSocketVersion);
113 |
114 |
115 |
116 | if (mirrorHistory.length > 0) {
117 | var historyString = mirrorHistory.join('');
118 | console.log((new Date()) + ' sending mirror protocol history to client; ' + connection.remoteAddress + ' : ' + Buffer.byteLength(historyString) + ' bytes');
119 |
120 | connection.send(historyString, sendCallback);
121 | }
122 |
123 | mirrorConnections.push(connection);
124 |
125 | connection.on('message', function(message) {
126 | // We only care about text messages
127 | if (message.type === 'utf8') {
128 | // Clear canvas command received
129 | if (message.utf8Data === 'clear;') {
130 | mirrorHistory = [];
131 | }
132 | else {
133 | // Record all other commands in the history
134 | mirrorHistory.push(message.utf8Data);
135 | }
136 |
137 | // Re-broadcast the command to all connected clients
138 | mirrorConnections.forEach(function (outputConnection) {
139 | outputConnection.send(message.utf8Data, sendCallback);
140 | });
141 | }
142 | });
143 |
144 | connection.on('close', function(closeReason, description) {
145 | var index = mirrorConnections.indexOf(connection);
146 | if (index !== -1) {
147 | console.log((new Date()) + ' lws-mirror-protocol peer ' + connection.remoteAddress + ' disconnected, code: ' + closeReason + '.');
148 | mirrorConnections.splice(index, 1);
149 | }
150 | });
151 |
152 | connection.on('error', function(error) {
153 | console.log('Connection error for peer ' + connection.remoteAddress + ': ' + error);
154 | });
155 | });
156 |
157 | router.mount('*', 'dumb-increment-protocol', function(request) {
158 | // Should do origin verification here. You have to pass the accepted
159 | // origin into the accept method of the request.
160 | var connection = request.accept(request.origin);
161 | console.log((new Date()) + ' dumb-increment-protocol connection accepted from ' + connection.remoteAddress +
162 | ' - Protocol Version ' + connection.webSocketVersion);
163 |
164 | var number = 0;
165 | connection.timerInterval = setInterval(function() {
166 | connection.send((number++).toString(10), sendCallback);
167 | }, 50);
168 | connection.on('close', function() {
169 | clearInterval(connection.timerInterval);
170 | });
171 | connection.on('message', function(message) {
172 | if (message.type === 'utf8') {
173 | if (message.utf8Data === 'reset\n') {
174 | console.log((new Date()) + ' increment reset received');
175 | number = 0;
176 | }
177 | }
178 | });
179 | connection.on('close', function(closeReason, description) {
180 | console.log((new Date()) + ' dumb-increment-protocol peer ' + connection.remoteAddress + ' disconnected, code: ' + closeReason + '.');
181 | });
182 | });
183 |
184 | console.log('WebSocket-Node: Test Server implementing Andy Green\'s');
185 | console.log('libwebsockets-test-server protocols.');
186 | console.log('Point your WebSocket Protocol Version 8 complant browser to http://localhost:' + args.port + '/');
187 |
--------------------------------------------------------------------------------
/test/scripts/libwebsockets-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Minimal Websocket test app
6 |
7 |
8 |
9 |
libwebsockets "dumb-increment-protocol" test applet
10 | The incrementing number is coming from the server and is individual for
11 | each connection to the server... try opening a second browser window.
12 | Click the button to send the server a websocket message to
13 | reset the number.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Not initialized
23 |
24 |
25 |
26 |
libwebsockets "lws-mirror-protocol" test applet
27 | Use the mouse to draw on the canvas below -- all other browser windows open
28 | on this page see your drawing in realtime and you can see any of theirs as
29 | well.
30 |
31 | The lws-mirror protocol doesn't interpret what is being sent to it, it just
32 | re-sends it to every other websocket it has a connection with using that
33 | protocol, including the guy who sent the packet.
34 |
libwebsockets-test-client spams circles on to this shared canvas when
35 | run.
36 |
37 |
38 |
39 |
40 |
Drawing color:
41 |
47 |
48 |
Not initialized
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
251 |
252 |
253 |
254 |
--------------------------------------------------------------------------------
/test/scripts/memoryleak-client.js:
--------------------------------------------------------------------------------
1 | var WebSocketClient = require('../../lib/websocket').client;
2 |
3 | var connectionAmount = process.argv[2];
4 | var activeCount = 0;
5 | var deviceList = [];
6 |
7 | connectDevices();
8 |
9 | function logActiveCount() {
10 | console.log('---activecount---: ' + activeCount);
11 | }
12 |
13 | setInterval(logActiveCount, 500);
14 |
15 | function connectDevices() {
16 | for( var i=0; i < connectionAmount; i++ ){
17 | connect( i );
18 | }
19 | }
20 |
21 | function connect( i ){
22 | // console.log( '--- Connecting: ' + i );
23 | var client = new WebSocketClient({
24 | tlsOptions: {
25 | rejectUnauthorized: false
26 | }
27 | });
28 | client._clientID = i;
29 | deviceList[i] = client;
30 |
31 | client.on('connectFailed', function(error) {
32 | console.log(i + ' - connect Error: ' + error.toString());
33 | });
34 |
35 | client.on('connect', function(connection) {
36 | console.log(i + ' - connect');
37 | activeCount ++;
38 | client.connection = connection;
39 | flake( i );
40 |
41 | maybeScheduleSend(i);
42 |
43 | connection.on('error', function(error) {
44 | console.log(i + ' - ' + error.toString());
45 | });
46 |
47 | connection.on('close', function(reasonCode, closeDescription) {
48 | console.log(i + ' - close (%d) %s', reasonCode, closeDescription);
49 | activeCount --;
50 | if (client._flakeTimeout) {
51 | clearTimeout(client._flakeTimeout);
52 | client._flakeTimeout = null;
53 | }
54 | connect(i);
55 | });
56 |
57 | connection.on('message', function(message) {
58 | if ( message.type === 'utf8' ) {
59 | console.log(i + ' received: \'' + message.utf8Data + '\'');
60 | }
61 | });
62 |
63 | });
64 | client.connect('wss://localhost:8080');
65 | }
66 |
67 | function disconnect( i ){
68 | var client = deviceList[i];
69 | if (client._flakeTimeout) {
70 | client._flakeTimeout = null;
71 | }
72 | client.connection.close();
73 | }
74 |
75 | function maybeScheduleSend(i) {
76 | var client = deviceList[i];
77 | var random = Math.round(Math.random() * 100);
78 | console.log(i + ' - scheduling send. Random: ' + random);
79 | if (random < 50) {
80 | setTimeout(function() {
81 | console.log(i + ' - send timeout. Connected? ' + client.connection.connected);
82 | if (client && client.connection.connected) {
83 | console.log(i + ' - Sending test data! random: ' + random);
84 | client.connection.send( (new Array(random)).join('TestData') );
85 | }
86 | }, random);
87 | }
88 | }
89 |
90 | function flake(i) {
91 | var client = deviceList[i];
92 | var timeBeforeDisconnect = Math.round(Math.random() * 2000);
93 | client._flakeTimeout = setTimeout( function() {
94 | disconnect(i);
95 | }, timeBeforeDisconnect);
96 | }
97 |
--------------------------------------------------------------------------------
/test/scripts/memoryleak-server.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
2 |
3 | // var heapdump = require('heapdump');
4 | // var memwatch = require('memwatch');
5 | var fs = require('fs');
6 | var WebSocketServer = require('../../lib/websocket').server;
7 | var https = require('https');
8 |
9 | var activeCount = 0;
10 |
11 | var config = {
12 | key: fs.readFileSync( 'privatekey.pem' ),
13 | cert: fs.readFileSync( 'certificate.pem' )
14 | };
15 |
16 | var server = https.createServer( config );
17 |
18 | server.listen(8080, function() {
19 | console.log((new Date()) + ' Server is listening on port 8080 (wss)');
20 | });
21 |
22 | var wsServer = new WebSocketServer({
23 | httpServer: server,
24 | autoAcceptConnections: false
25 | });
26 |
27 | wsServer.on('request', function(request) {
28 | activeCount++;
29 | console.log('Opened from: %j\n---activeCount---: %d', request.remoteAddresses, activeCount);
30 | var connection = request.accept(null, request.origin);
31 | console.log((new Date()) + ' Connection accepted.');
32 | connection.on('message', function(message) {
33 | if (message.type === 'utf8') {
34 | console.log('Received Message: ' + message.utf8Data);
35 | setTimeout(function() {
36 | if (connection.connected) {
37 | connection.sendUTF(message.utf8Data);
38 | }
39 | }, 1000);
40 | }
41 | });
42 | connection.on('close', function(reasonCode, description) {
43 | activeCount--;
44 | console.log('Closed. (' + reasonCode + ') ' + description +
45 | '\n---activeCount---: ' + activeCount);
46 | // connection._debug.printOutput();
47 | });
48 | connection.on('error', function(error) {
49 | console.log('Connection error: ' + error);
50 | });
51 | });
52 |
53 | // setInterval( function(){
54 | // // global.gc();
55 | // var filename = './heapdump/'+ new Date().getTime() + '_' + activeCount + '.heapsnapshot';
56 | // console.log('Triggering heapdump to write to %s', filename);
57 | // heapdump.writeSnapshot( filename );
58 | // }, 10000 );
59 | // memwatch.on('leak', function(info) { console.log(info); });
60 |
--------------------------------------------------------------------------------
/test/scripts/privatekey.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXQIBAAKBgQCpX8/M7iA86+s0N91CRj1e83LEuJ48M6MrCJBosYYWKz983sw0
3 | Kf1QviW5dhAzT5R1AdGyvddVd80z5J9tNZqje2vyrXssClq+iETxW8U71wfUZ/Vm
4 | GhWcXwU7XD4JP33WbTIEHS2xgpMQNXQnQFM40+NjQBViK0b9mQtclOliqwIDAQAB
5 | AoGAS5yrPtg7ahcD7E7YJsMGmMHjxXCJq2R9/jMXjLVbn7/02sD3tn3cSVRpsO7E
6 | 8rMfGlESlVHstIoEAJualH1up7y2fLd2KFoHJ+7tCH0VHXsNp07XVDCnjrTH3z7J
7 | DRt6RjoKDqra1os+CzV+C+NnNvIt74+KSo06JjQVb5ZRmXECQQDcPIFKFK8LZVlX
8 | 9cZrhRLW7K3g8K2BE3tmaocTk3T+0+u0cK9OaNIuEcj3cM3H35DtA4kXtNCRmjA+
9 | ytDmzLH5AkEAxODoU/ve4YuU0r7OTzxd0G9FLuif2ktqfqid185BBH6/K4SttXob
10 | yI93h261jVHZ9RgifXs/MBdCmeB5GODiwwJBAINeQ/CgbdlqVuS04ep4skgpXX5z
11 | kcsQh+cLXA89QehPGKXFIYyv0c9RJIMUcmrq3FPEbB4L6O0w/940tG83YmECQQCv
12 | BhrEfse3zzTw3bvfaRUltaXVe+yQTjdQfmpEbhITAvLEp2EeUn3coN5sQhmYlsmj
13 | QF95GlYkVKlaztoZKeOtAkB0uNhifHAfCi4mUVzRb+oWrju1pRgHoTXQUh98XWpL
14 | Am8gTOfkcszD+Kg+weCH1V0LJelptTJ/5dSpDRfI+I2S
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/test/shared/start-echo-server.js:
--------------------------------------------------------------------------------
1 | module.exports = startEchoServer;
2 |
3 | function startEchoServer(outputStream, callback) {
4 | if ('function' === typeof outputStream) {
5 | callback = outputStream;
6 | outputStream = null;
7 | }
8 | if ('function' !== typeof callback) {
9 | callback = function(){};
10 | }
11 |
12 | var path = require('path').join(__dirname + '/../scripts/echo-server.js');
13 |
14 | console.log(path);
15 |
16 | var echoServer = require('child_process').spawn('node', [ path ]);
17 |
18 | var state = 'starting';
19 |
20 | var processProxy = {
21 | kill: function(signal) {
22 | state = 'exiting';
23 | echoServer.kill(signal);
24 | }
25 | };
26 |
27 | if (outputStream) {
28 | echoServer.stdout.pipe(outputStream);
29 | echoServer.stderr.pipe(outputStream);
30 | }
31 |
32 | echoServer.stdout.on('data', function(chunk) {
33 | chunk = chunk.toString();
34 | if (/Server is listening/.test(chunk)) {
35 | if (state === 'starting') {
36 | state = 'ready';
37 | callback(null, processProxy);
38 | }
39 | }
40 | });
41 |
42 | echoServer.on('exit', function(code, signal) {
43 | echoServer = null;
44 | if (state !== 'exiting') {
45 | state = 'exited';
46 | callback(new Error('Echo Server exited unexpectedly with code ' + code));
47 | process.exit(1);
48 | }
49 | });
50 |
51 | process.on('exit', function() {
52 | if (echoServer && state === 'ready') {
53 | echoServer.kill();
54 | }
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/test/shared/test-server.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var WebSocketServer = require('../../lib/WebSocketServer');
3 |
4 | var server;
5 | var wsServer;
6 |
7 | function prepare(callback) {
8 | if (typeof(callback) !== 'function') { callback = function(){}; }
9 | server = http.createServer(function(request, response) {
10 | response.writeHead(404);
11 | response.end();
12 | });
13 |
14 | wsServer = new WebSocketServer({
15 | httpServer: server,
16 | autoAcceptConnections: false,
17 | maxReceivedFrameSize: 64*1024*1024, // 64MiB
18 | maxReceivedMessageSize: 64*1024*1024, // 64MiB
19 | fragmentOutgoingMessages: false,
20 | keepalive: false,
21 | disableNagleAlgorithm: false
22 | });
23 |
24 | server.listen(64321, function(err) {
25 | if (err) {
26 | return callback(err);
27 | }
28 | callback(null, wsServer);
29 | });
30 | }
31 |
32 | function stopServer() {
33 | try {
34 | wsServer.shutDown();
35 | server.close();
36 | }
37 | catch(e) {
38 | console.warn('stopServer threw', e);
39 | }
40 | }
41 |
42 | module.exports = {
43 | prepare: prepare,
44 | stopServer: stopServer
45 | };
46 |
--------------------------------------------------------------------------------
/test/unit/dropBeforeAccept.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var test = require('tape');
4 |
5 | var WebSocketClient = require('../../lib/WebSocketClient');
6 | var server = require('../shared/test-server');
7 | var stopServer = server.stopServer;
8 |
9 | test('Drop TCP Connection Before server accepts the request', function(t) {
10 | t.plan(5);
11 |
12 | server.prepare(function(err, wsServer) {
13 | if (err) {
14 | t.fail('Unable to start test server');
15 | return t.end();
16 | }
17 |
18 | wsServer.on('connect', function(connection) {
19 | t.pass('Server should emit connect event');
20 | });
21 |
22 | wsServer.on('request', function(request) {
23 | t.pass('Request received');
24 |
25 | // Wait 500 ms before accepting connection
26 | setTimeout(function() {
27 | var connection = request.accept(request.requestedProtocols[0], request.origin);
28 |
29 | connection.on('close', function(reasonCode, description) {
30 | t.pass('Connection should emit close event');
31 | t.equal(reasonCode, 1006, 'Close reason code should be 1006');
32 | t.equal(description,
33 | 'TCP connection lost before handshake completed.',
34 | 'Description should be correct');
35 | t.end();
36 | stopServer();
37 | });
38 |
39 | connection.on('error', function(error) {
40 | t.fail('No error events should be received on the connection');
41 | stopServer();
42 | });
43 |
44 | }, 500);
45 | });
46 |
47 | var client = new WebSocketClient();
48 | client.on('connect', function(connection) {
49 | t.fail('Client should never connect.');
50 | connection.drop();
51 | stopServer();
52 | t.end();
53 | });
54 |
55 | client.connect('ws://localhost:64321/', ['test']);
56 |
57 | setTimeout(function() {
58 | // Bail on the connection before we hear back from the server.
59 | client.abort();
60 | }, 250);
61 |
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/unit/regressions.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 |
3 | var WebSocketClient = require('../../lib/WebSocketClient');
4 | var startEchoServer = require('../shared/start-echo-server');
5 |
6 | test('Issue 195 - passing number to connection.send() shouldn\'t throw', function(t) {
7 | startEchoServer(function(err, echoServer) {
8 | if (err) { return t.fail('Unable to start echo server: ' + err); }
9 |
10 | var client = new WebSocketClient();
11 | client.on('connect', function(connection) {
12 | t.pass('connected');
13 |
14 | t.doesNotThrow(function() {
15 | connection.send(12345);
16 | });
17 |
18 | connection.close();
19 | echoServer.kill();
20 | t.end();
21 | });
22 |
23 | client.on('connectFailed', function(errorDescription) {
24 | echoServer.kill();
25 | t.fail(errorDescription);
26 | t.end();
27 | });
28 |
29 | client.connect('ws://localhost:8080', null);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/test/unit/request.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 |
3 | var WebSocketClient = require('../../lib/WebSocketClient');
4 | var server = require('../shared/test-server');
5 | var stopServer = server.stopServer;
6 |
7 | test('Request can only be rejected or accepted once.', function(t) {
8 | t.plan(6);
9 |
10 | t.on('end', function() {
11 | stopServer();
12 | });
13 |
14 | server.prepare(function(err, wsServer) {
15 | if (err) {
16 | t.fail('Unable to start test server');
17 | return t.end();
18 | }
19 |
20 | wsServer.once('request', firstReq);
21 | connect(2);
22 |
23 | function firstReq(request) {
24 | var accept = request.accept.bind(request, request.requestedProtocols[0], request.origin);
25 | var reject = request.reject.bind(request);
26 |
27 | t.doesNotThrow(accept, 'First call to accept() should succeed.');
28 | t.throws(accept, 'Second call to accept() should throw.');
29 | t.throws(reject, 'Call to reject() after accept() should throw.');
30 |
31 | wsServer.once('request', secondReq);
32 | }
33 |
34 | function secondReq(request) {
35 | var accept = request.accept.bind(request, request.requestedProtocols[0], request.origin);
36 | var reject = request.reject.bind(request);
37 |
38 | t.doesNotThrow(reject, 'First call to reject() should succeed.');
39 | t.throws(reject, 'Second call to reject() should throw.');
40 | t.throws(accept, 'Call to accept() after reject() should throw.');
41 |
42 | t.end();
43 | }
44 |
45 | function connect(numTimes) {
46 | var client;
47 | for (var i=0; i < numTimes; i++) {
48 | client = new WebSocketClient();
49 | client.connect('ws://localhost:64321/', 'foo');
50 | client.on('connect', function(connection) { connection.close(); });
51 | }
52 | }
53 | });
54 | });
55 |
56 |
57 | test('Protocol mismatch should be handled gracefully', function(t) {
58 | var wsServer;
59 |
60 | t.test('setup', function(t) {
61 | server.prepare(function(err, result) {
62 | if (err) {
63 | t.fail('Unable to start test server');
64 | return t.end();
65 | }
66 |
67 | wsServer = result;
68 | t.end();
69 | });
70 | });
71 |
72 | t.test('mismatched protocol connection', function(t) {
73 | t.plan(2);
74 | wsServer.on('request', handleRequest);
75 |
76 | var client = new WebSocketClient();
77 |
78 | var timer = setTimeout(function() {
79 | t.fail('Timeout waiting for client event');
80 | }, 2000);
81 |
82 | client.connect('ws://localhost:64321/', 'some_protocol_here');
83 | client.on('connect', function(connection) {
84 | clearTimeout(timer);
85 | connection.close();
86 | t.fail('connect event should not be emitted on client');
87 | });
88 | client.on('connectFailed', function() {
89 | clearTimeout(timer);
90 | t.pass('connectFailed event should be emitted on client');
91 | });
92 |
93 |
94 |
95 | function handleRequest(request) {
96 | var accept = request.accept.bind(request, 'this_is_the_wrong_protocol', request.origin);
97 | t.throws(accept, 'request.accept() should throw');
98 | }
99 | });
100 |
101 | t.test('teardown', function(t) {
102 | stopServer();
103 | t.end();
104 | });
105 | });
106 |
--------------------------------------------------------------------------------
/test/unit/w3cwebsocket.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var test = require('tape');
4 | var WebSocket = require('../../lib/W3CWebSocket');
5 | var startEchoServer = require('../shared/start-echo-server');
6 |
7 | test('W3CWebSockets adding event listeners with ws.onxxxxx', function(t) {
8 | var counter = 0;
9 | var message = 'This is a test message.';
10 |
11 | startEchoServer(function(err, echoServer) {
12 | if (err) { return t.fail('Unable to start echo server: ' + err); }
13 |
14 | var ws = new WebSocket('ws://localhost:8080/');
15 |
16 | ws.onopen = function() {
17 | t.equal(++counter, 1, 'onopen should be called first');
18 |
19 | ws.send(message);
20 | };
21 | ws.onerror = function(event) {
22 | t.fail('No errors are expected: ' + event);
23 | };
24 | ws.onmessage = function(event) {
25 | t.equal(++counter, 2, 'onmessage should be called second');
26 |
27 | t.equal(event.data, message, 'Received message data should match sent message data.');
28 |
29 | ws.close();
30 | };
31 | ws.onclose = function(event) {
32 | t.equal(++counter, 3, 'onclose should be called last');
33 |
34 | echoServer.kill();
35 |
36 | t.end();
37 | };
38 | });
39 | });
40 |
41 | test('W3CWebSockets adding event listeners with ws.addEventListener', function(t) {
42 | var counter = 0;
43 | var message = 'This is a test message.';
44 |
45 | startEchoServer(function(err, echoServer) {
46 | if (err) { return t.fail('Unable to start echo server: ' + err); }
47 |
48 | var ws = new WebSocket('ws://localhost:8080/');
49 |
50 | ws.addEventListener('open', function() {
51 | t.equal(++counter, 1, '"open" should be fired first');
52 |
53 | ws.send(message);
54 | });
55 | ws.addEventListener('error', function(event) {
56 | t.fail('No errors are expected: ' + event);
57 | });
58 | ws.addEventListener('message', function(event) {
59 | t.equal(++counter, 2, '"message" should be fired second');
60 |
61 | t.equal(event.data, message, 'Received message data should match sent message data.');
62 |
63 | ws.close();
64 | });
65 | ws.addEventListener('close', function(event) {
66 | t.equal(++counter, 3, '"close" should be fired');
67 | });
68 | ws.addEventListener('close', function(event) {
69 | t.equal(++counter, 4, '"close" should be fired one more time');
70 |
71 | echoServer.kill();
72 |
73 | t.end();
74 | });
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/test/unit/websocketFrame.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var test = require('tape');
4 | var bufferEqual = require('buffer-equal');
5 | var WebSocketFrame = require('../../lib/WebSocketFrame');
6 | var utils = require('../../lib/utils');
7 | var bufferAllocUnsafe = utils.bufferAllocUnsafe;
8 | var bufferFromString = utils.bufferFromString;
9 |
10 |
11 | test('Serializing a WebSocket Frame with no data', function(t) {
12 | t.plan(2);
13 |
14 | // WebSocketFrame uses a per-connection buffer for the mask bytes
15 | // and the frame header to avoid allocating tons of small chunks of RAM.
16 | var maskBytesBuffer = bufferAllocUnsafe(4);
17 | var frameHeaderBuffer = bufferAllocUnsafe(10);
18 |
19 | var frameBytes;
20 | var frame = new WebSocketFrame(maskBytesBuffer, frameHeaderBuffer, {});
21 | frame.fin = true;
22 | frame.mask = true;
23 | frame.opcode = 0x09; // WebSocketFrame.PING
24 | t.doesNotThrow(
25 | function() { frameBytes = frame.toBuffer(true); },
26 | 'should not throw an error'
27 | );
28 |
29 | t.assert(
30 | bufferEqual
31 | (frameBytes, bufferFromString('898000000000', 'hex')),
32 | 'Generated bytes should be correct'
33 | );
34 |
35 | t.end();
36 | });
37 |
38 | test('Serializing a WebSocket Frame with 16-bit length payload', function(t) {
39 | t.plan(2);
40 |
41 | var maskBytesBuffer = bufferAllocUnsafe(4);
42 | var frameHeaderBuffer = bufferAllocUnsafe(10);
43 |
44 | var payload = bufferAllocUnsafe(200);
45 | for (var i = 0; i < payload.length; i++) {
46 | payload[i] = i % 256;
47 | }
48 |
49 | var frameBytes;
50 | var frame = new WebSocketFrame(maskBytesBuffer, frameHeaderBuffer, {});
51 | frame.fin = true;
52 | frame.mask = true;
53 | frame.opcode = 0x02; // WebSocketFrame.BINARY
54 | frame.binaryPayload = payload;
55 | t.doesNotThrow(
56 | function() { frameBytes = frame.toBuffer(true); },
57 | 'should not throw an error'
58 | );
59 |
60 | var expected = bufferAllocUnsafe(2 + 2 + 4 + payload.length);
61 | expected[0] = 0x82;
62 | expected[1] = 0xFE;
63 | expected.writeUInt16BE(payload.length, 2);
64 | expected.writeUInt32BE(0, 4);
65 | payload.copy(expected, 8);
66 |
67 | t.assert(
68 | bufferEqual(frameBytes, expected),
69 | 'Generated bytes should be correct'
70 | );
71 |
72 | t.end();
73 | });
74 |
75 | test('Serializing a WebSocket Frame with 64-bit length payload', function(t) {
76 | t.plan(2);
77 |
78 | var maskBytesBuffer = bufferAllocUnsafe(4);
79 | var frameHeaderBuffer = bufferAllocUnsafe(10);
80 |
81 | var payload = bufferAllocUnsafe(66000);
82 | for (var i = 0; i < payload.length; i++) {
83 | payload[i] = i % 256;
84 | }
85 |
86 | var frameBytes;
87 | var frame = new WebSocketFrame(maskBytesBuffer, frameHeaderBuffer, {});
88 | frame.fin = true;
89 | frame.mask = true;
90 | frame.opcode = 0x02; // WebSocketFrame.BINARY
91 | frame.binaryPayload = payload;
92 | t.doesNotThrow(
93 | function() { frameBytes = frame.toBuffer(true); },
94 | 'should not throw an error'
95 | );
96 |
97 | var expected = bufferAllocUnsafe(2 + 8 + 4 + payload.length);
98 | expected[0] = 0x82;
99 | expected[1] = 0xFF;
100 | expected.writeUInt32BE(0, 2);
101 | expected.writeUInt32BE(payload.length, 6);
102 | expected.writeUInt32BE(0, 10);
103 | payload.copy(expected, 14);
104 |
105 | t.assert(
106 | bufferEqual(frameBytes, expected),
107 | 'Generated bytes should be correct'
108 | );
109 |
110 | t.end();
111 | });
112 |
--------------------------------------------------------------------------------
/vendor/FastBufferList.js:
--------------------------------------------------------------------------------
1 | // This file was copied from https://github.com/substack/node-bufferlist
2 | // and modified to be able to copy bytes from the bufferlist directly into
3 | // a pre-existing fixed-size buffer without an additional memory allocation.
4 |
5 | // bufferlist.js
6 | // Treat a linked list of buffers as a single variable-size buffer.
7 | var Buffer = require('buffer').Buffer;
8 | var EventEmitter = require('events').EventEmitter;
9 | var bufferAllocUnsafe = require('../lib/utils').bufferAllocUnsafe;
10 |
11 | module.exports = BufferList;
12 | module.exports.BufferList = BufferList; // backwards compatibility
13 |
14 | function BufferList(opts) {
15 | if (!(this instanceof BufferList)) return new BufferList(opts);
16 | EventEmitter.call(this);
17 | var self = this;
18 |
19 | if (typeof(opts) == 'undefined') opts = {};
20 |
21 | // default encoding to use for take(). Leaving as 'undefined'
22 | // makes take() return a Buffer instead.
23 | self.encoding = opts.encoding;
24 |
25 | var head = { next : null, buffer : null };
26 | var last = { next : null, buffer : null };
27 |
28 | // length can get negative when advanced past the end
29 | // and this is the desired behavior
30 | var length = 0;
31 | self.__defineGetter__('length', function () {
32 | return length;
33 | });
34 |
35 | // keep an offset of the head to decide when to head = head.next
36 | var offset = 0;
37 |
38 | // Write to the bufferlist. Emits 'write'. Always returns true.
39 | self.write = function (buf) {
40 | if (!head.buffer) {
41 | head.buffer = buf;
42 | last = head;
43 | }
44 | else {
45 | last.next = { next : null, buffer : buf };
46 | last = last.next;
47 | }
48 | length += buf.length;
49 | self.emit('write', buf);
50 | return true;
51 | };
52 |
53 | self.end = function (buf) {
54 | if (Buffer.isBuffer(buf)) self.write(buf);
55 | };
56 |
57 | // Push buffers to the end of the linked list. (deprecated)
58 | // Return this (self).
59 | self.push = function () {
60 | var args = [].concat.apply([], arguments);
61 | args.forEach(self.write);
62 | return self;
63 | };
64 |
65 | // For each buffer, perform some action.
66 | // If fn's result is a true value, cut out early.
67 | // Returns this (self).
68 | self.forEach = function (fn) {
69 | if (!head.buffer) return bufferAllocUnsafe(0);
70 |
71 | if (head.buffer.length - offset <= 0) return self;
72 | var firstBuf = head.buffer.slice(offset);
73 |
74 | var b = { buffer : firstBuf, next : head.next };
75 |
76 | while (b && b.buffer) {
77 | var r = fn(b.buffer);
78 | if (r) break;
79 | b = b.next;
80 | }
81 |
82 | return self;
83 | };
84 |
85 | // Create a single Buffer out of all the chunks or some subset specified by
86 | // start and one-past the end (like slice) in bytes.
87 | self.join = function (start, end) {
88 | if (!head.buffer) return bufferAllocUnsafe(0);
89 | if (start == undefined) start = 0;
90 | if (end == undefined) end = self.length;
91 |
92 | var big = bufferAllocUnsafe(end - start);
93 | var ix = 0;
94 | self.forEach(function (buffer) {
95 | if (start < (ix + buffer.length) && ix < end) {
96 | // at least partially contained in the range
97 | buffer.copy(
98 | big,
99 | Math.max(0, ix - start),
100 | Math.max(0, start - ix),
101 | Math.min(buffer.length, end - ix)
102 | );
103 | }
104 | ix += buffer.length;
105 | if (ix > end) return true; // stop processing past end
106 | });
107 |
108 | return big;
109 | };
110 |
111 | self.joinInto = function (targetBuffer, targetStart, sourceStart, sourceEnd) {
112 | if (!head.buffer) return new bufferAllocUnsafe(0);
113 | if (sourceStart == undefined) sourceStart = 0;
114 | if (sourceEnd == undefined) sourceEnd = self.length;
115 |
116 | var big = targetBuffer;
117 | if (big.length - targetStart < sourceEnd - sourceStart) {
118 | throw new Error("Insufficient space available in target Buffer.");
119 | }
120 | var ix = 0;
121 | self.forEach(function (buffer) {
122 | if (sourceStart < (ix + buffer.length) && ix < sourceEnd) {
123 | // at least partially contained in the range
124 | buffer.copy(
125 | big,
126 | Math.max(targetStart, targetStart + ix - sourceStart),
127 | Math.max(0, sourceStart - ix),
128 | Math.min(buffer.length, sourceEnd - ix)
129 | );
130 | }
131 | ix += buffer.length;
132 | if (ix > sourceEnd) return true; // stop processing past end
133 | });
134 |
135 | return big;
136 | };
137 |
138 | // Advance the buffer stream by n bytes.
139 | // If n the aggregate advance offset passes the end of the buffer list,
140 | // operations such as .take() will return empty strings until enough data is
141 | // pushed.
142 | // Returns this (self).
143 | self.advance = function (n) {
144 | offset += n;
145 | length -= n;
146 | while (head.buffer && offset >= head.buffer.length) {
147 | offset -= head.buffer.length;
148 | head = head.next
149 | ? head.next
150 | : { buffer : null, next : null }
151 | ;
152 | }
153 | if (head.buffer === null) last = { next : null, buffer : null };
154 | self.emit('advance', n);
155 | return self;
156 | };
157 |
158 | // Take n bytes from the start of the buffers.
159 | // Returns a string.
160 | // If there are less than n bytes in all the buffers or n is undefined,
161 | // returns the entire concatenated buffer string.
162 | self.take = function (n, encoding) {
163 | if (n == undefined) n = self.length;
164 | else if (typeof n !== 'number') {
165 | encoding = n;
166 | n = self.length;
167 | }
168 | var b = head;
169 | if (!encoding) encoding = self.encoding;
170 | if (encoding) {
171 | var acc = '';
172 | self.forEach(function (buffer) {
173 | if (n <= 0) return true;
174 | acc += buffer.toString(
175 | encoding, 0, Math.min(n,buffer.length)
176 | );
177 | n -= buffer.length;
178 | });
179 | return acc;
180 | } else {
181 | // If no 'encoding' is specified, then return a Buffer.
182 | return self.join(0, n);
183 | }
184 | };
185 |
186 | // The entire concatenated buffer as a string.
187 | self.toString = function () {
188 | return self.take('binary');
189 | };
190 | }
191 | require('util').inherits(BufferList, EventEmitter);
192 |
--------------------------------------------------------------------------------