UNPKG

ssocket

Version:

仿 Koa 中间件控制的 WebSocket 服务

480 lines (479 loc) 19 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.expandStatusCode = exports.StatusCode = exports.decode = exports.encode = exports.parseResponseJson = exports.parseRequestJson = exports.SocketStatus = exports.PackageType = void 0; const zlib_1 = __importDefault(require("zlib")); const logger_1 = __importDefault(require("./logger")); const UINT32_MAX = 0xFFFFFFFF; Buffer.prototype.writeUInt64BE = function (value, offset = 0) { let big = ~~(value / UINT32_MAX); let low = (value % UINT32_MAX) - big; this.writeUInt32BE(big, offset); this.writeUInt32BE(low, offset + 4); return offset + 4; }; Buffer.prototype.readUInt64BE = function (offset = 0) { let hex = this.slice(offset, offset + 8).toString("hex"); return parseInt(hex, 16); }; const logger = logger_1.default("code"); /**字段类型 */ var FieldType; (function (FieldType) { /**多层结构 */ FieldType["message"] = "message"; FieldType["required"] = "required"; FieldType["optional"] = "optional"; /**数组 */ FieldType["repeated"] = "repeated"; })(FieldType || (FieldType = {})); /**数据类型 */ var DataType; (function (DataType) { DataType["uint8"] = "uint8"; DataType["uint16"] = "uint16"; DataType["uint32"] = "uint32"; DataType["uint64"] = "uint64"; DataType["float"] = "float"; DataType["double"] = "double"; DataType["string"] = "string"; DataType["message"] = "message"; })(DataType || (DataType = {})); class Protos { constructor() { this.protos = {}; } parse(protos_config) { for (let key in protos_config) { this.protos[key] = this.parseObject(protos_config[key]); } logger("ProtosCode:parse", { protos_config, proto: this.protos }); } parseObject(obj) { let proto = {}; let nestProtos = {}; let tags = {}; for (let name in obj) { let tag = obj[name]; let params = name.split(/\s+/); switch (params[0]) { case FieldType.message: if (params.length !== 2) { continue; } nestProtos[params[1]] = this.parseObject(tag); continue; case FieldType.required: case FieldType.optional: case FieldType.repeated: { // params length should be 3 and tag can't be duplicated if (params.length !== 3 || !!tags[tag]) { continue; } proto[params[2]] = { option: params[0], type: params[1], tag: tag }; tags[tag] = params[2]; } } } proto.__messages = nestProtos; proto.__tags = tags; return proto; } decode(protos_name, buffer) { if (this.protos[protos_name]) { let data = {}; this.read(this.protos[protos_name], data, buffer, 0); logger("ProtosCode:decode", { data }); return data; } return buffer.length ? JSON.parse(buffer.toString() || "{}") : {}; } encode(protos_name, data) { if (this.protos[protos_name] && data) { let buffer = Buffer.alloc(Buffer.byteLength(JSON.stringify(data)) * 2); let length = this.write(this.protos[protos_name], data, buffer); logger("ProtosCode:encode", { protos_name, data, length }); return buffer.slice(0, length); } return Buffer.from(data ? JSON.stringify(data) : ""); } writeTag(buffer, tag, offset) { buffer.writeUInt8(+tag, offset++); logger("ProtosCode:writeTag", { tag, offset }); return offset; } readTag(buffer, offset) { let tag = buffer.readUInt8(offset++); logger("ProtosCode:readTag", { offset: offset - 1, tag }); return tag; } write(protos, data, buffer) { let offset = 0; if (protos) { logger("ProtosCode:write1", { data }); for (let name in data) { if (!!protos[name]) { let proto = protos[name]; logger("ProtosCode:write2", { name, data: data[name], proto }); switch (proto.option) { case FieldType.required: case FieldType.optional: offset = this.writeTag(buffer, proto.tag, offset); offset = this.writeBody(data[name], proto.type, buffer, offset, protos); break; case FieldType.repeated: offset = this.writeTag(buffer, proto.tag, offset); buffer.writeInt32BE(+data[name].length, offset); offset += 4; for (let i = 0, l = data[name].length; i < l; i++) { offset = this.writeBody(data[name][i], proto.type, buffer, offset, protos); } break; } } } } logger("ProtosCode:write3", { offset }); return offset; } writeBody(value, type, buffer, offset, protos) { logger("ProtosCode:writeBody", { type, value, offset }); switch (type) { case DataType.uint8: buffer.writeUInt8(+value, offset); offset += 1; break; case DataType.uint16: buffer.writeUInt16BE(+value, offset); offset += 2; break; case DataType.uint32: buffer.writeUInt32BE(+value, offset); offset += 4; break; case DataType.uint64: buffer.writeUInt64BE(+value, offset); offset += 8; break; case DataType.float: buffer.writeFloatBE(+value, offset); offset += 4; break; case DataType.double: buffer.writeDoubleBE(+value, offset); offset += 8; break; case DataType.string: // Encode length let length = Buffer.byteLength(value + ""); buffer.writeUInt32BE(+length, offset); offset += 4; // write string buffer.write(value + "", offset, length); offset += length; break; default: let message = protos.__messages[type]; if (message) { let tmpBuffer = Buffer.alloc(Buffer.byteLength(JSON.stringify(value)) * 2); let length = this.write(message, value, tmpBuffer); buffer.writeUInt32BE(+length, offset); offset += 4; tmpBuffer.copy(buffer, offset, 0, length); offset += length; } break; } return offset; } read(protos, data, buffer, offset) { logger("ProtosCode:decode1", { offset, data, protos }); if (!!protos) { while (offset < buffer.length) { let tag = this.readTag(buffer, offset); offset += 1; let name = protos.__tags[tag]; let proto = protos[name]; logger("ProtosCode:decode2", { offset, tag, name, proto }); switch (proto.option) { case 'optional': case 'required': let body = this.readBody(buffer, proto.type, offset, protos); offset = body.offset; data[name] = body.value; break; case 'repeated': if (!data[name]) { data[name] = []; } let length = buffer.readUInt32BE(offset); offset += 4; for (let i = 0; i < length; i++) { let body = this.readBody(buffer, proto.type, offset, protos); offset = body.offset; data[name].push(body.value); } break; } } return offset; } return 0; } readBody(buffer, type, offset, protos) { let value = ""; switch (type) { case DataType.uint8: value = buffer.readUInt8(offset); offset += 1; break; case DataType.uint16: value = buffer.readUInt16BE(offset); offset += 2; break; case DataType.uint32: value = buffer.readUInt32BE(offset); offset += 4; break; case DataType.uint64: value = buffer.readUInt64BE(offset); offset += 8; break; case DataType.float: value = buffer.readFloatBE(offset); offset += 4; break; case DataType.double: value = buffer.readDoubleBE(offset); offset += 8; break; case DataType.string: let length = buffer.readUInt32BE(offset); offset += 4; value = buffer.toString('utf8', offset, offset += length); break; default: let message = protos.__messages[type]; if (message) { let length = buffer.readUInt32BE(offset); offset += 4; this.read(message, value = {}, buffer.slice(offset, offset += length), 0); } break; } logger("ProtosCode:readBody", { offset, type, value }); return { value, offset }; } } const RequestProtos = new Protos(); const ResponseProtos = new Protos(); var PackageType; (function (PackageType) { /**握手 */ PackageType[PackageType["shakehands"] = 0] = "shakehands"; /**心跳 */ PackageType[PackageType["heartbeat"] = 1] = "heartbeat"; /**消息 */ PackageType[PackageType["data"] = 2] = "data"; })(PackageType = exports.PackageType || (exports.PackageType = {})); /**Socket 状态 */ var SocketStatus; (function (SocketStatus) { /**打开 */ SocketStatus[SocketStatus["OPEN"] = 0] = "OPEN"; /**正在握手 */ SocketStatus[SocketStatus["SHAKING_HANDS"] = 1] = "SHAKING_HANDS"; /**握手完毕 */ SocketStatus[SocketStatus["HANDSHAKE"] = 2] = "HANDSHAKE"; /**连接 */ SocketStatus[SocketStatus["CONNECTION"] = 3] = "CONNECTION"; /**关闭 */ SocketStatus[SocketStatus["CLOSE"] = 4] = "CLOSE"; })(SocketStatus = exports.SocketStatus || (exports.SocketStatus = {})); /** * 配置 Protos 文件 * @param config */ function parseRequestJson(config) { RequestProtos.parse(config); } exports.parseRequestJson = parseRequestJson; /** * 配置 Protos 文件 * @param config */ function parseResponseJson(config) { ResponseProtos.parse(config); } exports.parseResponseJson = parseResponseJson; /** * 消息封包 * - +------+----------------------------------+------+ * - | head | This data exists when type == 0 | body | * - +------+------------+---------------------+------+ * - | type | id length | id | ack | * - +------+------------+---------------------+------+ * - | 1B | 4B | -- | 1B | * - +------+------------+---------------------+------+ * - +------+----------------------------------+------+ * - | head | This data exists when type == 1 | body | * - +------+----------------------------------+------+ * - | type | body length | time | * - +------+----------------------------------+------+ * - | 1B | 0B | 8B | * - +------+----------------------------------+------+ * - +------+-------------------------------------------------------------------------------+------+ * - | head | This data exists when type == 2 | body | * - +------+------------+---------------+--------+--------+------------+-----+-------------+------+ * - | type | request_id | path length | path | status | msg length | msg | body length | body | * - +------+------------+---------------+--------+--------+------------+-----+-------------+------+ * - | 1B | 4B | 4B | -- | 4B | 4B | -- | 4B | -- | * - +------+------------+---------------+--------+--------+------------+-----+-------------+------+ * - * @param type 类型:0握手|1心跳|2数据 * @param package_data */ function encode(type, package_data) { if (PackageType.data == type) { let { path = "", request_id = 0, status = 0, msg = "", data } = package_data || {}; let _data = ResponseProtos.encode(path, data); if (_data.length > 128) { _data = zlib_1.default.gzipSync(_data); } let _type = Buffer.allocUnsafe(1); _type.writeUInt8(+type); let _request_id = Buffer.allocUnsafe(4); _request_id.writeUInt32BE(+request_id); let _path = Buffer.from(path); let _path_length = Buffer.allocUnsafe(4); _path_length.writeUInt32BE(_path.length); let _status = Buffer.allocUnsafe(4); _status.writeUInt32BE(+status); let _msg = Buffer.from(msg); let _msg_length = Buffer.allocUnsafe(4); _msg_length.writeUInt32BE(_msg.length); let _data_length = Buffer.allocUnsafe(4); _data_length.writeUInt32BE(_data.length); return Buffer.concat([ _type, _request_id, _path_length, _path, _status, _msg_length, _msg, _data_length, _data ]); } else if (type == PackageType.heartbeat) { let _type = Buffer.allocUnsafe(1); _type.writeUInt8(+type); let _data = Buffer.allocUnsafe(8); _data.writeUInt64BE(Date.now()); return Buffer.concat([_type, _data]); } else if (type == PackageType.shakehands) { let { id, ack } = package_data || {}; let _type = Buffer.allocUnsafe(1); _type.writeUInt8(+type); let _id = Buffer.from(id); let _id_length = Buffer.allocUnsafe(4); _id_length.writeUInt32BE(_id.length); let _ack = Buffer.allocUnsafe(1); _ack.writeUInt8(+ack); return Buffer.concat([_type, _id_length, _id, _ack]); } return Buffer.alloc(0); } exports.encode = encode; /** * 消息拆包 * - +------+----------------------------------+------+ * - | head | This data exists when type == 0 | body | * - +------+------------+---------------------+------+ * - | type | id length | id | ack | * - +------+------------+---------------------+------+ * - | 1B | 4B | -- | 1B | * - +------+------------+---------------------+------+ * - +------+----------------------------------+------+ * - | head | This data exists when type == 1 | time | * - +------+----------------------------------+------+ * - | type | body length | body | * - +------+----------------------------------+------+ * - | 1B | 0B | 8B | * - +------+----------------------------------+------+ * - +------+---------------------------------------------------+------+ * - | head | This data exists when type == 2 | body | * - +------+------------+---------------+--------+-------------+------+ * - | type | request_id | path length | path | body length | body | * - +------+------------+---------------+--------+-------------+------+ * - | 1B | 4B | 4B | 4B | -- | 4B | * - +------+------------+---------------+--------+-------------+------+ * @param buffer */ function decode(_buffer) { try { if (Buffer.isBuffer(_buffer)) { let index = 0; let buffer = Buffer.from(_buffer); let type = buffer.slice(index, index += 1).readUInt8(); if (type == PackageType.data) { let request_id = buffer.slice(index, index += 4).readUInt32BE(); let path_length = buffer.slice(index, index += 4).readUInt32BE(); let path = buffer.slice(index, index += path_length).toString(); let data_length = buffer.slice(index, index += 4).readUInt32BE(); let data_buffer = data_length ? buffer.slice(index, index += data_length) : Buffer.alloc(0); // 判断是否 GZIP 压缩的数据 if (data_buffer.length > 2 && data_buffer.slice(0, 2).readUInt16BE() == 0x8b1f) { data_buffer = zlib_1.default.gunzipSync(data_buffer); } let data = RequestProtos.decode(path, data_buffer); return { type, request_id, path, data }; } else if (type == PackageType.heartbeat) { let data = buffer.slice(index, index += 8).readUInt64BE(); return { type, data }; } else if (type == PackageType.shakehands) { let id_length = buffer.slice(index, index += 4).readUInt32BE(); let id = id_length ? buffer.slice(index, index += id_length).toString() : ""; let ack = buffer.slice(index, index += 1).readUInt8(); return { type, id, ack }; } } ; } catch (error) { logger("decode", error); } return {}; } exports.decode = decode; /**系统状态码:这个状态码会通过事件返回给前端 */ exports.StatusCode = { 4100: [4100, "client ping timeout"], 4101: [4101, "connection close"], 4102: [4102, "server ping timeout"], 4103: [4103, "server error"], 200: [200, "ok"], }; class CodeError extends Error { constructor(message) { super(message); } } /** * 扩展状态码 * @param code * @param msg */ function expandStatusCode(code, msg) { if (exports.StatusCode[code]) throw new CodeError(" code already exists "); exports.StatusCode[code] = [code, msg]; } exports.expandStatusCode = expandStatusCode; exports.default = exports.StatusCode;