"use strict"; var crypto = require('crypto'); var fs = require("fs"); function wsServer(options) { var evt = ["Close", "Error", "SocketIn", "Connect", "Receive", "ClientError", "ClientClose", "ClientTimeout"]; for (var i = 0; i < evt.length; i++) { this["on" + evt[i]] = function () { } } this.options = options.SSL ? { key: fs.readFileSync(options.KEY), cert: fs.readFileSync(options.CERT), requestCert: true, rejectUnauthorized: true, passphrase: options.PASSWORD, ca: [fs.readFileSync(options.CERT)] } : null; this.ssl = options.SSL; } wsServer.prototype.listen = function (port, func) { var net = require(this.ssl ? 'tls' : 'net'); var tcpserver = net.createServer(this.options, onClientConnect.bind(this)); tcpserver.listen(port, func); tcpserver.on('close', this.onClose.bind(this)); tcpserver.on('error', this.onError.bind(this)); this.tcpServer = tcpserver; } wsServer.prototype.send = function (msg, socket) { socket.send(msg); } wsServer.prototype.close = function () { return new Promise((resolve) => { this.tcpServer.close(resolve); }); } module.exports = wsServer; function onClientConnect(socket) { socket.send = function (msg) { if (msg) this.protocol.sendData(msg, socket); } socket.on('close', this.onClientClose.bind(this, socket)); socket.on('error', this.onClientError.bind(this, socket)); var $this = this; socket.setTimeout(3000); socket.on('timeout', this.onClientTimeout.bind(this, socket)); $this.onSocketIn(socket); socket.on('data', function (data) { if (socket.protocol) { socket.protocol.readData(data, socket, $this); } else { var header = readHeader(data); socket.requestHeader = header; if (header["Sec-WebSocket-Key"]) { socket.protocol = protocols.var1; } else if (header["Sec-WebSocket-Key1"]) { socket.protocol = protocols.var2; } else { socket.protocol = protocols.tcp; socket.protocol.readData(data, socket, $this); return; } socket.protocol.handShake(header, socket, data); $this.onConnect(socket); } }); } var protocols = { var1: { handShake: function (header, socket) { var hasher = crypto.createHash("sha1"); hasher.update(header["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); var hashmsg = hasher.digest().toString('base64'); var origin = header.Origin; var protocol = header['sec-websocket-protocol']; if (protocol) protocol.split(/, */); var respon = ["HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: WebSocket", `Sec-WebSocket-Accept:${hashmsg}`, `Sec-WebSocket-Origin:${origin}`]; if (protocol) respon.push(`Sec-WebSocket-Protocol: ${protocol}`); //var extens = header["Sec-Websocket-Extensions"]; //if (extens) { // respon.push(`Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits`); //} respon.push("\r\n"); socket.write(respon.join("\r\n")); }, readData: function (data, socket, server) { var start = 0; while (start < data.length) { var iseof = (data[start] >> 7) > 0; var frameType = data[start++] & 0xF; var hasMask = (data[start] >> 7) > 0; var length = (data[start++] & 0x7F); if (length == 126) { length = data.readUInt16BE(start); start = start + 2; } else if (length == 127) { length = data.readUInt32BE(start); start = start + 4; return; } var markIndex = start; start = start + 4; for (let i = 0; i < length; i++) { data[start] = data[start] ^ data[markIndex + (i % 4)]; start++; } switch (frameType) { case FrameTypes.Close: socket.end(); // server.onClientClose(socket); break; case FrameTypes.Binary: break; case FrameTypes.Ping: break; case FrameTypes.Pong: break; case FrameTypes.Text: var msg = data.toString("utf8", markIndex + 4, markIndex + 4 + length); server.onReceive(msg, socket); break; default: break; } } }, sendData: function (text, socket) { var textBuffer = Buffer.from(text); var length = textBuffer.length; var data; if (length < 126) { data = Buffer.alloc(length + 2); data[0] = 129; data.writeUInt8(length, 1); textBuffer.copy(data, 2); } else if (length >= 126 && length < 65536) { data = Buffer.alloc(length + 4); data[0] = 129; data.writeUInt8(126, 1); data.writeUInt16BE(length, 2); textBuffer.copy(data, 4); } else { data = Buffer.alloc(length + 10); data[0] = 0x81; data[1] = 127; data.writeUInt32BE(0, 2); data.writeUInt32BE(length, 6); textBuffer.copy(data, 10); } socket.write(data); } }, var2: { handShake: function (header, socket, buffer) { var key1 = header["Sec-WebSocket-Key1"]; var key2 = header["Sec-WebSocket-Key2"]; var origin = header["Origin"]; var n1 = getNumber(key1); n1 = parseInt(n1); n1 = n1 / getSpace(key1); var n2 = getNumber(key2); n2 = parseInt(n2); n2 = n2 / getSpace(key2); var buf = Buffer.alloc(16); buf.writeIntBE(n1, 0, 4, true); buf.writeIntBE(n2, 4, 4, true); buffer.copy(buf, 8, buffer.length - 8, buffer.length); var hasherbs = crypto.createHash("md5"); hasherbs = hasherbs.update(buf); hasherbs = hasherbs.digest(); var host = "ws://" + header["Host"] + "/"; var headers = [ "HTTP/1.1 101 WebSocket Protocol Handshake", "Upgrade: WebSocket", "Connection: Upgrade", "Sec-WebSocket-Origin:" + origin, "Sec-WebSocket-Location:" + host , "\r\n" ]; socket.write(headers.join("\r\n")); socket.write(hasherbs); }, buffer: null , readData: function (data, socket, server) { var start = 0; while (start < data.length) { if (data[start] != 0) { break;//error } var end = start + 1; while (data[end] != 255 && end < data.length) { end++; } var msg = data.toString("utf8", start + 1, end); server.onReceive(msg, socket); start = end + 1; } }, sendData: function (text, socket) { var textBuffer = Buffer.from(text, "utf-8"); var length = textBuffer.length; var wrappedBytes = Buffer.alloc(length + 2); wrappedBytes[0] = 0; textBuffer.copy(wrappedBytes, 1); wrappedBytes[wrappedBytes.length - 1] = 255; socket.write(wrappedBytes); } }, tcp: { readData: function (data, socket, server) { var start = 0; if (socket.unread_data) { data = Buffer.concat([socket.unread_data, data], socket.unread_data.length + data.length); socket.unread_data = null; } let isread = false; while (start < data.length) { let length = data.readUInt8(start); let index = start + 1; if (length === 254) { //不够读长度咋办 length = data.readUInt16BE(index); index += 2; } else if (length === 255) { length = data.readUInt32BE(index); index += 4; } if (data.length < index + length) { socket.unread_data = isread ? data.slice(start) : data; return; } let msg = data.toString("utf8", index, index + length); isread = true; server.onTcpReceive(msg, socket); start = index + length; } }, sendData: function (text, socket) { var textBuffer = Buffer.from(text); var length = textBuffer.length; var data; if (length < 254) { data = Buffer.alloc(length + 1); data.writeUInt8(length); textBuffer.copy(data, 1); } else if (length >= 254 && length < 65536) { data = Buffer.alloc(length + 3); data.writeUInt8(254); data.writeUInt16BE(length, 1); textBuffer.copy(data, 3); } else { data = Buffer.alloc(length + 5); data.writeUInt8(255); data.writeUInt32BE(length, 1); textBuffer.copy(data, 5); } socket.write(data); } } }; function getNumber(str) { return str.replace(/\D/g, ""); } function getSpace(str) { return str.replace(/\S/g, "").length; } function readHeader(data) { var header = {}, key, flag = 0; for (var i = 0; i < data.length; i++) { switch (data[i]) { case 0x0D://\r key && (header[key] = data.toString("utf8", flag, i)); break; case 0x0A://\n key = null; flag = i + 1; break; case 0x3A://: if (!key) { key = data.toString("utf8", flag, i); data[i + 1] == 0x20 ? flag = i + 2 : flag = i + 1; } break; } } if (flag < data.length) { header.CONTENT = data.toString("utf8", flag); } return header; } var FrameTypes = { Continuation: 0, Text: 1, Binary: 2, Close: 8, Ping: 9, Pong: 10, }; //%x0 代表一个继续帧 //%x1 代表一个文本帧 //%x2 代表一个二进制帧 //%x3-7 保留用于未来的非控制帧 //%x8 代表连接关闭 //%x9 代表ping //%xA 代表pong //%xB-F 保留用于未来的控制帧