import Application from '../application'; import define = require('../util/define'); import * as path from 'path'; import * as fs from 'fs'; import { sessionCopyJson, I_clientSocket, I_clientManager, I_connectorConstructor, I_encodeDecodeConfig, loggerLevel } from '../util/interfaceDefine'; import { Session, initSessionApp } from './session'; import * as protocol from '../connector/protocol'; const meFilename = `[${path.basename(__filename, '.js')}.ts]`; export class FrontendServer { private readonly app: Application; private readonly clientManager: ClientManager; constructor(app: Application) { this.app = app; initSessionApp(this.app); protocol.init(this.app); const defaultEncodeDecode: Required = protocol.default_encodeDecode; const encodeDecodeConfig = this.app.someconfig.encodeDecode || {}; this.app.protoEncode = encodeDecodeConfig.protoEncode || defaultEncodeDecode.protoEncode; this.app.msgEncode = encodeDecodeConfig.msgEncode || defaultEncodeDecode.msgEncode; this.app.protoDecode = encodeDecodeConfig.protoDecode || defaultEncodeDecode.protoDecode; this.app.msgDecode = encodeDecodeConfig.msgDecode || defaultEncodeDecode.msgDecode; this.clientManager = new ClientManager(app); } start(cb: () => void) { const self = this; const startCb = function () { const str = `listening at [${self.app.serverInfo.host}:${self.app.serverInfo.clientPort}] ${self.app.serverId} (clientPort)`; console.log(str); cb?.(); }; const omelot = require('../omelot'); const connectorConfig = this.app.someconfig.connector || {}; const ConnectorConstructor: I_connectorConstructor = connectorConfig.connector || omelot.connector.Tcp; return new ConnectorConstructor({ app: this.app, clientManager: this.clientManager, config: this.app.someconfig.connector, startCb }); } /** * Sync session */ applySession(data: Buffer) { const session = JSON.parse(data.slice(1).toString()) as sessionCopyJson; const client = this.app.clients.get(session.uid); if (client) { client.session.applySession(session.settings); } } /** * The front-end server forwards the message of the back-end server to the client */ sendMsgByUids(data: Buffer) { const uidsLen = data.readUInt16BE(1); const msgBuf = data.slice(3 + uidsLen * 4); const clients = this.app.clients; let client: I_clientSocket | undefined; let i: number; for (i = 0; i < uidsLen; i++) { client = clients.get(data.readUInt32BE(3 + i * 4)); if (client) { client.send(msgBuf); } } } } function clientOnOffCb() { } class ClientManager implements I_clientManager { private readonly app: Application; private msgHandler: Record = {}; private readonly serverType: string = ''; private readonly router: Record string>; private readonly clientOnCb: (session: Session) => void = null as any; private readonly clientOffCb: (session: Session) => void = null as any; constructor(app: Application) { this.app = app; this.serverType = app.serverType; this.router = this.app.router; const connectorConfig = this.app.someconfig.connector || {}; this.clientOnCb = connectorConfig.clientOnCb || clientOnOffCb; this.clientOffCb = connectorConfig.clientOffCb || clientOnOffCb; this.loadHandler(); } /** * Front-end server load routing processing */ private loadHandler() { const dirName = path.join(this.app.base, define.some_config.File_Dir.Servers, this.serverType, 'handler'); const exists = fs.existsSync(dirName); if (exists) { const self = this; fs.readdirSync(dirName).forEach(function (filename) { if (!filename.endsWith('.js')) { return; } const name = path.basename(filename, '.js'); const handler = require(path.join(dirName, filename)); if (handler.default && typeof handler.default === 'function') { // eslint-disable-next-line new-cap self.msgHandler[name] = new handler.default(self.app); } }); } } addClient(client: I_clientSocket) { if (client.session) { this.app.logger(loggerLevel.error, `${meFilename} the I_client has already been added, close it`); client.close(); return; } this.app.clientNum++; const session = new Session(this.app.serverId); session.socket = client; client.session = session; this.clientOnCb(session as any); } removeClient(client: I_clientSocket) { const session = client.session; if (!session) { return; } this.app.clients.delete(session.uid); this.app.clientNum--; client.session = null as any; session.socket = null as any; this.clientOffCb(session as any); } handleMsg(client: I_clientSocket, msgBuf: Buffer) { try { if (!client.session) { this.app.logger(loggerLevel.error, `${meFilename} cannot handle msg before added, close it`); client.close(); return; } const data = this.app.protoDecode(msgBuf); this.app.filter.globalBeforeFilter(data, client.session, (hasError) => { if (hasError) { return; } const cmdArr = this.app.routeConfig2[data.cmd]; if (this.serverType === cmdArr[0]) { const msg = this.app.msgDecode(data.cmd, data.msg); this.app.filter.beforeFilter(data.cmd, msg, client.session, (hasError) => { if (hasError) { return; } this.msgHandler[cmdArr[1]][cmdArr[2]](msg, client.session, this.callBack(client, data.cmd)); }); } else { this.doRemote(data, client.session, cmdArr); } }); } catch (e: any) { this.app.logger(loggerLevel.error, e); } } /** * Callback */ private callBack(client: I_clientSocket, cmd: number) { const self = this; return function (msg: any) { if (msg === undefined) { msg = null; } const buf = self.app.protoEncode(cmd, msg); client.send(buf); self.app.filter.afterFilter(cmd, msg, client.session); }; } /** * Forward client messages to the backend server */ private doRemote(msg: { 'cmd': number; 'msg': Buffer }, session: Session, cmdArr: string[]) { const id = this.router[cmdArr[0]](session); const socket = this.app.rpcPool.getSocket(id); if (!socket) { return; } const svr = this.app.serversIdMap.get(id); if (svr?.serverType !== cmdArr[0] || svr.frontend) { this.app.logger(loggerLevel.error, `${meFilename} illegal doRemote`); return; } const sessionBuf = session.sessionBuf; const buf = Buffer.allocUnsafe(9 + sessionBuf.length + msg.msg.length); buf.writeUInt32BE(5 + sessionBuf.length + msg.msg.length, 0); buf.writeUInt8(define.Rpc_Msg.clientMsgIn, 4); buf.writeUInt16BE(sessionBuf.length, 5); sessionBuf.copy(buf, 7); buf.writeUInt16BE(msg.cmd, 7 + sessionBuf.length); msg.msg.copy(buf, 9 + sessionBuf.length); socket.send(buf); } }