├── TODO.md ├── package.json ├── examples ├── echo-server.js ├── ssl │ ├── cert.key │ └── cert.pem ├── chat.html ├── chat-server.js ├── test-https-upgrade.js ├── dev-server.js └── client.html ├── LICENSE.md ├── lib ├── ws │ ├── mem-store.js │ ├── manager.js │ ├── parser.js │ └── connection.js └── ws.js ├── Rakefile ├── samples └── handshake-packets ├── README.md └── docs ├── style.css └── index.html /TODO.md: -------------------------------------------------------------------------------- 1 | ## NWS TODO ## 2 | 3 | * nothing 4 | 5 | ## NWS Done ## 6 | 7 | * change `server` constructor to have externalServer as option, not argument 8 | * add `connection._req.headers` -> `connection.headers` 9 | * abstract `connection.storage` to use a standard API & events 10 | * add `manager.map` should have similar api to Array.map 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "version": "1.3.52" 2 | , "description": "A WebSocket Server for node.js, 90-100% spec compatible." 3 | , "repository": { "type": "git" 4 | , "url": "http://github.com/miksago/node-websocket-server.git" 5 | } 6 | , "main": "./lib/ws" 7 | , "bugs": { "web": "http://github.com/miksago/node-websocket-server/issues" 8 | } 9 | , "author": "Micheil Smith " 10 | , "licenses": [{ "type": "MIT", "url": "./LICENSE.md" }] 11 | , "name": "websocket-server" 12 | , "engines": { "node": ">=0.1.98-0"} 13 | } -------------------------------------------------------------------------------- /examples/echo-server.js: -------------------------------------------------------------------------------- 1 | var sys = require("sys") 2 | , ws = require('../lib/ws'); 3 | 4 | var server = ws.createServer(); 5 | 6 | server.addListener("listening", function(){ 7 | sys.log("Listening for connections."); 8 | }); 9 | 10 | // Handle WebSocket Requests 11 | server.addListener("connection", function(conn){ 12 | conn.send("Connection: "+conn.id); 13 | 14 | conn.addListener("message", function(message){ 15 | conn.broadcast("<"+conn.id+"> "+message); 16 | }); 17 | }); 18 | 19 | server.addListener("close", function(conn){ 20 | server.broadcast("<"+conn.id+"> disconnected"); 21 | }); 22 | 23 | server.listen(8000); -------------------------------------------------------------------------------- /examples/ssl/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDJtZNMsy2rH8B1DC7trfDVQ2L3HDTqHn1QIRdCseBUiTNpqqPm 3 | UPBJ1VfygrGZn/gczfd8iii9agiio1GlHEjKVUnf5oZN/3Hb86IlHrOqyn+tlD6k 4 | lxHSoi0a8vKwJg8gIAR9d/+UvlVjXR2Dxq0PKCP4tTG3lXl6pjtiO5xngQIDAQAB 5 | AoGAXjFYk9yoWtz89qu1znAHos9RC6w0WY/bXOvW/OIrq85WH+X7m7X4P8JMy4aU 6 | gPTe3DSieZna5Hj7h1dU7RGEO4OilrW2inlnBWSWXXisEBjhJy5r3AmUcFaN2LBZ 7 | cc4UCfwbI/Sp0FwRmpTOk7AqRBMMgzDEUjgZPr5spt9dMfECQQDtPq41aFGtRec2 8 | bxakE1SO026yhQuTJNvXC8tKyc8NFZ+z710h+LzzXCrh9HmapCNntQ+B0tOO7gWL 9 | GUNGnu8rAkEA2ae7ee6BOSoxVnrxtjSF+OB5xjqqInhoblQCR5xUo0gQf5SBvSJ4 10 | 2h1Em1a909os/LBwkVzs0mtHZyARrGHOAwJAIXTHaGyvp/nFo12DGdxH/mQ+phox 11 | Ca0W+3qETqWq9zNndB57ScEkFEWZHog1ZeNjwso7x5kNkIhsa7NCU4EyRQJAQiqW 12 | eJvD1u/3rbWUU3KmI6GCA3wECpSWEqwhQUnKBRU8RryLsEaRfKUXHS4CaSvTL0Io 13 | Bli8eb85JqV+GexlpwJAIpbqzTdQXrNF9AgKAV9FarE400/KMZX5bYSCrU1AbI+t 14 | I2DIk0SItWfVGkBFYd5X+JK/IwdCzFIZ4ifmx8Kp3Q== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2010 Micheil Smith. 2 | 3 | All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/ssl/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDuDCCAyGgAwIBAgIJAI8+1nULngRFMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD 3 | VQQGEwJBVTEMMAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZMZWV0b24xFDASBgNVBAoT 4 | C0JyYW5kZWRDb2RlMS4wLAYDVQQDEyVNaWNoZWlsIFNtaXRoIC8gTm9kZS13ZWJz 5 | b2NrZXQtc2VydmVyMSYwJAYJKoZIhvcNAQkBFhdtaWNoZWlsQGJyYW5kZWRjb2Rl 6 | LmNvbTAeFw0xMDA4MjUxNjQxMDlaFw0xMTA4MjUxNjQxMDlaMIGaMQswCQYDVQQG 7 | EwJBVTEMMAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZMZWV0b24xFDASBgNVBAoTC0Jy 8 | YW5kZWRDb2RlMS4wLAYDVQQDEyVNaWNoZWlsIFNtaXRoIC8gTm9kZS13ZWJzb2Nr 9 | ZXQtc2VydmVyMSYwJAYJKoZIhvcNAQkBFhdtaWNoZWlsQGJyYW5kZWRjb2RlLmNv 10 | bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAybWTTLMtqx/AdQwu7a3w1UNi 11 | 9xw06h59UCEXQrHgVIkzaaqj5lDwSdVX8oKxmZ/4HM33fIoovWoIoqNRpRxIylVJ 12 | 3+aGTf9x2/OiJR6zqsp/rZQ+pJcR0qItGvLysCYPICAEfXf/lL5VY10dg8atDygj 13 | +LUxt5V5eqY7YjucZ4ECAwEAAaOCAQIwgf8wHQYDVR0OBBYEFEYrvzkTYSwCqlOc 14 | F9u+1GZZPiL3MIHPBgNVHSMEgccwgcSAFEYrvzkTYSwCqlOcF9u+1GZZPiL3oYGg 15 | pIGdMIGaMQswCQYDVQQGEwJBVTEMMAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZMZWV0 16 | b24xFDASBgNVBAoTC0JyYW5kZWRDb2RlMS4wLAYDVQQDEyVNaWNoZWlsIFNtaXRo 17 | IC8gTm9kZS13ZWJzb2NrZXQtc2VydmVyMSYwJAYJKoZIhvcNAQkBFhdtaWNoZWls 18 | QGJyYW5kZWRjb2RlLmNvbYIJAI8+1nULngRFMAwGA1UdEwQFMAMBAf8wDQYJKoZI 19 | hvcNAQEFBQADgYEAXE1dN7pt5f1+Yzyr6SDsAJwQiYr3v9irq5WihN0hoqXfIECX 20 | w8cL710RKFvYPM9p1lEzYanAcQMs+Jygx5XCjYCNREqx7ymg5XZY9NQxu6RVbD6N 21 | BTyooToiX8UnkbDH6lnc7I4L+YtaImBfZ+B29+W5lTKTr9a+anXAqkj2r4g= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /lib/ws/mem-store.js: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------- 2 | Storage is a simple in memory storage object. 3 | In otherwords, don't run your website off it. 4 | -----------------------------------------------*/ 5 | 6 | module.exports = { 7 | create: function(){ 8 | return new memStore(); 9 | } 10 | }; 11 | 12 | function memStore(){ 13 | var data = {}; 14 | 15 | return { 16 | set: function(key, value){ 17 | return data[key] = value; 18 | }, 19 | 20 | get: function(key, def){ 21 | if(data[key] !== undefined){ 22 | return data[key]; 23 | } else { 24 | return def; 25 | } 26 | }, 27 | 28 | exists: function(key){ 29 | return data[key] !== undefined; 30 | }, 31 | 32 | incr: function(key){ 33 | if(data[key] === undefined){ 34 | data[key] = 0; 35 | } 36 | 37 | if(typeof(data[key]) === "number"){ 38 | return data[key]++; 39 | } 40 | }, 41 | 42 | decr: function(key){ 43 | if(typeof(data[key]) === "number"){ 44 | return data[key]--; 45 | } 46 | }, 47 | 48 | push: function(key, value){ 49 | if(data[key] === undefined){ 50 | data[key] = []; 51 | } 52 | 53 | if(Array.isArray(data[key])){ 54 | data[key].push(value); 55 | } 56 | }, 57 | 58 | pop: function(key){ 59 | if(data[key] !== undefined && Array.isArray(data[key])){ 60 | return data[key].pop(); 61 | } 62 | }, 63 | 64 | del: function(key){ 65 | data[key] = undefined; 66 | }, 67 | 68 | to_json: function(){ 69 | return JSON.stringify(data); 70 | } 71 | }; 72 | }; -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rubygems' 3 | require 'json' 4 | 5 | puts "---" 6 | 7 | 8 | @version = `git describe`.match("v([0-9]+\.[0-9]+.\[0-9]+).*")[1] 9 | 10 | def to_node_json(obj) 11 | obj = obj.to_json 12 | # reformat it a bit 13 | obj.gsub!(/\"\}\]|\[\{|\}\,|\:\{|\:\[[^\{]|\{\"|\,\"|\:\{\"|\"\:|\"\}$/) {|s| 14 | case s 15 | when ",\""; "\n, \"" 16 | when "{\""; "{ \"" 17 | when ":{"; ":\n {" 18 | when ":["; ": [\n" 19 | when "},"; "}\n, " 20 | when "[{"; "[{ " 21 | when "\"}]"; "\" }]" 22 | when "\":"; "\": " 23 | when "\"}"; "\"\n}" 24 | else; s 25 | end 26 | } 27 | # finally clean up arrays. 28 | obj.gsub!(/(.+\[.+)\n\,/, '\1,').gsub(/\}$/, "\n}") 29 | end 30 | 31 | desc "Release a new version to various places." 32 | task :release => ['write:pkgspec', 'npm:publish'] do 33 | puts "Released!" 34 | puts "" 35 | end 36 | 37 | namespace :write do 38 | desc "Write the package.json file" 39 | task :pkgspec do 40 | package = { 41 | :name => "websocket-server", 42 | :version => @version, 43 | :author => "Micheil Smith ", 44 | :description => "A WebSocket Server for node.js, 90-100% spec compatible.", 45 | :main => "./lib/ws", 46 | :engines => { :node => ">=0.1.98-0" }, 47 | :licenses => [{ :type => "MIT", :url => "./LICENSE.md" }], 48 | :repository => { 49 | :type => "git", 50 | :url => "http://github.com/miksago/node-websocket-server.git" 51 | }, 52 | :bugs => { 53 | :web => "http://github.com/miksago/node-websocket-server/issues" 54 | } 55 | } 56 | 57 | puts "Making package.json" 58 | f = File.new("package.json", "w+") 59 | f.write(to_node_json(package)) 60 | f.close 61 | puts "-> Done" 62 | end 63 | end 64 | 65 | namespace :npm do 66 | desc "Publish to NPM" 67 | task :publish do 68 | puts "Publishing to NPM" 69 | system("npm publish #{`pwd`}") 70 | system("npm tag websocket-server #{@version} latest") 71 | puts "-> Done" 72 | end 73 | end -------------------------------------------------------------------------------- /examples/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat demo 5 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 | 92 | 93 | -------------------------------------------------------------------------------- /examples/chat-server.js: -------------------------------------------------------------------------------- 1 | var sys = require("sys") 2 | , http = require("http") 3 | , fs = require("fs") 4 | , path = require("path") 5 | , ws = require('../lib/ws'); 6 | 7 | var httpServer = http.createServer(function(req, res){ 8 | if(req.method == "GET"){ 9 | if( req.url.indexOf("favicon") > -1 ){ 10 | res.writeHead(200, {'Content-Type': 'image/x-icon', 'Connection': 'close'}); 11 | res.end(""); 12 | } else { 13 | res.writeHead(200, {'Content-Type': 'text/html', 'Connection': 'close'}); 14 | fs.createReadStream( path.normalize(path.join(__dirname, "chat.html")), { 15 | 'flags': 'r', 16 | 'encoding': 'binary', 17 | 'mode': 0666, 18 | 'bufferSize': 4 * 1024 19 | }).addListener("data", function(chunk){ 20 | res.write(chunk, 'binary'); 21 | }).addListener("end",function() { 22 | res.end(); 23 | }); 24 | } 25 | } else { 26 | res.writeHead(404); 27 | res.end(); 28 | } 29 | }); 30 | 31 | 32 | var server = ws.createServer({ 33 | server: httpServer 34 | }); 35 | 36 | server.addListener("listening", function(){ 37 | sys.log("Listening for connections."); 38 | }); 39 | 40 | // Handle WebSocket Requests 41 | server.addListener("connection", function(conn){ 42 | conn.storage.set("username", "user_"+conn.id); 43 | 44 | conn.send("** Connected as: user_"+conn.id); 45 | conn.send("** Type `/nick USERNAME` to change your username"); 46 | 47 | conn.broadcast("** "+conn.storage.get("username")+" connected"); 48 | 49 | conn.addListener("message", function(message){ 50 | if(message[0] == "/"){ 51 | // set username 52 | if((matches = message.match(/^\/nick (\w+)$/i)) && matches[1]){ 53 | conn.storage.set("username", matches[1]); 54 | conn.send("** you are now known as: "+matches[1]); 55 | 56 | // get message count 57 | } else if(/^\/stats/.test(message)){ 58 | conn.send("** you have sent "+conn.storage.get("messages", 0)+" messages."); 59 | } 60 | } else { 61 | conn.storage.incr("messages"); 62 | server.broadcast(conn.storage.get("username")+": "+message); 63 | } 64 | }); 65 | }); 66 | 67 | server.addListener("close", function(conn){ 68 | server.broadcast("<"+conn.id+"> disconnected"); 69 | }); 70 | 71 | server.listen(8000); -------------------------------------------------------------------------------- /samples/handshake-packets: -------------------------------------------------------------------------------- 1 | GET /chat.ws HTTP/1.1 2 | Upgrade: WebSocket 3 | Connection: Upgrade 4 | 5 | 6 | GET /secure HTTP/1.1 7 | Host: example.bank.com 8 | Upgrade: TLS/1.0 9 | Connection: Upgrade 10 | 11 | 12 | GET /app/cc4ce1421747bb6b1f44?channel=test_channel HTTP/1.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\n\r\n 13 | 14 | GET /app/cc4ce1421747bb6b1f44?channel=test_channel HTTP/1.1 15 | Upgrade: WebSocket 16 | Connection: Upgrade 17 | Host: ws.pusherapp.com 18 | Origin: http://ws.pusherapp.com 19 | 20 | 21 | GET /demo HTTP/1.1 22 | Host: example.com 23 | Connection: Upgrade 24 | Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 25 | Sec-WebSocket-Protocol: sample 26 | Upgrade: WebSocket 27 | Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 28 | Origin: http://example.com 29 | 30 | ^n:ds[4U 31 | 32 | 33 | GET /test HTTP/1.1 34 | Upgrade: WebSocket 35 | Connection: Upgrade 36 | Host: 192.168.46.19:7000 37 | Origin: http://localhost:3000 38 | Content-Length: 4 39 | 40 | test 41 | 42 | 43 | 44 | GET / HTTP/1.1 45 | Connection: Upgrade 46 | Upgrade: WebSocket 47 | Sec-WebSocket-Key1: 3e6b263 4 17 80 48 | Origin: http://example.com 49 | Sec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90 50 | 51 | WjN}|M(6 52 | 53 | 54 | 55 | 56 | 57 | GET / HTTP/1.1 58 | Connection: Upgrade 59 | Host: example.com 60 | Upgrade: WebSocket 61 | Sec-WebSocket-Key1: 3e6b263 4 17 80 62 | Origin: http://example.com 63 | Sec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90 64 | 65 | WjN}|M(6 66 | 67 | 68 | 69 | String.fromCharCode(829309203>>>16, 829309203 & 0xFF, 259970620>>>16, 259970620&0xFF) + ^n:ds[4U 70 | 71 | 72 | GET / HTTP/1.1 73 | Host: example.com 74 | Upgrade: WebSocket 75 | Sec-WebSocket-Key1: 3e6b263 4 17 80 76 | Origin: http://example.com 77 | Sec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90 78 | 79 | WjN}|M(6 80 | 81 | 82 | GET / HTTP/1.1 83 | Host: example.com 84 | Origin: http://example.com 85 | 86 | POST / HTTP/1.1 87 | Host: example.com 88 | Origin: http://example.com 89 | 90 | POST / HTTP/1.1 91 | Upgrade: WebSocket 92 | Connection: Upgrade 93 | Host: example.com 94 | Origin: http://example.com 95 | 96 | GET / HTTP/1.1 97 | Upgrade: WebSocket 98 | Connection: Upgrade 99 | Host: example.com 100 | Origin: http://example.com 101 | 102 | 103 | -------------------------------------------------------------------------------- /examples/test-https-upgrade.js: -------------------------------------------------------------------------------- 1 | assert = require('assert'); 2 | var path = require("path"); 3 | 4 | net = require("net"); 5 | http = require("http"); 6 | url = require("url"); 7 | qs = require("querystring"); 8 | var fs = require('fs'); 9 | var sys = require('sys'); 10 | 11 | var have_openssl; 12 | try { 13 | var crypto = require('crypto'); 14 | var dummy_server = http.createServer(function(){}); 15 | dummy_server.setSecure(); 16 | have_openssl=true; 17 | } catch (e) { 18 | have_openssl=false; 19 | console.log("Not compiled with OPENSSL support."); 20 | process.exit(); 21 | } 22 | 23 | var request_number = 0; 24 | var requests_sent = 0; 25 | var server_response = ""; 26 | var client_got_eof = false; 27 | var caPem = fs.readFileSync( path.normalize(path.join(__dirname, "test_ca.pem")), 'ascii'); 28 | var certPem = fs.readFileSync(path.normalize(path.join(__dirname, "test_cert.pem")), 'ascii'); 29 | var keyPem = fs.readFileSync(path.normalize(path.join(__dirname, "test_key.pem")), 'ascii'); 30 | 31 | var credentials = crypto.createCredentials({key:keyPem, cert:certPem, ca:caPem}); 32 | 33 | var https_server = http.createServer(function (req, res) { 34 | request_number++; 35 | res.writeHead(204, {"Content-Type": "text/plain"}); 36 | 37 | res.end(); 38 | }); 39 | 40 | https_server.on("upgrade", function(req, socket, upgradeHead){ 41 | console.log("upgrade!"); 42 | 43 | request_number++; 44 | 45 | socket.write("hello"); 46 | 47 | socket.on("data", function(d){ 48 | console.log("server recv", d) 49 | socket.write("die"); 50 | }) 51 | 52 | socket.on("end", function(){ 53 | socket.end(); 54 | socket.destroy(); 55 | }); 56 | }) 57 | 58 | https_server.setSecure(credentials); 59 | https_server.listen(123456); 60 | 61 | https_server.addListener("listening", function() { 62 | var c = net.createConnection(123456); 63 | 64 | c.setEncoding("utf8"); 65 | 66 | c.addListener("connect", function () { 67 | console.log("c.connect") 68 | c.setSecure(credentials); 69 | }); 70 | 71 | c.addListener("secure", function () { 72 | c.write("GET / HTTP/1.1\r\n\r\n"); 73 | requests_sent++; 74 | c.write("GET / HTTP/1.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\n\r\n"); 75 | requests_sent++; 76 | }); 77 | 78 | c.addListener("data", function (chunk) { 79 | console.log("<< c: '", chunk, "'"); 80 | if(chunk != "hello"){ 81 | c.end(); 82 | } else { 83 | c.write(chunk); 84 | console.log(">> c:", chunk) 85 | } 86 | }); 87 | 88 | c.addListener("end", function () { 89 | console.log("c.end") 90 | c.destroy(); 91 | }); 92 | 93 | c.addListener("close", function () { 94 | console.log("c.close"); 95 | console.dir(https_server); 96 | https_server.close(); 97 | }); 98 | }); 99 | 100 | process.addListener("exit", function () { 101 | assert.equal(2, request_number); 102 | assert.equal(2, requests_sent); 103 | }); 104 | -------------------------------------------------------------------------------- /lib/ws/manager.js: -------------------------------------------------------------------------------- 1 | var debug, sys; 2 | 3 | /*----------------------------------------------- 4 | Connection Manager 5 | -----------------------------------------------*/ 6 | module.exports = Manager; 7 | 8 | function Manager(showDebug){ 9 | if(showDebug) { 10 | sys = require("sys"); 11 | debug = function(){sys.error('\033[31mManager: ' + Array.prototype.join.call(arguments, ", ") + "\033[39m"); }; 12 | } else { 13 | debug = function(){}; 14 | } 15 | 16 | this._head = null; 17 | this._tail = null; 18 | this._length = 0; 19 | }; 20 | 21 | Object.defineProperty(Manager.prototype, "length", { 22 | get: function(){ 23 | return this._length; 24 | } 25 | }); 26 | 27 | 28 | Manager.prototype.attach = function(id, client){ 29 | var connection = { 30 | id: id, 31 | _next: null, 32 | client: client 33 | }; 34 | 35 | if(this._length == 0) { 36 | this._head = connection; 37 | this._tail = connection; 38 | } else { 39 | this._tail._next = connection; 40 | this._tail = connection; 41 | } 42 | 43 | ++this._length; 44 | 45 | debug("Attached: "+id, this._length); 46 | }; 47 | 48 | Manager.prototype.detach = function(id, callback){ 49 | var previous = current = this._head; 50 | 51 | while(current !== null){ 52 | if(current.id === id){ 53 | previous._next = current._next; 54 | this._length--; 55 | 56 | if(current.id === this._head.id){ 57 | this._head = current._next; 58 | } 59 | if(current.id === this._tail.id){ 60 | this._tail = previous; 61 | } 62 | 63 | break; 64 | } else { 65 | previous = current; 66 | current = current._next; 67 | } 68 | } 69 | 70 | delete current, previous; 71 | 72 | debug("Detached: "+id, this._length); 73 | callback(); 74 | }; 75 | 76 | Manager.prototype.find = function(id, callback, thisArg){ 77 | var current = this._head; 78 | 79 | while(current !== null){ 80 | if(current.id === id){ 81 | callback.call(thisArg, current.client); 82 | break; 83 | } else { 84 | current = current._next; 85 | } 86 | } 87 | }; 88 | 89 | Manager.prototype.forEach = function(callback, thisArg){ 90 | var current = this._head; 91 | 92 | while(current !== null){ 93 | callback.call(thisArg, current.client); 94 | current = current._next; 95 | } 96 | }; 97 | 98 | Manager.prototype.map = function(callback, thisArg){ 99 | var current = this._head 100 | , len = 0 101 | , result = new Array(this._length); 102 | 103 | while(current !== null){ 104 | result[len] = callback.call(thisArg, current.client, len, this._head); 105 | current = current._next; 106 | ++len; 107 | } 108 | 109 | return result; 110 | }; 111 | 112 | Manager.prototype.filter = function(callback, thisArg){ 113 | var current = this._head 114 | , len = 0 115 | , result = new Array(this._length); 116 | 117 | while(current !== null){ 118 | if( Boolean(callback.call(thisArg, current.client, len, this._head)) ){ 119 | result[len] = current.client; 120 | ++len; 121 | } 122 | 123 | current = current._next; 124 | } 125 | 126 | return result; 127 | }; -------------------------------------------------------------------------------- /examples/dev-server.js: -------------------------------------------------------------------------------- 1 | var sys = require("sys") 2 | , fs = require("fs") 3 | , Path = require("path") 4 | , http = require("http") 5 | , crypto = require("crypto") 6 | , ws = require('../lib/ws'); 7 | 8 | console.log(process.pid); 9 | 10 | /*----------------------------------------------- 11 | logging: 12 | -----------------------------------------------*/ 13 | var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 14 | 15 | function pad(n) { 16 | return n < 10 ? '0' + n.toString(10) : n.toString(10); 17 | } 18 | 19 | function timestamp() { 20 | var d = new Date(); 21 | return [ 22 | d.getDate(), 23 | months[d.getMonth()], 24 | [ pad(d.getHours()) 25 | , pad(d.getMinutes()) 26 | , pad(d.getSeconds()) 27 | , (d.getTime() + "").substr( - 4, 4) 28 | ].join(':') 29 | ].join(' '); 30 | }; 31 | 32 | function log(msg) { 33 | sys.puts(timestamp() + ' - ' + msg.toString()); 34 | }; 35 | 36 | var cache = {}; 37 | function readPage(path, callback){ 38 | if(cache[path]) { 39 | callback(cache[path]); 40 | } else { 41 | cache[path] = []; 42 | 43 | fs.createReadStream( Path.normalize(Path.join(__dirname, path)), { 44 | 'flags': 'r', 45 | 'encoding': 'binary', 46 | 'mode': 0666, 47 | 'bufferSize': 4 * 1024 48 | }).addListener("data", function(chunk){ 49 | cache[path].push(chunk); 50 | }).on("end", function(){ 51 | callback(cache[path]); 52 | }); 53 | } 54 | } 55 | 56 | readPage("client.html", function(){}); 57 | 58 | var reqnum = 0; 59 | function serveFile(req, res){ 60 | if(req.method == "GET"){ 61 | if( req.url.indexOf("favicon") > -1 ){ 62 | log("HTTP: inbound request, served nothing, (favicon)"); 63 | 64 | res.writeHead(200, {'Content-Type': 'image/x-icon'}); 65 | res.end(""); 66 | } else { 67 | log("HTTP: inbound request, served client.html"); 68 | res.writeHead(200, {'Content-Type': 'text/html', 'Connection': 'close'}); 69 | readPage("client.html", function(data){ 70 | data.forEach(function(datum){ 71 | res.write(datum); 72 | }); 73 | 74 | res.end(); 75 | }); 76 | } 77 | } else { 78 | res.writeHead(200, {'Content-Type': 'text/html'}); 79 | res.end(); 80 | } 81 | }; 82 | 83 | /*----------------------------------------------- 84 | Spin up our server: 85 | -----------------------------------------------*/ 86 | 87 | var certPem = fs.readFileSync(Path.normalize(Path.join(__dirname, "ssl/cert.pem")), 'ascii'); 88 | var keyPem = fs.readFileSync(Path.normalize(Path.join(__dirname, "ssl/cert.key")), 'ascii'); 89 | 90 | var credentials = crypto.createCredentials({key:keyPem, cert:certPem}); 91 | 92 | 93 | var httpServer = http.createServer(serveFile); 94 | 95 | 96 | var server = ws.createServer({ 97 | debug: true, 98 | useStorage: true, 99 | server: httpServer 100 | }); 101 | server.setSecure(credentials); 102 | 103 | 104 | server.addListener("listening", function(){ 105 | log("Listening for connections."); 106 | }); 107 | 108 | // Handle WebSocket Requests 109 | server.addListener("connection", function(conn){ 110 | conn.send("Connection: "+conn.id); 111 | conn.broadcast("<"+conn.id+"> connected"); 112 | conn.addListener("message", function(message){ 113 | conn.broadcast("<"+conn.id+"> "+message); 114 | // conn.storage.incr("messages"); 115 | }); 116 | }); 117 | 118 | server.addListener("close", function(conn){ 119 | // sys.puts(conn.id+" sent "+conn.storage.get("messages")); 120 | server.broadcast("<"+conn.id+"> disconnected"); 121 | }); 122 | 123 | server.listen(8000); -------------------------------------------------------------------------------- /lib/ws/parser.js: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------- 2 | The new onData callback for 3 | http.Server IncomingMessage 4 | -----------------------------------------------*/ 5 | var sys = require("sys") 6 | , events = require("events") 7 | , Buffer = require("buffer").Buffer; 8 | 9 | module.exports = Parser; 10 | 11 | function Parser(version){ 12 | events.EventEmitter.call(this); 13 | 14 | this.version = version.toLowerCase() || "draft76"; 15 | this.readable = true; 16 | this.paused = false; 17 | 18 | if(this.version == "draft76" || this.version == "draft75"){ 19 | this.frameData = []; 20 | this.frameStage = "begin"; 21 | } 22 | }; 23 | 24 | sys.inherits(Parser, events.EventEmitter); 25 | 26 | 27 | Parser.prototype.write = function(data){ 28 | var pkt, msg; 29 | for(var i = 0, len = data.length; i 132 | }; 133 | ParserStream.prototype.destroy = function(){} 134 | 135 | 136 | // Writable Stream 137 | ParserStream.prototype.write = function(){}; 138 | ParserStream.prototype.flush = function(){}; 139 | ParserStream.prototype.end = function(){}; 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /examples/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | node-websocket-server test 5 | 20 | 21 | 22 |

