1 var events = require("events");
2 var http = require("http");
3 var crypto = require("crypto");
4 var util = require("util");
5 //opcodes for WebSocket frames
6 //http://tools.ietf.org/html/rfc6455#section-5.2
7 var opcodes =
8 {
9 TEXT:1,
10 BINARY:2,
11 CLOSE:8,
12 PING:9,
13 PONG:10
14 };
15 var WebSocketConnection = function(req,socket,upgradeHead)
16 {
17 var self = this;
18 var key = hashWebSocketKey(req.headers["sec-websocket-key"]);
19 //handshake response
20 //http://tools.ietf.org/html/rfc6455#section-4.2.2
21 socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n'+
22 'Upgrade:WebSocket\r\n'+
23 'Connection:Upgrade\r\n'+
24 'sec-websocket-accept: '+key+'\r\n\r\n');
25
26 socket.on("data",function(buf)
27 {
28 self.buffer = Buffer.concat([self.buffer,buff]);
29 while(self._processBuffer())
30 {
31 //process buffer while it contains complete frames
32 }
33 });
34
35 socket.on("close",function(hac_error)
36 {
37 if(!self.closed)
38 {
39 self.emit("close",1006);
40 self.closed = true;
41 }
42 });
43 // initialize connection state
44 this.socket = socket;
45 this.buffer = new Buffer(0);
46 this.colsed = false;
47
48 }
49
50 util.inherits(WebSocketConnection,events.EventEmitter);
51 // Send a text or binary message on the WebSocket connection
52
53 WebSocketConnection.prototype.send = function(obj)
54 {
55 var opcode;
56 var payload;
57 if(Buffer.isBuffer(obj))
58 {
59 opcode = opcodes.BINARY;
60 payload = obj;
61 }
62 else
63 {
64 throw new Error("Cannot send object. Must be string or Buffer");
65 }
66 this._doSend(opcode,payload);
67 }
68 //Close the WebSocket connection
69 WebSocketConnection.prototype.close = function(code,reason)
70 {
71 var opcode = opcodes.CLOSE;
72 var buffer;
73 //Encode close and reason
74 if(code)
75 {
76 buffer = new Buffer(Buffer.byteLength(reason)+2);
77 buffer.writeUInt16BE(code,0);
78 buffer.write(reason,2);
79 }
80 else
81 {
82 buffer = new Buffer(0);
83 }
84 this._doSend(opcode,buffer);
85 this.close = true;
86
87 }
88 //process incoming bytes
89 WebSocketConnection.prototype._processBuffer = function()
90 {
91 var buf = this.buffer;
92 if(buf.length < 2)
93 {
94 //insufficient data read
95 return;
96 }
97
98 var idx = 2;
99 var b1 = buf.readUInt8(0);
100 var fin = b1 & 0x80;
101 var opcode = b1 & 0x0f;
102 var b2 = buf.readUInt8(1);
103 var mask = b2 & 0x80;
104 var length = b2 & 0x7f;
105
106 if(length > 125)
107 {
108 if(buf.length < 8)
109 {
110 //insufficient data read
111 return;
112 }
113
114 if(length == 126)
115 {
116 length = buf.readUInt16BE(2);
117 idx += 2;
118 }
119 else if(length == 127)
120 {
121 //discard high 4 bits because this server cannot handle huge lengths
122 var highBits = buf.readUInt32BE(2);
123 if(highBits != 0)
124 {
125 this.close(1009,"");
126 }
127 length = buf.readUInt32BE(6);
128 idx += 8;
129 }
130 }
131
132 if(buf.length < idx + 4 + length)
133 {
134 //insufficient data read
135 return;
136 }
137
138 maskBytes = buf.slice(idx,idx+4);
139 idx += 4;
140 var payload = buf.slice(idx,idx+length);
141 payload = unmask(maskBytes,payload);
142 this._handleFreme(opcode,payload);
143
144 this.buffer = buf.slice(idx+length);
145 return true;
146 }
147
148 WebSocketConnection.prototype._handleFrame = function(opcode,buffer)
149 {
150 var payload;
151 switch(opcode)
152 {
153 case opcodes.TEXT:
154 payload = buffer.toString("utf8");
155 this.emit("data",opcode,payload);
156 break;
157 case opcode.BINARY:
158 payload = buffer;
159 this.emit("data",opcode,payload);
160 break;
161 case opcode.PING:
162 //Respond to pings with pongs
163 this._doSend(opcode.PONG,buffer);
164 break;
165 case opcode.PONG:
166 //Ignore pongs;
167 break;
168 case opcode.CLOSE:
169 //Parse close and reason
170 var code,reason;
171 if(buffer.length >= 2)
172 {
173 code = buffer.readUInt16BE(0);
174 reason = buffer.toString("utf8",2);
175 }
176 this.close(code,reason);
177 this.emit("close",code,reason);
178 break;
179 default:
180 this.close(1002,"unknown opcode");
181
182 }
183 }
184 //Format and send a WebSocket message
185 WebSocketConnection.prototype._doSend = function(opcode,payload)
186 {
187 this.socket.write(encodeMessage(opcode,payload));
188 }
189
190 var KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
191 var hashWebSocketKey = function(key)
192 {
193 var sha1 = crypto.createHash("sha1");
194 sha1.update(key+KEY_SUFFIX,"ascii");
195 return sha1.digest("base64");
196 }
197
198 var unmask = function(maskBytes,data)
199 {
200 var payload = new Buffer(data.length);
201 for(var i=0;i<data.length;i++)
202 {
203 payload[] = maskBytes[i%4] ^ data[i];
204 }
205
206 return payload;
207
208 }
209
210 var encodeMessage = function(opcode,payload)
211 {
212 var buf;
213 //first byte:fin and opcode
214 var b1 = 0x80 | opcode;
215 //always send message as one frame(fin)
216 //Second byte:mask and length part 1
217 //Followed by 0,2, or 8 additional bytes of continued length
218 var b2 = 0;//server does not mask frames
219 var length = payload.length;
220 if(length < 126)
221 {
222 buf = new Buffer(payload.length + 2 + 0);
223 //zero extra bytes
224 b2 |= length;
225 buf.writeUInt8BE(b1,0);
226 buf.writeUInt8BE(b1,1);
227 payload.copy(buf.2);
228 }
229 else if(length<(1<<16))
230 {
231 buf = new Buffer(payload.length + 2 + 2);
232 //two bytes extra
233 b2 |= 126;
234 buf.writeUInt8BE(b1,0);
235 buf.writeUInt8BE(b1,1);
236 //add two tyte length
237 buf.writeUInt16BE(length,2);
238 payload.copy(buf,4);
239 }
240 else
241 {
242 buf = new Buffer(payload.length + 2 + 8);
243 //eight bytes extra
244 b2 |= 127;
245 buf.writeUInt8(b1,0);
246 buf.writeUInt8(b1,1);
247 //add eigth byte length
248 //note:this implementation cannt handle lengths greater than 2^32
249 //the 32 bit length is prefixed with 0X0000
250 buf.writeUInt32BE(0,2);
251 buf.writeUInt32BE(length,6);
252 payload.copy(buf,10);
253 }
254
255 return;
256 }
257
258 exports.listen = function(port,host,connectionHandler)
259 {
260 var srv = http.createServer(function(req,res){});
261
262 srv.on('upgrade',function(req,socket,upgradeHead)
263 {
264 var ws = new WebSocketConnection(req,socket,upgradeHead);
265 connectionHandler(ws);
266 });
267 srv.listen(port,host);
268 };
269
270 //echo.js
271 var websocket = require("./websocket-example");
272 websocket.listen(9999,"localhost",function(conn)
273 {
274 console.log("connection opened");
275 conn.on("data",function(opcode,data)
276 {
277 console.log("message: ",data);
278 conn.send(data);
279 });
280
281 conn.on("close",function(code,reason)
282 {
283 console.log("connection closed: ",code,reason);
284
285 });
286
287 });