23 | WARNING: run the Spam Test at your own risk, see below for more information.

24 | Spam Test: The spam test rapidly spends 1 million messages to the websocket-server at a near instant interval, as such, any browsers connected to the server at the time of a spam test may crash or freeze up. The websocket server should not crash, but this is not 100% guaranteed.

25 | The author does not take responsibility for any damage or data loss caused.

26 | 29 |
30 | 141 | 142 | -------------------------------------------------------------------------------- /lib/ws.js: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------- 2 | Requirements: 3 | -----------------------------------------------*/ 4 | var sys = require("sys") 5 | , http = require("http") 6 | , events = require("events") 7 | , path = require("path"); 8 | 9 | require.paths.unshift(__dirname); 10 | 11 | var Manager = require("./ws/manager") 12 | , Connection = require("./ws/connection"); 13 | 14 | 15 | /*----------------------------------------------- 16 | Mixin: 17 | -----------------------------------------------*/ 18 | var mixin = function(target, source) { 19 | for(var i = 0, keys = Object.keys(source), l = keys.length; i < l; ++i) { 20 | if(source.hasOwnProperty(keys[i])){ 21 | target[keys[i]] = source[keys[i]]; 22 | } 23 | } 24 | return target; 25 | }; 26 | 27 | /*----------------------------------------------- 28 | WebSocket Server Exports: 29 | -----------------------------------------------*/ 30 | exports.Server = Server; 31 | exports.createServer = function(options){ 32 | return new Server(options); 33 | }; 34 | 35 | exports._Manager = Manager; 36 | exports._Connection = Connection; 37 | 38 | /*----------------------------------------------- 39 | WebSocket Server Implementation: 40 | -----------------------------------------------*/ 41 | 42 | function Server(options){ 43 | var ws = this; 44 | 45 | events.EventEmitter.call(this); 46 | 47 | this.options = mixin({ 48 | debug: false, // Boolean: Show debug information. 49 | version: "auto", // String: Value must be either: draft75, draft76, auto 50 | origin: "*", // String, Array: A match for a valid connection origin 51 | subprotocol: "*", // String, Array: A match for a valid connection subprotocol. 52 | datastore: true, // Object, Function, Boolean: If === true, then it is the default mem-store. 53 | server: new http.Server() 54 | }, options || {}); 55 | 56 | this.manager = new Manager(this.options.debug); 57 | this.server = this.options.server 58 | this.debug = this.options.debug; 59 | 60 | this.server.addListener("upgrade", function(req, socket, upgradeHead){ 61 | if( req.method == "GET" && ( "upgrade" in req.headers && "connection" in req.headers) && 62 | req.headers.upgrade.toLowerCase() == "websocket" && req.headers.connection.toLowerCase() == "upgrade" 63 | ){ 64 | // create a new connection, it'll handle everything else. 65 | new Connection(ws, req, socket, upgradeHead); 66 | } else { 67 | // Close the socket, it wasn't a valid connection. 68 | socket.end(); 69 | socket.destroy(); 70 | } 71 | }); 72 | 73 | this.server.addListener("connection", function(socket){ 74 | socket.setTimeout(0); 75 | socket.setNoDelay(true); 76 | socket.setKeepAlive(true, 0); 77 | }); 78 | 79 | this.server.addListener("listening", function(req, res){ 80 | ws.emit("listening"); 81 | }); 82 | 83 | this.server.addListener("close", function(errno){ 84 | ws.emit("shutdown", errno); 85 | }); 86 | 87 | this.server.addListener("request", function(req, res){ 88 | ws.emit("request", req, res); 89 | }); 90 | 91 | this.server.addListener("stream", function(stream){ 92 | ws.emit("stream", stream); 93 | }); 94 | 95 | this.server.addListener("clientError", function(e){ 96 | ws.emit("clientError", e); 97 | }); 98 | }; 99 | 100 | sys.inherits(Server, events.EventEmitter); 101 | 102 | /*----------------------------------------------- 103 | Public API 104 | -----------------------------------------------*/ 105 | Server.prototype.setSecure = function (credentials) { 106 | this.server.setSecure.call(this.server, credentials); 107 | } 108 | 109 | Server.prototype.listen = function(){ 110 | this.server.listen.apply(this.server, arguments); 111 | }; 112 | 113 | Server.prototype.close = function(){ 114 | this.server.close(); 115 | }; 116 | 117 | Server.prototype.send = function(id, data){ 118 | this.manager.find(id, function(client){ 119 | if(client && client._state === 4){ 120 | client.write(data); 121 | } 122 | }); 123 | }; 124 | 125 | Server.prototype.broadcast = function(data){ 126 | this.manager.forEach(function(client){ 127 | if(client && client._state === 4){ 128 | client.write(data); 129 | } 130 | }); 131 | }; 132 | 133 | 134 | 135 | Server.prototype.use = function(module){ 136 | module.call(this, this.options); 137 | }; 138 | 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-websocket-server # 2 | 3 | This is a server for the WebSocket Protocol. It currently to works 4 | with both [draft75](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75) and [draft76](http://www.whatwg.org/specs/web-socket-protocol/) of the protocol specification. 5 | 6 | ## Compatibility ## 7 | 8 | This module is known to work with Node.js v0.1.98. May also work on Node.js greater than v0.1.94, dependent on protocol version being used. 9 | 10 | It has been reported that this module experiences some issues on solaris and ubuntu systems, so far these issues are unresolved, but seem to be related to the core of Node.js 11 | 12 | ## Synopsis ## 13 | 14 | An example of a simple server that will echo the messages received back out can be found in `examples/echo-server.js`, coupled with a websocket client like the `examples/client.html`, and you have a working websocket client/server. 15 | 16 | ## Server ## 17 | 18 | The server acts like a normal http server in many respects, and exposes much of node's http.Server events and 19 | methods. However, there are a few differences, and things that haven't yet been implemented. 20 | 21 | `ws.creareServer()` returns an instance of `ws.Server`, which acts like `http.Server`. However, not all methods 22 | and events that act on `http.Server` will act on `ws.Server`. Your application can handle normal http requests by listening for the "request" event. 23 | 24 | `ws.createServer()` and `ws.Server()` takes an options object as its only parameter. The options object has a these 25 | defaults: 26 | 27 | { debug: false, // Boolean: Show debug information. 28 | , version: "auto" // String: Value must be either: draft75, draft76, auto 29 | , origin: "*" // String, Array: A match for a valid connection origin 30 | , subprotocol: null // String, Array: A match for a valid connection subprotocol. 31 | } 32 | 33 | After a websocket client connects to the server, the server will emit the `'connection'` event, with the `ws/connection` 34 | instance for the connection. This means that the connection has undertaken the necessary websocket handshaking and 35 | is now ready to send and receive data. 36 | 37 | **NOTE:** Currently the origin and subprotocols are not strictly checked, this will be added in future versions. 38 | 39 | ### server.listen(port, host) ### 40 | 41 | The same as the `http.Server` listen method. 42 | 43 | ### server.send(client_id, message) ### 44 | 45 | Sends `message` to the client with `id` of `client_id`. 46 | 47 | ### server.broadcast(message) ### 48 | 49 | Sends `message` to all connected clients. 50 | 51 | ### server.close() ### 52 | 53 | The same as the `http.Server` close method. 54 | 55 | ### Event: listening ### 56 | 57 | `function () { }` 58 | 59 | Emits when the server is ready to start accepting clients, after `listen` has been called. 60 | 61 | ### Event: connection ### 62 | 63 | `function (connection) { }` 64 | 65 | Emits when a websocket client connects to the server. The `connection` is an instance of `ws/connection`. 66 | 67 | ### Event: request ### 68 | 69 | `function (request, response) { }` 70 | 71 | Emits when a client connects using standard HTTP to the server. 72 | This is the same as the `http.Server` `request` event. 73 | Use this to handle normal http connections that won't upgrade to WebSocket. 74 | 75 | ### Event: stream ### 76 | 77 | `function (stream) { }` 78 | 79 | The same as the `http.Server` `stream` event. 80 | 81 | ### Event: shutdown ### 82 | 83 | `function (errno) { }` 84 | 85 | Emits when the server is closed. Proxied from `http.Server` 86 | 87 | ### Event: close ### 88 | 89 | `function(connection) { }` 90 | 91 | Emits when a websocket client's connection closes. The `connection` is an instance of `ws/connection`. 92 | 93 | ## ws/connection ## 94 | 95 | This is an instance of a client connecting to the `ws.Server`, this is similar to the `req` on a `http.Server`. 96 | 97 | ### connecting.getVersion() ### 98 | 99 | Returns the websocket specification version that the connection is using. 100 | 101 | ### connection.write(data) ### 102 | 103 | Publishes a message to the client. 104 | 105 | ### connection.close() ### 106 | 107 | Closes the client's connection. 108 | 109 | ### connection.reject(reason) ### 110 | 111 | Rejects a client's connection. `reason` is only used when the server is in debug mode. 112 | 113 | ### connection.handshake() ### 114 | 115 | This carries out handshaking with a client, this method is semi-private. 116 | 117 | ### Event: stateChange ### 118 | 119 | `function (state, previous_state) { }` 120 | 121 | Each time the connection's status changes this is emitted, the state codes are: 122 | 123 | 0. unknown 124 | 1. opening 125 | 2. waiting 126 | 3. handshaking 127 | 4, connected 128 | 5. closed 129 | 130 | A state of `2` should never be reached, if it is, please do let me know. 131 | 132 | ### Event: message ### 133 | 134 | `function (message) { }` 135 | 136 | Emits when a client sends a message to the server. 137 | 138 | ### Event: close ### 139 | 140 | `function () { }` 141 | 142 | Emits when a connection is closes. 143 | 144 | ### Event: rejected ### 145 | 146 | `function () { }` 147 | 148 | Emits when a connection is rejected by the server, usually for a bad handshake or version mismatch. This event is immediately followed by the `close` event 149 | 150 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | body { 3 | // font-family: "Helvetica Neue", Helvetica, FreeSans, Arial, sans-serif; 4 | font-family: Georgia, FreeSerif, Times, serif; 5 | font-size: 0.9375em; 6 | line-height: 1.4667em; 7 | color: #222; 8 | margin: 0; padding: 0; 9 | } 10 | a { 11 | color: #0050c0; 12 | text-decoration: underline; 13 | } 14 | a:visited { 15 | color: #b950b7; 16 | text-decoration: underline; 17 | } 18 | a:hover, a:focus { 19 | text-decoration: none; 20 | } 21 | 22 | code a:hover { 23 | background: none; 24 | color: #b950b7; 25 | } 26 | 27 | .notice { 28 | display: block; 29 | padding: 1em; 30 | margin: 1.4667em 0 2.9334em; 31 | background:#FFF6BF; 32 | color:#514721; 33 | border:1px solid #FFD324; 34 | } 35 | .notice p { 36 | margin: 0; 37 | } 38 | 39 | ul.plain { 40 | list-style: none; 41 | } 42 | 43 | abbr { 44 | border-bottom: 1px dotted #454545; 45 | } 46 | 47 | p { 48 | margin: 0 0 1.4667em 0; 49 | position: relative; 50 | text-rendering: optimizeLegibility; 51 | } 52 | 53 | ol, ul, dl { 54 | margin: 0 0 1em 0; 55 | padding: 0; 56 | } 57 | 58 | ul, ol { 59 | margin-left: 2em; 60 | } 61 | 62 | 63 | dl dt { 64 | position: relative; 65 | margin: 1.5em 0 0; 66 | } 67 | 68 | dl dd { 69 | position: relative; 70 | margin: 0 1em 0; 71 | } 72 | 73 | dd + dt.pre { 74 | margin-top: 1.6em; 75 | } 76 | 77 | h1, h2, h3, h4, h5, h6 { 78 | font-family: Georgia, FreeSerif, Times, serif; 79 | color: #000; 80 | text-rendering: optimizeLegibility; 81 | position: relative; 82 | } 83 | 84 | h1 { 85 | font-size: 2.55em; 86 | line-height: 1.375em; 87 | margin: 1.25em -0.5em 1.3em; 88 | padding: 0 0.5em 0.225em; 89 | border-bottom: 1px solid #ccc; 90 | } 91 | 92 | h2 { 93 | font-size: 1.8em; 94 | line-height: 1.227em; 95 | margin: 2.125em 0 0.75em; 96 | font-style: italic; 97 | } 98 | 99 | h3 { 100 | font-size: 1.6em; 101 | line-height: 1.0909em; 102 | margin: 1.6em 0 1em; 103 | } 104 | 105 | h4 { 106 | font-size: 1.3em; 107 | line-height: 1.1282em; 108 | margin: 2.2em 0 1.25em; 109 | } 110 | 111 | h5 { 112 | font-size: 1.125em; 113 | line-height: 1.4em; 114 | } 115 | 116 | h6 { 117 | font-size: 1em; 118 | line-height: 1.4667em; 119 | } 120 | 121 | pre, tt, code { 122 | font-size: 0.95em; 123 | line-height: 1.5438em; 124 | font-family: Monaco, Consolas, "Lucida Console", monospace; 125 | margin: 0; padding: 0; 126 | } 127 | 128 | .pre { 129 | font-family: Monaco, Consolas, "Lucida Console", monospace; 130 | line-height: 1.5438em; 131 | font-size: 0.95em; 132 | } 133 | 134 | pre { 135 | padding: 2em 1.6em 2em 1.2em; 136 | vertical-align: top; 137 | background: #f8f8f8; 138 | border: 1px solid #e8e8e8; 139 | border-width: 1px 1px 1px 6px; 140 | margin: -0.5em 0 1.1em; 141 | } 142 | 143 | pre + h3 { 144 | margin-top: 2.225em; 145 | } 146 | 147 | code.pre { 148 | white-space: pre; 149 | } 150 | 151 | #container { 152 | position: relative; 153 | padding: 6em 6em 1200px; 154 | width: 50em; 155 | text-align: justify; 156 | } 157 | 158 | #jump_to, #jump_page { 159 | background: white; 160 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 161 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 162 | font: 10px Arial; 163 | text-transform: uppercase; 164 | cursor: pointer; 165 | text-align: right; 166 | } 167 | #jump_to, #jump_wrapper { 168 | position: fixed; 169 | right: 0; top: 0; 170 | padding: 5px 10px; 171 | } 172 | #jump_wrapper { 173 | padding: 0; 174 | display: none; 175 | } 176 | #jump_to:hover #jump_wrapper { 177 | display: block; 178 | } 179 | #jump_page { 180 | padding: 5px 0 3px; 181 | margin: 0 0 25px 25px; 182 | } 183 | #jump_page .source { 184 | display: block; 185 | padding: 5px 10px; 186 | text-decoration: none; 187 | border-top: 1px solid #eee; 188 | } 189 | #jump_page .source:hover { 190 | background: #f5f5ff; 191 | } 192 | #jump_page .source:first-child { 193 | } 194 | 195 | p tt, p code { 196 | background: #f8f8ff; 197 | border: 1px solid #dedede; 198 | padding: 0 0.2em; 199 | } 200 | 201 | a.octothorpe { 202 | text-decoration: none; 203 | color: #777; 204 | position: absolute; 205 | top: 0; left: -1.4em; 206 | padding: 1px 2px; 207 | opacity: 0; 208 | -webkit-transition: opacity 0.2s linear; 209 | } 210 | p:hover > a.octothorpe, 211 | dt:hover > a.octothorpe, 212 | dd:hover > a.octothorpe, 213 | h1:hover > a.octothorpe, 214 | h2:hover > a.octothorpe, 215 | h3:hover > a.octothorpe, 216 | h4:hover > a.octothorpe, 217 | h5:hover > a.octothorpe, 218 | h6:hover > a.octothorpe { 219 | opacity: 1; 220 | } 221 | 222 | 223 | 224 | /*---------------------- Syntax Highlighting -----------------------------*/ 225 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 226 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 227 | body .hll { background-color: #ffffcc } 228 | body .c { color: #408080; font-style: italic } /* Comment */ 229 | body .err, body .e { color: #FF0000 } /* Error */ 230 | body .k { color: #ff6922 } /* Keyword */ 231 | body .o { color: #666666 } /* Operator */ 232 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 233 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 234 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 235 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 236 | body .gd { color: #A00000 } /* Generic.Deleted */ 237 | body .ge { font-style: italic } /* Generic.Emph */ 238 | body .gr { color: #FF0000 } /* Generic.Error */ 239 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 240 | body .gi { color: #00A000 } /* Generic.Inserted */ 241 | body .go { color: #808080 } /* Generic.Output */ 242 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 243 | body .gs { font-weight: bold } /* Generic.Strong */ 244 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 245 | body .gt { color: #0040D0 } /* Generic.Traceback */ 246 | body .kc { color: #954121 } /* Keyword.Constant */ 247 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 248 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 249 | body .kp { color: #954121 } /* Keyword.Pseudo */ 250 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 251 | body .kt { color: #B00040 } /* Keyword.Type */ 252 | body .m { color: #009999 } /* Literal.Number */ 253 | body .s { color: #00af5c } /* Literal.String */ 254 | body .na { color: #7D9029 } /* Name.Attribute */ 255 | body .nb { color: #954121 } /* Name.Builtin */ 256 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 257 | body .no { color: #880000 } /* Name.Constant */ 258 | body .nd { color: #AA22FF } /* Name.Decorator */ 259 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 260 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 261 | body .nf { color: #b950b7 } /* Name.Function */ 262 | body .nl { color: #A0A000 } /* Name.Label */ 263 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 264 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 265 | body .nv { color: #19469D } /* Name.Variable */ 266 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 267 | body .w { color: #bbbbbb } /* Text.Whitespace */ 268 | body .mf { color: #666666 } /* Literal.Number.Float */ 269 | body .mh { color: #666666 } /* Literal.Number.Hex */ 270 | body .mi { color: #666666 } /* Literal.Number.Integer */ 271 | body .mo { color: #666666 } /* Literal.Number.Oct */ 272 | body .sb { color: #219161 } /* Literal.String.Backtick */ 273 | body .sc { color: #219161 } /* Literal.String.Char */ 274 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 275 | body .s2 { color: #219161 } /* Literal.String.Double */ 276 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 277 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 278 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 279 | body .sx { color: #954121 } /* Literal.String.Other */ 280 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 281 | body .s1 { color: #219161 } /* Literal.String.Single */ 282 | body .ss { color: #19469D } /* Literal.String.Symbol */ 283 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 284 | body .vc { color: #19469D } /* Name.Variable.Class */ 285 | body .vg { color: #19469D } /* Name.Variable.Global */ 286 | body .vi { color: #19469D } /* Name.Variable.Instance */ 287 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /lib/ws/connection.js: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------- 2 | Requirements: 3 | -----------------------------------------------*/ 4 | var sys = require("sys") 5 | , Url = require("url") 6 | , Events = require("events") 7 | , Buffer = require("buffer").Buffer 8 | , Crypto = require("crypto"); 9 | 10 | /*----------------------------------------------- 11 | The Connection: 12 | -----------------------------------------------*/ 13 | module.exports = Connection; 14 | 15 | // Our connection instance: 16 | function Connection(server, req, socket, data){ 17 | this.debug = server.debug; 18 | 19 | if (this.debug) { 20 | debug = function (id, data) { sys.error('\033[90mWS: ' + Array.prototype.join.call(arguments, ", ") + "\033[39m"); }; 21 | } else { 22 | debug = function (id, data) { }; 23 | } 24 | 25 | Events.EventEmitter.call(this); 26 | 27 | this._req = req; 28 | this._server = server; 29 | this.headers = this._req.headers; 30 | 31 | var _firstFrame = false; 32 | 33 | Object.defineProperties(this, { 34 | id: { 35 | get: function(){ return this._req.socket.remotePort } 36 | }, 37 | 38 | version: { 39 | get: function(){ 40 | if(this._req.headers["sec-websocket-key1"] && this._req.headers["sec-websocket-key2"]){ 41 | return "draft76"; 42 | } else { 43 | return "draft75"; 44 | } 45 | } 46 | } 47 | }); 48 | 49 | 50 | if(server.options.datastore){ 51 | var storage; 52 | 53 | if(typeof server.options.datastore === "object" || typeof server.options.datastore === "function"){ 54 | storage = server.options.datastore; 55 | } else if(server.options.datastore === true){ 56 | storage = require("./mem-store"); 57 | } else { 58 | storage = false; 59 | } 60 | } 61 | 62 | // Start to process the connection 63 | if( !checkVersion(this)) { 64 | this.reject("Invalid version."); 65 | } else { 66 | var connection = this 67 | , parser; 68 | 69 | // Let the debug mode know that we have a connection: 70 | this.debug && debug(this.id, this.version+" connection"); 71 | 72 | // Set the initial connecting state. 73 | this.state(1); 74 | 75 | // Handle incoming data: 76 | parser = new Parser(this); 77 | 78 | socket.addListener("data", function(data){ 79 | if(data.length == 2 && data[0] == 0xFF && data[1] == 0x00){ 80 | connection.state(5); 81 | } else { 82 | parser.write(data); 83 | } 84 | }); 85 | 86 | // Handle the end of the stream, and set the state 87 | // appropriately to notify the correct events. 88 | socket.addListener("end", function(){ 89 | connection.state(5); 90 | }); 91 | 92 | socket.addListener('timeout', function () { 93 | debug(connection.id, "timed out"); 94 | server.emit("timeout", connection); 95 | connection.emit("timeout"); 96 | }); 97 | 98 | socket.addListener("error", function(e){ 99 | server.emit("error", connection, e); 100 | connection.emit("error", e); 101 | connection.state(5); 102 | }); 103 | 104 | // Setup the connection manager's state change listeners: 105 | this.addListener("stateChange", function(state, laststate){ 106 | //debug(connection.id, "Change state: "+laststate+" => "+state); 107 | if(state === 4){ 108 | if(storage && storage.create){ 109 | connection.storage = storage.create(); 110 | } else if(storage){ 111 | connection.storage = storage; 112 | } 113 | 114 | server.manager.attach(connection.id, connection); 115 | server.emit("connection", connection); 116 | 117 | if(_firstFrame){ 118 | parser.write(_firstFrame); 119 | delete _firstFrame; 120 | } 121 | 122 | } else if(state === 5){ 123 | connection.close(); 124 | } else if(state === 6 && laststate === 5){ 125 | 126 | if(connection.storage && connection.storage.disconnect){ 127 | connection.storage.disconnect(connection.id); 128 | } 129 | 130 | server.manager.detach(connection.id, function(){ 131 | server.emit("close", connection); 132 | connection.emit("close"); 133 | }); 134 | } 135 | }); 136 | 137 | // Let us see the messages when in debug mode. 138 | if(this.debug){ 139 | this.addListener("message", function(msg){ 140 | debug(connection.id, "recv: " + msg); 141 | }); 142 | } 143 | 144 | // Carry out the handshaking. 145 | // - Draft75: There's no upgradeHead, goto Then. 146 | // Draft76: If there's an upgradeHead of the right length, goto Then. 147 | // Then: carry out the handshake. 148 | // 149 | // - Currently no browsers to my knowledge split the upgradeHead off the request, 150 | // but in the case it does happen, then the state is set to waiting for 151 | // the upgradeHead. 152 | // 153 | if(this.version == "draft75"){ 154 | this.handshake(); 155 | } 156 | 157 | if(this.version == "draft76"){ 158 | if(data.length >= 8){ 159 | this._upgradeHead = data.slice(0, 8); 160 | 161 | _firstFrame = data.slice(8, data.length); 162 | 163 | this.handshake(); 164 | } else { 165 | this.reject("Missing key3"); 166 | } 167 | } 168 | } 169 | }; 170 | 171 | sys.inherits(Connection, Events.EventEmitter); 172 | 173 | /*----------------------------------------------- 174 | Various utility style functions: 175 | -----------------------------------------------*/ 176 | var writeSocket = function(socket, data, encoding) { 177 | if(socket.writable){ 178 | try { 179 | socket.write(data, encoding); 180 | return true; 181 | } catch(e){ 182 | debug(null, "Error on write: "+e.toString()); 183 | return false; 184 | } 185 | } 186 | return false; 187 | }; 188 | 189 | var closeClient = function(client){ 190 | client._req.socket.flush(); 191 | client._req.socket.end(); 192 | client._req.socket.destroy(); 193 | debug(client.id, "socket closed"); 194 | client.state(6); 195 | }; 196 | 197 | function checkVersion(client){ 198 | var server_version = client._server.options.version.toLowerCase(); 199 | 200 | return (server_version == "auto" || server_version == client.version); 201 | }; 202 | 203 | 204 | function pack(num) { 205 | var result = ''; 206 | result += String.fromCharCode(num >> 24 & 0xFF); 207 | result += String.fromCharCode(num >> 16 & 0xFF); 208 | result += String.fromCharCode(num >> 8 & 0xFF); 209 | result += String.fromCharCode(num & 0xFF); 210 | return result; 211 | }; 212 | 213 | 214 | /*----------------------------------------------- 215 | Formatters for the urls 216 | -----------------------------------------------*/ 217 | function websocket_origin(){ 218 | var origin = this._server.options.origin || "*"; 219 | if(origin == "*" || Array.isArray(origin)){ 220 | origin = this._req.headers.origin; 221 | } 222 | return origin; 223 | }; 224 | 225 | function websocket_location(){ 226 | if(this._req.headers["host"] === undefined){ 227 | this.reject("Missing host header"); 228 | return; 229 | } 230 | 231 | var location = "" 232 | , secure = this._req.socket.secure 233 | , host = this._req.headers.host.split(":") 234 | , port = host[1] !== undefined ? host[1] : (secure ? 443 : 80); 235 | 236 | location += secure ? "wss://" : "ws://"; 237 | location += host[0]; 238 | 239 | if(!secure && port != 80 || secure && port != 443){ 240 | location += ":"+port; 241 | } 242 | 243 | location += this._req.url; 244 | 245 | return location; 246 | }; 247 | 248 | 249 | /*----------------------------------------------- 250 | 0. unknown 251 | 1. opening 252 | 2. waiting 253 | 3. handshaking 254 | 4, connected 255 | 5. closing 256 | 6. closed 257 | -----------------------------------------------*/ 258 | Connection.prototype._state = 0; 259 | 260 | 261 | /*----------------------------------------------- 262 | Connection Public API 263 | -----------------------------------------------*/ 264 | Connection.prototype.state = function(state){ 265 | if(state !== undefined && typeof state === "number"){ 266 | var oldstate = this._state; 267 | this._state = state; 268 | this.emit("stateChange", this._state, oldstate); 269 | } 270 | }; 271 | 272 | Connection.prototype.write = function(data){ 273 | var socket = this._req.socket; 274 | 275 | if(this._state == 4){ 276 | debug(this.id, "write: "+data); 277 | 278 | if( 279 | writeSocket(socket, "\x00", "binary") && 280 | writeSocket(socket, data, "utf8") && 281 | writeSocket(socket, "\xff", "binary") 282 | ){ 283 | return true; 284 | } else { 285 | debug(this.id, "\033[31mERROR: write: "+data); 286 | } 287 | } else { 288 | debug(this.id, "\033[31mCouldn't send."); 289 | } 290 | return false; 291 | }; 292 | 293 | Connection.prototype.send = Connection.prototype.write; 294 | 295 | Connection.prototype.broadcast = function(data){ 296 | var conn = this; 297 | 298 | this._server.manager.forEach(function(client){ 299 | if(client && client._state === 4 && client.id != conn.id){ 300 | client.write(data); 301 | } 302 | }); 303 | }; 304 | 305 | Connection.prototype.close = function(){ 306 | var socket = this._req.socket; 307 | 308 | if(this._state == 4 && socket.writable){ 309 | writeSocket(socket, "\xff", "binary"); 310 | writeSocket(socket, "\x00", "binary"); 311 | } 312 | closeClient(this); 313 | }; 314 | 315 | 316 | Connection.prototype.reject = function(reason){ 317 | this.debug && debug(this.id, "rejected. Reason: "+reason); 318 | 319 | this.emit("rejected"); 320 | closeClient(this); 321 | }; 322 | 323 | Connection.prototype.handshake = function(){ 324 | if(this._state < 3){ 325 | this.debug && debug(this.id, this.version+" handshake"); 326 | 327 | this.state(3); 328 | 329 | doHandshake[this.version].call(this); 330 | } else { 331 | this.debug && debug(this.id, "Already handshaked."); 332 | } 333 | }; 334 | 335 | /*----------------------------------------------- 336 | Do the handshake. 337 | -----------------------------------------------*/ 338 | var doHandshake = { 339 | // Using draft75, work out and send the handshake. 340 | draft75: function(){ 341 | var location = websocket_location.call(this), res; 342 | 343 | if(location){ 344 | res = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" 345 | + "Upgrade: WebSocket\r\n" 346 | + "Connection: Upgrade\r\n" 347 | + "WebSocket-Origin: "+websocket_origin.call(this)+"\r\n" 348 | + "WebSocket-Location: "+location; 349 | 350 | if(this._server.options.subprotocol && typeof this._server.options.subprotocol == "string") { 351 | res += "\r\nWebSocket-Protocol: "+this._server.options.subprotocol; 352 | } 353 | 354 | writeSocket(this._req.socket, res+"\r\n\r\n", "ascii"); 355 | this.state(4); 356 | } 357 | }, 358 | 359 | // Using draft76 (security model), work out and send the handshake. 360 | draft76: function(){ 361 | var location = websocket_location.call(this), res; 362 | 363 | if(location){ 364 | res = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" 365 | + "Upgrade: WebSocket\r\n" 366 | + "Connection: Upgrade\r\n" 367 | + "Sec-WebSocket-Origin: "+websocket_origin.call(this)+"\r\n" 368 | + "Sec-WebSocket-Location: "+location; 369 | 370 | if(this._server.options.subprotocol && typeof this._server.options.subprotocol == "string") { 371 | res += "\r\nSec-WebSocket-Protocol: "+this._server.options.subprotocol; 372 | } 373 | 374 | var strkey1 = this._req.headers['sec-websocket-key1'] 375 | , strkey2 = this._req.headers['sec-websocket-key2'] 376 | 377 | , numkey1 = parseInt(strkey1.replace(/[^\d]/g, ""), 10) 378 | , numkey2 = parseInt(strkey2.replace(/[^\d]/g, ""), 10) 379 | 380 | , spaces1 = strkey1.replace(/[^\ ]/g, "").length 381 | , spaces2 = strkey2.replace(/[^\ ]/g, "").length; 382 | 383 | 384 | if (spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0) { 385 | this.reject("WebSocket contained an invalid key -- closing connection."); 386 | } else { 387 | var hash = Crypto.createHash("md5") 388 | , key1 = pack(parseInt(numkey1/spaces1)) 389 | , key2 = pack(parseInt(numkey2/spaces2)); 390 | 391 | hash.update(key1); 392 | hash.update(key2); 393 | hash.update(this._upgradeHead.toString("binary")); 394 | 395 | res += "\r\n\r\n"; 396 | res += hash.digest("binary"); 397 | 398 | writeSocket(this._req.socket, res, "binary"); 399 | this.state(4); 400 | } 401 | } 402 | } 403 | }; 404 | 405 | /*----------------------------------------------- 406 | The new onData callback for 407 | http.Server IncomingMessage 408 | -----------------------------------------------*/ 409 | var Parser = function(client){ 410 | this.frameData = []; 411 | this.order = 0; 412 | this.client = client; 413 | }; 414 | 415 | Parser.prototype.write = function(data){ 416 | var pkt, msg; 417 | for(var i = 0, len = data.length; i 2 | 3 | 4 | Node WebSocket Server 5 | 6 | 7 | 8 | 41 | 42 | 43 |
44 |

Node WebSocket Server

45 | 46 |

Hey there, this is an early release for Node Knockout, there'll be full documentation coming really soon, if anything's missing, try the source code.

47 | 48 |

49 | # 50 | Node WebSocket Server is a near specification compliant implementation of the server-side WebSocket Protocol. It is built on top of Node.js as a third-party module, and is designed to support various versions of the WebSocket protocol – which is currently still an IETF Draft. 51 |

52 |

53 | # 54 | The WebSockets protocol is a way of the browser providing a bi-directional, full-duplex communication channel which is accessible through the DOM. 55 |

56 | 57 |
58 | 59 |

Installation

60 |

61 | # 62 | Node WebSocket Server can be easily installed via NPM or cloned from github. This module requires Node to be compiled with SSL support (which is by default enabled, if it's available), the reason for the dependency on SSL being available in Node.js is to enable Draft 76 handshaking and Secure WebSocket connections. 63 |

64 |

With Node Package Manager

65 |

66 | # 67 | For the latest stable build: 68 |

69 |
npm install websocket-server
70 |

71 | # 72 | Or, alternatively, you can run with the latest bleeding edge build with: 73 |

74 |
npm install websocket-server@latest
75 | 76 |

With git submodules

77 |

78 | # 79 | Installing Node WebSocket Server as a git submodule is a recommended route for installing if you plan on deploying your application and don't want the hassle of having to check that both production and development environments are running the same module versions. Another benefit of installing as a submodule, is that the websocket server is then packaged with your code. 80 |

81 |

82 | # 83 | The installation is as follows: 84 |

85 |
 86 | // Assumption that you are in a working git repository:
 87 | $ mkdir vendor
 88 | $ cd vendor
 89 | $ git submodule add git://github.com/miksago/node-websocket-server.git websocket-server
 90 | Initialized empty Git repository in ./websocket-server/.git/
 91 | // ...repository gets cloned...
 92 | $ git commit -m "Adding websocket-server submodule"
 93 | 
94 |

95 | # 96 | Later on, in order to update your projects copy of Node WebSocket Server, simply do: 97 |

98 |
$ git submodule update
99 |

100 | # 101 | For your collaborators, they can simply do a git pull then a git submodule init then a git submodule update. You can learn more about Git Submodules from Chapter 6.6 of the Pro Git book, which you can read online. 102 |

103 | 104 |

Contributing

105 |

106 | # 107 | In order to contribute to this project, I ask that you follow these steps: 108 |

109 |
    110 |
  1. Fork the project.
  2. 111 |
  3. Make your feature addition or bug fix.
  4. 112 | 113 |
  5. Commit, do not mess with rakefile, version, or history.
  6. 114 |
  7. Send me a pull request. Bonus points for topic branches.
  8. 115 |
116 | 117 |

Basic Usage

118 | 119 |

120 | # 121 | The API to Node WebSocket Server is fairly simple, and stays consistent with the Node.js API's you may already be familiar with. Firstly, there are three main parts to the server, these are: The constructor, the connection instance, and the connection manager. Each of these parts interact with each other, and end up providing a clean API with which you write your server-side logic. 122 |

123 |

124 | # 125 | An example server, that simply echoes anything that a client sends to all other connections, looks like the following: 126 |

127 |
128 | var ws = require("websocket-server");
129 | 
130 | var server = ws.createServer();
131 | 
132 | server.addListener("connection", function(connection){
133 |   connection.addListener("message", function(msg){
134 |     server.send(msg);
135 |   });
136 | });
137 | 
138 | server.listen(8080);
139 | 
140 | 141 |

142 | # 143 | So what does the above code actually do? First, we need to require in the websocket-server library — If you have installed this module via NPM then you can require it exactly like the above does. Next, we create a server instance, by default, this server will automatically choose which version of the protocol to use when communicating with the client and it will also act as a standard http.Server instance. 144 |

145 | 146 |

147 | # 148 | After we have a instance of ws.Server, which ws.createServer returns, we can proceed to add event listeners to the various events it emits. We use the same API as that which node's Event.EventEmitter provides to do so. In this case, we only bind to one event on the server, the Connection event. Each time a user successfully connects to the server the connection is emitted. We then watch for the message event on the emitted connection, this event occurs every time a user sends a message to the server. 149 |

150 | 151 |

API Documentation

152 |

153 | # 154 | The API to Node WebSocket Server is split into three main parts:

155 |
    156 |
  1. The require-time module, that lets you create a new websocket server
  2. 157 |
  3. The connection instance
  4. 158 |
  5. The connection manager
  6. 159 |
160 | 161 |

162 | Module Methods 163 |

164 |
165 |
166 | # 167 | ws.createServer( [options] ); 168 |
169 |
Returns a new ws.Server instance.
170 |
This method passes it's arguments through to the ws.Server constructor, see constructor documentation for description of options.
171 |
172 | # 173 | ws.Server( [options] ); 174 |
175 |
The constructor for a new WebSocket Server.
176 |
options is an object of options to pass to the server, these are as follows:
177 |
178 |
179 |
debug:
180 |
Toggles the printing of debug messages from each component to stdout.
181 |
Type: Boolean
182 | 183 |
version:
184 |
The WebSocket protocol version the server will accept connections from, connections not matching this version will be rejected.
185 |
Type: String
186 |
Values: "auto", "draft76", "draft75"
187 |
Default: "auto"
188 | 189 |
storage:
190 |
Defines a storage model to be implemented as a member of each connection. If the value is false, no storage will be created. If the value is true, then an in memory store will be used. The value may also be an Object or Constructor, see the section on Connection.storage for more information.
191 |
Type: Boolean, Object, Constructor
192 | 193 |
server:
194 |
Defines an existing http.Server instance for the WebSocket Server to bind to.
195 |
Type: http.Server
196 | 197 | 203 | 204 |
origin:
205 |
A string or array of domains from which the server should accept connections.
206 |
Type: String, String[]
207 |
Note: This is not fully implemented
208 | 209 |
subprotocol:
210 |
A string or array of subprotocols which the server will use when communicating with clients. The server and client should negotiate which subprotocol to use if an Array of strings is passed.
211 |
Type: String, String[]
212 |
Note: This is not fully implemented
213 |
214 |
215 |
216 | 217 |

Server Instances

218 |

Throughout this section, the variable server is an instant of a ws.Server.

219 |
220 |
server.send( client_id, message )
221 |
Sends message to the client with id of client_id.
222 |
client_id: Integer
223 |
message: String
224 | 225 |
server.broadcast( message )
226 |
Sends message to all connected clients.
227 |
message: String
228 | 229 |
server.listen( port, [host] )
230 |
Same as the http.Server listen method.
231 | 232 |
server.setSecure( credentials )
233 |
Switches to using SSL to encrypt connections with the supplied credentials. Requires the client to connect using wss:// protocol, instead of the standard ws://.
234 |
credentials: crypto.Credentials
235 | 236 | 237 |
server.close()
238 |
Same as the http.Server close method.
239 | 240 |

Inherited Methods

241 |

The following methods are inherited from Events.eventEmitter.

242 |
243 |
server.addListener( event, callback )
244 |
Adds a listener for the specified event that is emitted from the server.
245 |
event: String
246 |
callback: Function
247 | 248 |
server.removeListener( event, callback )
249 |
Remove a listener from the specified event that is emitted from the server.
250 |
event: String
251 |
callback: Function
252 | 253 |
server.removeAllListeners( event )
254 |
Removes all listeners from the specified event that is emitted from the server.
255 |
event: String
256 |
257 | 258 |
259 |

Events

260 |

A Server instance can emit various events during it's life cycle. The WebSocket Server will also emit each of the http.Server events, except "connection" and "close" which are used by the WebSocket Server. These events are described as follows:

261 |
262 |
connection
263 |
Emitted whenever a client connects to the server successfully.
264 |
client: Connection
265 | 266 |
close
267 |
Emitted whenever a client disconnects from the server.
268 |
client: Connection
269 | 270 |
shutdown
271 |
Emitted when the server is closed, this event is echoed from http.Server
272 |
273 | 274 |

Connection Instances

275 |

276 | # 277 | Each time a client connects to the server, a new connection instance is created and registered with the connection manager.

278 |

279 |
280 |
connection.id
281 |
A String, which is a unique identifier for the connection, this is based on the connection's remotePort.
282 | 283 |
connection.state
284 |
An Integer representing the current state of the connection, states are as follows:
285 |
    286 |
  • 0. unknown
  • 287 |
  • 1. opening
  • 288 |
  • 2. waiting
  • 289 |
  • 3. handshaking
  • 290 |
  • 4, connected
  • 291 |
  • 5. closing
  • 292 |
  • 6. closed
  • 293 |
294 |
State changes can be listened for using the stateChange event.
295 | 296 |
connection.version
297 |
A String, representing the protocol version the client is connecting from. Either draft75 or draft76.
298 | 299 |
connection.headers
300 |
An Object, these are the HTTP headers that were sent with the initial WebSocket Handshake. See http.ServerRequest for details.
301 | 302 |
connection.storage
303 |
This is an instance of your chosen Storage provider. By default, it is a in memory store with an API similar to redis. This can be disabled, in which case it will be undefined.
304 |
305 | 306 |

Methods

307 | 308 |
309 |
connection.send( data )
310 |
Sends data from the current connection to all other connections on the server, including the sending connection. This method is aliased as connection.write
311 |
data: String
312 | 313 |
connection.broadcast( data )
314 |
Sends data from the current connection to all other connections on the server, excluding the sending connection.
315 |
data: String
316 | 317 |
connection.close()
318 |
Closes the current connection and emits the "close".
319 | 320 |
connection.reject( [reason] )
321 |
Emits the "rejected" event, then proceeds to close the connection using connection.close(). The reason is used only for logging purposes.
322 |
reason: String
323 |
324 | 325 |

Inherited Methods

326 |

The following methods are inherited from Events.eventEmitter.

327 | 328 |
329 |
connection.addListener( event, callback )
330 |
Adds a listener for the specified event that is emitted from the connection.
331 |
event: String
332 |
callback: Function
333 | 334 |
connection.removeListener( event, callback )
335 |
Remove a listener from the specified event that is emitted from the connection.
336 |
event: String
337 |
callback: Function
338 | 339 |
connection.removeAllListeners( event )
340 |
Removes all listeners from the specified event that is emitted from the connection.
341 |
event: String
342 |
343 | 344 |

Events

345 |

Each connection comes with the following events that may be emitted through out it's life-cycle.

346 |
347 |
close
348 |
Emitted whenever a client or server closes the connection.
349 | 350 |
rejected
351 |
Emitted whenever the server rejects the connection.
352 | 353 |
message: function( data )
354 |
Emitted whenever a client sends a message to the server successfully.
355 | 356 |
stateChange: function( new_state, old_state )
357 |
Emitted whenever the internal connection status changes, this equates to the various of the Connection life-cycle.
358 | 359 |
360 | 361 |

Manager Instances

362 |

363 | # 364 | ... 365 |

366 |
367 |
server.manager.length
368 |
Number of currently attached connections. This property is not writable, only readable.
369 | 370 |
server.manager.find( id, callback, [thisArg] )
371 |
Finds the Connection Instance with the given id. If a match is found, the call property of callback is called with thisArg as the value of this, with the only argument being the pointer to the Connection Instance that was matched.
372 | 373 |
server.manager.forEach( callback, [thisArg] )
374 |
... description ...
375 | 376 |
server.manager.map( callback, [thisArg] )
377 |
... description ...
378 | 379 |
server.manager.filter( callback, [thisArg] )
380 |
... description ...
381 |
382 | 383 |

Further Reading

384 |
385 | 386 | 387 | --------------------------------------------------------------------------------