@bigtyphoon/melo 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +55 -0
- package/bin/commadtest.ts +10 -0
- package/bin/commands/add.ts +43 -0
- package/bin/commands/init.ts +292 -0
- package/bin/commands/kill.ts +21 -0
- package/bin/commands/list.ts +60 -0
- package/bin/commands/masterha.ts +40 -0
- package/bin/commands/restart.ts +48 -0
- package/bin/commands/start.ts +65 -0
- package/bin/commands/stop.ts +26 -0
- package/bin/melo.ts +30 -0
- package/bin/utils/constants.ts +27 -0
- package/bin/utils/utils.ts +130 -0
- package/dist/bin/commadtest.js +9 -0
- package/dist/bin/commands/add.js +40 -0
- package/dist/bin/commands/init.js +279 -0
- package/dist/bin/commands/kill.js +21 -0
- package/dist/bin/commands/list.js +65 -0
- package/dist/bin/commands/masterha.js +36 -0
- package/dist/bin/commands/restart.js +45 -0
- package/dist/bin/commands/start.js +58 -0
- package/dist/bin/commands/stop.js +20 -0
- package/dist/bin/melo.js +26 -0
- package/dist/bin/utils/constants.js +28 -0
- package/dist/bin/utils/utils.js +134 -0
- package/dist/lib/application.js +888 -0
- package/dist/lib/common/manager/appManager.js +112 -0
- package/dist/lib/common/manager/taskManager.js +39 -0
- package/dist/lib/common/remote/backend/msgRemote.js +63 -0
- package/dist/lib/common/remote/frontend/channelRemote.js +78 -0
- package/dist/lib/common/remote/frontend/sessionRemote.js +76 -0
- package/dist/lib/common/service/backendSessionService.js +337 -0
- package/dist/lib/common/service/channelService.js +514 -0
- package/dist/lib/common/service/connectionService.js +95 -0
- package/dist/lib/common/service/filterService.js +112 -0
- package/dist/lib/common/service/handlerService.js +187 -0
- package/dist/lib/common/service/sessionService.js +610 -0
- package/dist/lib/components/backendSession.js +14 -0
- package/dist/lib/components/channel.js +13 -0
- package/dist/lib/components/connection.js +12 -0
- package/dist/lib/components/connector.js +437 -0
- package/dist/lib/components/dictionary.js +93 -0
- package/dist/lib/components/master.js +39 -0
- package/dist/lib/components/monitor.js +25 -0
- package/dist/lib/components/protobuf.js +156 -0
- package/dist/lib/components/proxy.js +236 -0
- package/dist/lib/components/pushScheduler.js +62 -0
- package/dist/lib/components/remote.js +127 -0
- package/dist/lib/components/server.js +63 -0
- package/dist/lib/components/session.js +20 -0
- package/dist/lib/connectors/commands/handshake.js +119 -0
- package/dist/lib/connectors/commands/heartbeat.js +67 -0
- package/dist/lib/connectors/commands/kick.js +15 -0
- package/dist/lib/connectors/common/coder.js +90 -0
- package/dist/lib/connectors/common/handler.js +57 -0
- package/dist/lib/connectors/hybrid/IHybridSocket.js +3 -0
- package/dist/lib/connectors/hybrid/switcher.js +100 -0
- package/dist/lib/connectors/hybrid/tcpprocessor.js +40 -0
- package/dist/lib/connectors/hybrid/tcpsocket.js +171 -0
- package/dist/lib/connectors/hybrid/wsprocessor.js +49 -0
- package/dist/lib/connectors/hybridconnector.js +89 -0
- package/dist/lib/connectors/hybridsocket.js +139 -0
- package/dist/lib/connectors/mqtt/generate.js +113 -0
- package/dist/lib/connectors/mqtt/mqttadaptor.js +81 -0
- package/dist/lib/connectors/mqtt/protocol.js +48 -0
- package/dist/lib/connectors/mqttconnector.js +107 -0
- package/dist/lib/connectors/mqttsocket.js +59 -0
- package/dist/lib/connectors/sioconnector.js +135 -0
- package/dist/lib/connectors/siosocket.js +69 -0
- package/dist/lib/connectors/udpconnector.js +76 -0
- package/dist/lib/connectors/udpsocket.js +93 -0
- package/dist/lib/filters/handler/serial.js +44 -0
- package/dist/lib/filters/handler/time.js +32 -0
- package/dist/lib/filters/handler/timeout.js +45 -0
- package/dist/lib/filters/handler/toobusy.js +36 -0
- package/dist/lib/filters/rpc/rpcLog.js +43 -0
- package/dist/lib/filters/rpc/toobusy.js +41 -0
- package/dist/lib/index.js +81 -0
- package/dist/lib/interfaces/IComponent.js +3 -0
- package/dist/lib/interfaces/IConnector.js +3 -0
- package/dist/lib/interfaces/IHandlerFilter.js +3 -0
- package/dist/lib/interfaces/ILifeCycle.js +3 -0
- package/dist/lib/interfaces/IPlugin.js +3 -0
- package/dist/lib/interfaces/IPushScheduler.js +3 -0
- package/dist/lib/interfaces/ISocket.js +3 -0
- package/dist/lib/interfaces/IStore.js +3 -0
- package/dist/lib/interfaces/define.js +3 -0
- package/dist/lib/master/master.js +129 -0
- package/dist/lib/master/starter.js +236 -0
- package/dist/lib/master/watchdog.js +120 -0
- package/dist/lib/melo.js +125 -0
- package/dist/lib/modules/console.js +436 -0
- package/dist/lib/modules/masterwatcher.js +98 -0
- package/dist/lib/modules/monitorwatcher.js +124 -0
- package/dist/lib/modules/onlineUser.js +69 -0
- package/dist/lib/modules/restartNotifyModule.js +107 -0
- package/dist/lib/modules/watchServer.js +737 -0
- package/dist/lib/monitor/monitor.js +80 -0
- package/dist/lib/pushSchedulers/buffer.js +96 -0
- package/dist/lib/pushSchedulers/direct.js +58 -0
- package/dist/lib/pushSchedulers/multi.js +80 -0
- package/dist/lib/server/server.js +500 -0
- package/dist/lib/util/appUtil.js +306 -0
- package/dist/lib/util/constants.js +117 -0
- package/dist/lib/util/countDownLatch.js +51 -0
- package/dist/lib/util/events.js +20 -0
- package/dist/lib/util/handlerHelper.js +8 -0
- package/dist/lib/util/log.js +14 -0
- package/dist/lib/util/moduleUtil.js +101 -0
- package/dist/lib/util/pathUtil.js +134 -0
- package/dist/lib/util/remoterHelper.js +8 -0
- package/dist/lib/util/utils.js +358 -0
- package/dist/test/application.js +522 -0
- package/dist/test/config/log4js.json +28 -0
- package/dist/test/config/master.json +9 -0
- package/dist/test/config/servers.json +6 -0
- package/dist/test/filters/handler/serial.js +41 -0
- package/dist/test/filters/handler/time.js +41 -0
- package/dist/test/filters/handler/timeout.js +41 -0
- package/dist/test/filters/handler/toobusy.js +57 -0
- package/dist/test/filters/rpc/rpcLog.js +22 -0
- package/dist/test/filters/rpc/toobusy.js +39 -0
- package/dist/test/manager/mockChannelManager.js +77 -0
- package/dist/test/manager/taskManager.js +68 -0
- package/dist/test/mock-base/app/servers/other-file +0 -0
- package/dist/test/mock-plugin/components/mockPlugin.js +10 -0
- package/dist/test/mock-plugin/events/mockEvent.js +12 -0
- package/dist/test/modules/console.js +242 -0
- package/dist/test/pomelo.js +19 -0
- package/dist/test/remote/channelRemote.js +159 -0
- package/dist/test/service/channel.js +134 -0
- package/dist/test/service/channelService.js +216 -0
- package/dist/test/service/connectionService.js +114 -0
- package/dist/test/service/filterService.js +144 -0
- package/dist/test/service/handlerService.js +65 -0
- package/dist/test/service/sessionService.js +387 -0
- package/dist/test/util/countDownLatch.js +70 -0
- package/dist/test/util/pathUtil.js +108 -0
- package/dist/test/util/utils.js +140 -0
- package/lib/application.ts +1240 -0
- package/lib/common/manager/appManager.ts +118 -0
- package/lib/common/manager/taskManager.ts +50 -0
- package/lib/common/remote/backend/msgRemote.ts +134 -0
- package/lib/common/remote/frontend/channelRemote.ts +91 -0
- package/lib/common/remote/frontend/sessionRemote.ts +91 -0
- package/lib/common/service/backendSessionService.ts +388 -0
- package/lib/common/service/channelService.ts +609 -0
- package/lib/common/service/connectionService.ts +112 -0
- package/lib/common/service/filterService.ts +118 -0
- package/lib/common/service/handlerService.ts +224 -0
- package/lib/common/service/sessionService.ts +731 -0
- package/lib/components/backendSession.ts +14 -0
- package/lib/components/channel.ts +11 -0
- package/lib/components/connection.ts +13 -0
- package/lib/components/connector.ts +533 -0
- package/lib/components/dictionary.ts +121 -0
- package/lib/components/master.ts +41 -0
- package/lib/components/monitor.ts +30 -0
- package/lib/components/protobuf.ts +208 -0
- package/lib/components/proxy.ts +282 -0
- package/lib/components/pushScheduler.ts +70 -0
- package/lib/components/remote.ts +166 -0
- package/lib/components/server.ts +71 -0
- package/lib/components/session.ts +22 -0
- package/lib/connectors/commands/handshake.ts +155 -0
- package/lib/connectors/commands/heartbeat.ts +83 -0
- package/lib/connectors/commands/kick.ts +11 -0
- package/lib/connectors/common/coder.ts +93 -0
- package/lib/connectors/common/handler.ts +62 -0
- package/lib/connectors/hybrid/IHybridSocket.ts +9 -0
- package/lib/connectors/hybrid/switcher.ts +142 -0
- package/lib/connectors/hybrid/tcpprocessor.ts +43 -0
- package/lib/connectors/hybrid/tcpsocket.ts +223 -0
- package/lib/connectors/hybrid/wsprocessor.ts +57 -0
- package/lib/connectors/hybridconnector.ts +134 -0
- package/lib/connectors/hybridsocket.ts +168 -0
- package/lib/connectors/mqtt/generate.ts +103 -0
- package/lib/connectors/mqtt/mqttadaptor.ts +114 -0
- package/lib/connectors/mqtt/protocol.ts +49 -0
- package/lib/connectors/mqttconnector.ts +134 -0
- package/lib/connectors/mqttsocket.ts +79 -0
- package/lib/connectors/sioconnector.ts +161 -0
- package/lib/connectors/siosocket.ts +85 -0
- package/lib/connectors/udpconnector.ts +113 -0
- package/lib/connectors/udpsocket.ts +110 -0
- package/lib/filters/handler/serial.ts +46 -0
- package/lib/filters/handler/time.ts +35 -0
- package/lib/filters/handler/timeout.ts +50 -0
- package/lib/filters/handler/toobusy.ts +37 -0
- package/lib/filters/rpc/rpcLog.ts +42 -0
- package/lib/filters/rpc/toobusy.ts +41 -0
- package/lib/index.ts +74 -0
- package/lib/interfaces/IComponent.ts +47 -0
- package/lib/interfaces/IConnector.ts +20 -0
- package/lib/interfaces/IHandlerFilter.ts +15 -0
- package/lib/interfaces/ILifeCycle.ts +16 -0
- package/lib/interfaces/IPlugin.ts +65 -0
- package/lib/interfaces/IPushScheduler.ts +52 -0
- package/lib/interfaces/ISocket.ts +26 -0
- package/lib/interfaces/IStore.ts +10 -0
- package/lib/interfaces/define.ts +15 -0
- package/lib/master/master.ts +148 -0
- package/lib/master/starter.ts +234 -0
- package/lib/master/watchdog.ts +135 -0
- package/lib/melo.ts +152 -0
- package/lib/modules/console.ts +465 -0
- package/lib/modules/masterwatcher.ts +120 -0
- package/lib/modules/monitorwatcher.ts +151 -0
- package/lib/modules/onlineUser.ts +78 -0
- package/lib/modules/restartNotifyModule.ts +128 -0
- package/lib/modules/watchServer.ts +766 -0
- package/lib/monitor/monitor.ts +99 -0
- package/lib/pushSchedulers/buffer.ts +117 -0
- package/lib/pushSchedulers/direct.ts +65 -0
- package/lib/pushSchedulers/multi.ts +94 -0
- package/lib/server/server.ts +554 -0
- package/lib/util/appUtil.ts +313 -0
- package/lib/util/constants.ts +154 -0
- package/lib/util/countDownLatch.ts +72 -0
- package/lib/util/events.ts +15 -0
- package/lib/util/handlerHelper.ts +5 -0
- package/lib/util/log.ts +11 -0
- package/lib/util/moduleUtil.ts +110 -0
- package/lib/util/pathUtil.ts +132 -0
- package/lib/util/remoterHelper.ts +68 -0
- package/lib/util/utils.ts +365 -0
- package/package.json +93 -0
- package/template/game-server/.vscode/launch.json +27 -0
- package/template/game-server/app/servers/connector/handler/entryHandler.ts +50 -0
- package/template/game-server/app/servers/connector/remote/authRemoter.ts +36 -0
- package/template/game-server/app.ts +30 -0
- package/template/game-server/app.ts.mqtt +32 -0
- package/template/game-server/app.ts.sio +36 -0
- package/template/game-server/app.ts.sio.wss +34 -0
- package/template/game-server/app.ts.udp +31 -0
- package/template/game-server/app.ts.wss +40 -0
- package/template/game-server/config/adminServer.json +5 -0
- package/template/game-server/config/adminUser.json +22 -0
- package/template/game-server/config/clientProtos.json +1 -0
- package/template/game-server/config/dictionary.json +1 -0
- package/template/game-server/config/log4js.json +150 -0
- package/template/game-server/config/master.json +8 -0
- package/template/game-server/config/serverProtos.json +1 -0
- package/template/game-server/config/servers.json +12 -0
- package/template/game-server/copy.js +5 -0
- package/template/game-server/package.json +28 -0
- package/template/game-server/preload.ts +40 -0
- package/template/game-server/tsconfig.json +36 -0
- package/template/npm-install.bat +4 -0
- package/template/npm-install.sh +5 -0
- package/template/readme.md +31 -0
- package/template/shared/server.crt +15 -0
- package/template/shared/server.key +15 -0
- package/template/web-server/app.js +25 -0
- package/template/web-server/app.js.https +36 -0
- package/template/web-server/bin/component.bat +1 -0
- package/template/web-server/bin/component.sh +1 -0
- package/template/web-server/package.json +10 -0
- package/template/web-server/public/css/base.css +76 -0
- package/template/web-server/public/image/logo.png +0 -0
- package/template/web-server/public/image/sp.png +0 -0
- package/template/web-server/public/index.html +57 -0
- package/template/web-server/public/index.html.sio +58 -0
- package/template/web-server/public/js/lib/build/build.js +1730 -0
- package/template/web-server/public/js/lib/build/build.js.wss +1721 -0
- package/template/web-server/public/js/lib/component.json +6 -0
- package/template/web-server/public/js/lib/local/boot/component.json +11 -0
- package/template/web-server/public/js/lib/local/boot/index.js +11 -0
- package/template/web-server/public/js/lib/meloclient.js +456 -0
- package/template/web-server/public/js/lib/meloclient.js.wss +456 -0
- package/template/web-server/public/js/lib/socket.io.js +3 -0
- package/test/application.ts +607 -0
- package/test/filters/handler/serial.ts +47 -0
- package/test/filters/handler/time.ts +47 -0
- package/test/filters/handler/timeout.ts +46 -0
- package/test/filters/handler/toobusy.ts +59 -0
- package/test/filters/rpc/rpcLog.ts +20 -0
- package/test/filters/rpc/toobusy.ts +40 -0
- package/test/manager/mockChannelManager.ts +92 -0
- package/test/manager/taskManager.ts +78 -0
- package/test/mock-base/app/servers/other-file +0 -0
- package/test/mock-plugin/components/mockPlugin.ts +8 -0
- package/test/mock-plugin/events/mockEvent.ts +12 -0
- package/test/modules/console.ts +264 -0
- package/test/pomelo.ts +18 -0
- package/test/remote/channelRemote.ts +176 -0
- package/test/service/channel.ts +161 -0
- package/test/service/channelService.ts +243 -0
- package/test/service/connectionService.ts +142 -0
- package/test/service/filterService.ts +165 -0
- package/test/service/handlerService.ts +77 -0
- package/test/service/sessionService.ts +464 -0
- package/test/util/countDownLatch.ts +81 -0
- package/test/util/pathUtil.ts +122 -0
- package/test/util/utils.ts +165 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import * as util from 'util';
|
|
3
|
+
import { WSProcessor } from './wsprocessor';
|
|
4
|
+
import { TCPProcessor } from './tcpprocessor';
|
|
5
|
+
import { getLogger } from '@bigtyphoon/melo-logger';
|
|
6
|
+
import * as net from 'net';
|
|
7
|
+
import * as tls from 'tls';
|
|
8
|
+
import { TlsOptions } from 'tls';
|
|
9
|
+
import * as WebSocket from 'ws';
|
|
10
|
+
import { TcpSocket } from './tcpsocket';
|
|
11
|
+
import { IHybridSocket } from './IHybridSocket';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
let logger = getLogger('melo', path.basename(__filename));
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
let HTTP_METHODS = [
|
|
17
|
+
'GET', 'POST', 'DELETE', 'PUT', 'HEAD'
|
|
18
|
+
];
|
|
19
|
+
// max length of 'DELETE'
|
|
20
|
+
let MAX_HTTP_METHODS_LEN = 6;
|
|
21
|
+
|
|
22
|
+
let ST_STARTED = 1;
|
|
23
|
+
let ST_CLOSED = 2;
|
|
24
|
+
|
|
25
|
+
let DEFAULT_TIMEOUT = 90;
|
|
26
|
+
|
|
27
|
+
export type SslWatcher = (cb: (opts: tls.SecureContextOptions) => void) => void;
|
|
28
|
+
export interface HybridSwitcherOptions {
|
|
29
|
+
closeMethod ?: 'end';
|
|
30
|
+
timeout ?: number;
|
|
31
|
+
setNoDelay ?: boolean;
|
|
32
|
+
ssl ?: TlsOptions;
|
|
33
|
+
sslWatcher?: SslWatcher;
|
|
34
|
+
}
|
|
35
|
+
export interface IHybridSwitcher {
|
|
36
|
+
on(evt: 'connection' , listener: (socket: IHybridSocket) => void): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Switcher for tcp and websocket protocol
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} server tcp server instance from node.js net module
|
|
43
|
+
*/
|
|
44
|
+
export class HybridSwitcher extends EventEmitter implements IHybridSwitcher {
|
|
45
|
+
server: net.Server;
|
|
46
|
+
wsprocessor: WSProcessor;
|
|
47
|
+
tcpprocessor: TCPProcessor;
|
|
48
|
+
id: number;
|
|
49
|
+
timeout: number;
|
|
50
|
+
setNoDelay: boolean;
|
|
51
|
+
state: number;
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
constructor(server: net.Server, opts: HybridSwitcherOptions) {
|
|
55
|
+
super();
|
|
56
|
+
this.server = server;
|
|
57
|
+
this.wsprocessor = new WSProcessor();
|
|
58
|
+
this.tcpprocessor = new TCPProcessor(opts.closeMethod);
|
|
59
|
+
this.id = 1;
|
|
60
|
+
this.timeout = (opts.timeout || DEFAULT_TIMEOUT) * 1000;
|
|
61
|
+
this.setNoDelay = opts.setNoDelay;
|
|
62
|
+
|
|
63
|
+
if (!opts.ssl) {
|
|
64
|
+
this.server.on('connection', this.newSocket.bind(this));
|
|
65
|
+
} else {
|
|
66
|
+
this.server.on('secureConnection', this.newSocket.bind(this));
|
|
67
|
+
this.server.on('clientError', function (e, tlsSo) {
|
|
68
|
+
logger.warn('an ssl error occured before handshake established: ', e);
|
|
69
|
+
tlsSo.destroy();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.wsprocessor.on('connection', this.emit.bind(this, 'connection'));
|
|
74
|
+
this.tcpprocessor.on('connection', this.emit.bind(this, 'connection'));
|
|
75
|
+
|
|
76
|
+
this.state = ST_STARTED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
newSocket(socket: net.Socket) {
|
|
80
|
+
if (this.state !== ST_STARTED) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
socket.on('error', (err: Error) => {
|
|
85
|
+
logger.debug('connection error:%s, the remote ip is %s && port is %s', err.message, socket.remoteAddress, socket.remotePort);
|
|
86
|
+
socket.destroy();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
socket.on('close', () => {
|
|
90
|
+
socket.destroy();
|
|
91
|
+
});
|
|
92
|
+
socket.setTimeout(this.timeout, function () {
|
|
93
|
+
logger.warn('connection is timeout without communication, the remote ip is %s && port is %s',
|
|
94
|
+
socket.remoteAddress, socket.remotePort);
|
|
95
|
+
socket.destroy();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let self = this;
|
|
99
|
+
|
|
100
|
+
socket.once('data', (data) => {
|
|
101
|
+
// FIXME: handle incomplete HTTP method
|
|
102
|
+
if (isHttp(data)) {
|
|
103
|
+
this.processHttp(self.wsprocessor, socket, data);
|
|
104
|
+
} else {
|
|
105
|
+
if (!!self.setNoDelay) {
|
|
106
|
+
socket.setNoDelay(true);
|
|
107
|
+
}
|
|
108
|
+
this.processTcp(self.tcpprocessor, socket, data);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
close() {
|
|
114
|
+
if (this.state !== ST_STARTED) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.state = ST_CLOSED;
|
|
119
|
+
this.wsprocessor.close();
|
|
120
|
+
this.tcpprocessor.close();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private processHttp(processor: WSProcessor, socket: net.Socket, data: Buffer) {
|
|
124
|
+
processor.add(socket, data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private processTcp(processor: TCPProcessor, socket: net.Socket, data: Buffer) {
|
|
128
|
+
processor.add(socket, data);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
let isHttp = function (data: Buffer) {
|
|
133
|
+
let head = data.toString('utf8', 0, MAX_HTTP_METHODS_LEN - 1);
|
|
134
|
+
|
|
135
|
+
for (let i = 0, l = HTTP_METHODS.length; i < l; i++) {
|
|
136
|
+
if (head.indexOf(HTTP_METHODS[i]) === 0) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false;
|
|
142
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import * as util from 'util';
|
|
3
|
+
import * as net from 'net';
|
|
4
|
+
import * as utils from '../../util/utils';
|
|
5
|
+
import { TcpSocket } from './tcpsocket';
|
|
6
|
+
|
|
7
|
+
let ST_STARTED = 1;
|
|
8
|
+
let ST_CLOSED = 2;
|
|
9
|
+
|
|
10
|
+
// private protocol, no need exports
|
|
11
|
+
let HEAD_SIZE = 4;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* websocket protocol processor
|
|
15
|
+
*/
|
|
16
|
+
export class TCPProcessor extends EventEmitter {
|
|
17
|
+
closeMethod: 'end';
|
|
18
|
+
state: number;
|
|
19
|
+
constructor(closeMethod?: 'end') {
|
|
20
|
+
super();
|
|
21
|
+
this.closeMethod = closeMethod;
|
|
22
|
+
this.state = ST_STARTED;
|
|
23
|
+
}
|
|
24
|
+
add(socket: net.Socket, data: Buffer) {
|
|
25
|
+
if (this.state !== ST_STARTED) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let tcpsocket = new TcpSocket(socket, {
|
|
29
|
+
headSize: HEAD_SIZE,
|
|
30
|
+
headHandler: utils.headHandler,
|
|
31
|
+
closeMethod: this.closeMethod
|
|
32
|
+
});
|
|
33
|
+
this.emit('connection', tcpsocket);
|
|
34
|
+
socket.emit('data', data);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
close() {
|
|
38
|
+
if (this.state !== ST_STARTED) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.state = ST_CLOSED;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {Stream} from 'stream';
|
|
2
|
+
import * as util from 'util';
|
|
3
|
+
import * as net from 'net';
|
|
4
|
+
import {Package} from '@bigtyphoon/melo-protocol';
|
|
5
|
+
import {getLogger} from '@bigtyphoon/melo-logger';
|
|
6
|
+
import {ISocket} from '../../interfaces/ISocket';
|
|
7
|
+
import {IHybridSocket} from './IHybridSocket';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
let logger = getLogger('melo', path.basename(__filename));
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export interface TcpSocketOptions {
|
|
14
|
+
headSize: number;
|
|
15
|
+
headHandler: (data: Buffer) => number;
|
|
16
|
+
closeMethod?: 'end';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Work states
|
|
21
|
+
*/
|
|
22
|
+
let ST_HEAD = 1; // wait for head
|
|
23
|
+
let ST_BODY = 2; // wait for body
|
|
24
|
+
let ST_CLOSED = 3; // closed
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tcp socket wrapper with package compositing.
|
|
28
|
+
* Collect the package from socket and emit a completed package with 'data' event.
|
|
29
|
+
* Uniform with ws.WebSocket interfaces.
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} socket origin socket from node.js net module
|
|
32
|
+
* @param {Object} opts options parameter.
|
|
33
|
+
* opts.headSize size of package head
|
|
34
|
+
* opts.headHandler(headBuffer) handler for package head. caculate and return body size from head data.
|
|
35
|
+
*/
|
|
36
|
+
export class TcpSocket extends Stream implements IHybridSocket {
|
|
37
|
+
readable: boolean;
|
|
38
|
+
writeable: boolean;
|
|
39
|
+
|
|
40
|
+
_socket: net.Socket;
|
|
41
|
+
headSize: number;
|
|
42
|
+
closeMethod: string;
|
|
43
|
+
headBuffer: Buffer;
|
|
44
|
+
headHandler: Function;
|
|
45
|
+
|
|
46
|
+
headOffset: number;
|
|
47
|
+
packageOffset: number;
|
|
48
|
+
packageSize: number;
|
|
49
|
+
packageBuffer: Buffer;
|
|
50
|
+
state: number;
|
|
51
|
+
|
|
52
|
+
constructor(socket: net.Socket, opts?: TcpSocketOptions) {
|
|
53
|
+
// stream style interfaces.
|
|
54
|
+
// TODO: need to port to stream2 after node 0.9
|
|
55
|
+
super();
|
|
56
|
+
if (!socket || !opts) {
|
|
57
|
+
throw new Error('invalid socket or opts');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!opts.headSize || typeof opts.headHandler !== 'function') {
|
|
61
|
+
throw new Error('invalid opts.headSize or opts.headHandler');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.readable = true;
|
|
65
|
+
this.writeable = true;
|
|
66
|
+
|
|
67
|
+
this._socket = socket;
|
|
68
|
+
this.headSize = opts.headSize;
|
|
69
|
+
this.closeMethod = opts.closeMethod;
|
|
70
|
+
this.headBuffer = Buffer.alloc(opts.headSize);
|
|
71
|
+
this.headHandler = opts.headHandler;
|
|
72
|
+
|
|
73
|
+
this.headOffset = 0;
|
|
74
|
+
this.packageOffset = 0;
|
|
75
|
+
this.packageSize = 0;
|
|
76
|
+
this.packageBuffer = null;
|
|
77
|
+
|
|
78
|
+
// bind event form the origin socket
|
|
79
|
+
this._socket.on('data', this.ondata.bind(this));
|
|
80
|
+
this._socket.on('end', this.onend.bind(this));
|
|
81
|
+
this._socket.on('error', this.emit.bind(this, 'error'));
|
|
82
|
+
this._socket.on('close', this.emit.bind(this, 'close'));
|
|
83
|
+
|
|
84
|
+
this.state = ST_HEAD;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
send(msg: any, options: { binary?: boolean }, cb?: (err ?: Error) => void) {
|
|
89
|
+
this._socket.write(msg, options.binary ? 'binary' : 'utf8', cb);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
close() {
|
|
93
|
+
if (!!this.closeMethod && this.closeMethod === 'end') {
|
|
94
|
+
this._socket.end();
|
|
95
|
+
} else {
|
|
96
|
+
try {
|
|
97
|
+
this._socket.destroy();
|
|
98
|
+
} catch (e) {
|
|
99
|
+
logger.error('socket close with destroy error: %j', e.stack);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ondata(chunk: Buffer) {
|
|
105
|
+
if (this.state === ST_CLOSED) {
|
|
106
|
+
throw new Error('socket has closed');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) {
|
|
110
|
+
throw new Error('invalid data');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof chunk === 'string') {
|
|
114
|
+
chunk = Buffer.from(chunk, 'utf8');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let offset = 0, end = chunk.length;
|
|
118
|
+
|
|
119
|
+
while (offset < end && this.state !== ST_CLOSED) {
|
|
120
|
+
if (this.state === ST_HEAD) {
|
|
121
|
+
offset = this.readHead(chunk, offset);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this.state === ST_BODY) {
|
|
125
|
+
offset = this.readBody(chunk, offset);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onend(chunk: Buffer) {
|
|
133
|
+
if (chunk) {
|
|
134
|
+
this._socket.write(chunk);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.state = ST_CLOSED;
|
|
138
|
+
this.reset();
|
|
139
|
+
this.emit('end');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Read head segment from data to socket.headBuffer.
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} socket Socket instance
|
|
146
|
+
* @param {Object} data Buffer instance
|
|
147
|
+
* @param {Number} offset offset read star from data
|
|
148
|
+
* @return {Number} new offset of data after read
|
|
149
|
+
*/
|
|
150
|
+
readHead(data: Buffer, offset: number) {
|
|
151
|
+
let hlen = this.headSize - this.headOffset;
|
|
152
|
+
let dlen = data.length - offset;
|
|
153
|
+
let len = Math.min(hlen, dlen);
|
|
154
|
+
let dend = offset + len;
|
|
155
|
+
|
|
156
|
+
data.copy(this.headBuffer, this.headOffset, offset, dend);
|
|
157
|
+
this.headOffset += len;
|
|
158
|
+
|
|
159
|
+
if (this.headOffset === this.headSize) {
|
|
160
|
+
// if head segment finished
|
|
161
|
+
let size = this.headHandler(this.headBuffer);
|
|
162
|
+
if (size < 0) {
|
|
163
|
+
throw new Error('invalid body size: ' + size);
|
|
164
|
+
}
|
|
165
|
+
// check if header contains a valid type
|
|
166
|
+
if (checkTypeData(this.headBuffer[0])) {
|
|
167
|
+
this.packageSize = size + this.headSize;
|
|
168
|
+
this.packageBuffer = Buffer.alloc(this.packageSize);
|
|
169
|
+
this.headBuffer.copy(this.packageBuffer, 0, 0, this.headSize);
|
|
170
|
+
this.packageOffset = this.headSize;
|
|
171
|
+
this.state = ST_BODY;
|
|
172
|
+
} else {
|
|
173
|
+
dend = data.length;
|
|
174
|
+
logger.error('close the connection with invalid head message, the remote ip is %s && port is %s && message is %j', this._socket.remoteAddress, this._socket.remotePort, data);
|
|
175
|
+
this.close();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return dend;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Read body segment from data buffer to socket.packageBuffer;
|
|
185
|
+
*
|
|
186
|
+
* @param {Object} socket Socket instance
|
|
187
|
+
* @param {Object} data Buffer instance
|
|
188
|
+
* @param {Number} offset offset read star from data
|
|
189
|
+
* @return {Number} new offset of data after read
|
|
190
|
+
*/
|
|
191
|
+
readBody(data: Buffer, offset: number) {
|
|
192
|
+
let blen = this.packageSize - this.packageOffset;
|
|
193
|
+
let dlen = data.length - offset;
|
|
194
|
+
let len = Math.min(blen, dlen);
|
|
195
|
+
let dend = offset + len;
|
|
196
|
+
|
|
197
|
+
data.copy(this.packageBuffer, this.packageOffset, offset, dend);
|
|
198
|
+
|
|
199
|
+
this.packageOffset += len;
|
|
200
|
+
|
|
201
|
+
if (this.packageOffset === this.packageSize) {
|
|
202
|
+
// if all the package finished
|
|
203
|
+
let buffer = this.packageBuffer;
|
|
204
|
+
this.emit('message', buffer);
|
|
205
|
+
this.reset();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return dend;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
reset() {
|
|
212
|
+
this.headOffset = 0;
|
|
213
|
+
this.packageOffset = 0;
|
|
214
|
+
this.packageSize = 0;
|
|
215
|
+
this.packageBuffer = null;
|
|
216
|
+
this.state = ST_HEAD;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let checkTypeData = function (data: number) {
|
|
222
|
+
return data === Package.TYPE_HANDSHAKE || data === Package.TYPE_HANDSHAKE_ACK || data === Package.TYPE_HEARTBEAT || data === Package.TYPE_DATA || data === Package.TYPE_KICK;
|
|
223
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Server as HttpServer } from 'http';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import * as util from 'util';
|
|
4
|
+
import * as net from 'net';
|
|
5
|
+
import * as WebSocket from 'ws';
|
|
6
|
+
|
|
7
|
+
let ST_STARTED = 1;
|
|
8
|
+
let ST_CLOSED = 2;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* websocket protocol processor
|
|
12
|
+
*/
|
|
13
|
+
export class WSProcessor extends EventEmitter {
|
|
14
|
+
httpServer: HttpServer;
|
|
15
|
+
wsServer: WebSocket.Server;
|
|
16
|
+
state: number;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.httpServer = new HttpServer();
|
|
21
|
+
|
|
22
|
+
let self = this;
|
|
23
|
+
this.wsServer = new WebSocket.Server({ server: this.httpServer });
|
|
24
|
+
|
|
25
|
+
this.wsServer.on('connection', function (socket, request) {
|
|
26
|
+
// emit socket to outside
|
|
27
|
+
self.emit('connection', socket, request);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
this.state = ST_STARTED;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
add(socket: net.Socket, data: Buffer) {
|
|
35
|
+
if (this.state !== ST_STARTED) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.httpServer.emit('connection', socket);
|
|
39
|
+
if (typeof (socket as any).ondata === 'function') {
|
|
40
|
+
// compatible with stream2
|
|
41
|
+
(socket as any).ondata(data, 0, data.length);
|
|
42
|
+
} else {
|
|
43
|
+
// compatible with old stream
|
|
44
|
+
socket.emit('data', data);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
close() {
|
|
49
|
+
if (this.state !== ST_STARTED) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.state = ST_CLOSED;
|
|
53
|
+
this.wsServer.close();
|
|
54
|
+
this.wsServer = null;
|
|
55
|
+
this.httpServer = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import * as tls from 'tls';
|
|
3
|
+
import * as util from 'util';
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
|
|
6
|
+
import { HybridSocket } from './hybridsocket';
|
|
7
|
+
import { HybridSwitcher as Switcher, HybridSwitcherOptions } from './hybrid/switcher';
|
|
8
|
+
import { HandshakeCommand } from './commands/handshake';
|
|
9
|
+
import { HeartbeatCommand } from './commands/heartbeat';
|
|
10
|
+
import * as Kick from './commands/kick';
|
|
11
|
+
import * as coder from './common/coder';
|
|
12
|
+
import { ConnectorComponent } from '../components/connector';
|
|
13
|
+
import { DictionaryComponent } from '../components/dictionary';
|
|
14
|
+
import { ProtobufComponent } from '../components/protobuf';
|
|
15
|
+
import { IComponent } from '../interfaces/IComponent';
|
|
16
|
+
import { melo } from '../melo';
|
|
17
|
+
import { IConnector } from '../interfaces/IConnector';
|
|
18
|
+
import { TlsOptions } from 'tls';
|
|
19
|
+
import * as WebSocket from 'ws';
|
|
20
|
+
import { TcpSocket } from './hybrid/tcpsocket';
|
|
21
|
+
import { IHybridSocket } from './hybrid/IHybridSocket';
|
|
22
|
+
|
|
23
|
+
let curId = 1;
|
|
24
|
+
|
|
25
|
+
export interface HybridConnectorOptions extends HybridSwitcherOptions {
|
|
26
|
+
useDict?: boolean;
|
|
27
|
+
useProtobuf?: boolean;
|
|
28
|
+
distinctHost?: boolean;
|
|
29
|
+
realIPKey?: string; // 代理过后真实客户端ip获取字段
|
|
30
|
+
realPortKey?: string; // 代理过后真实客户端port获取字段
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Connector that manager low level connection and protocol bewteen server and client.
|
|
35
|
+
* Develper can provide their own connector to switch the low level prototol, such as tcp or probuf.
|
|
36
|
+
*/
|
|
37
|
+
export class HybridConnector extends EventEmitter implements IConnector {
|
|
38
|
+
opts: HybridConnectorOptions;
|
|
39
|
+
port: number;
|
|
40
|
+
host: string;
|
|
41
|
+
useDict: boolean;
|
|
42
|
+
useProtobuf: boolean;
|
|
43
|
+
handshake: HandshakeCommand;
|
|
44
|
+
heartbeat: HeartbeatCommand;
|
|
45
|
+
distinctHost: boolean;
|
|
46
|
+
ssl: tls.TlsOptions;
|
|
47
|
+
switcher: Switcher;
|
|
48
|
+
|
|
49
|
+
connector: IConnector;
|
|
50
|
+
dictionary: DictionaryComponent;
|
|
51
|
+
protobuf: ProtobufComponent;
|
|
52
|
+
decodeIO_protobuf: IComponent;
|
|
53
|
+
|
|
54
|
+
listeningServer: net.Server | tls.Server;
|
|
55
|
+
|
|
56
|
+
constructor(port: number, host: string, opts?: HybridConnectorOptions) {
|
|
57
|
+
super();
|
|
58
|
+
|
|
59
|
+
this.opts = opts || {};
|
|
60
|
+
if (this.opts.realPortKey) {
|
|
61
|
+
this.opts.realPortKey = opts.realPortKey.toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
if (this.opts.realIPKey) {
|
|
64
|
+
this.opts.realIPKey = opts.realIPKey.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
this.port = port;
|
|
67
|
+
this.host = host;
|
|
68
|
+
this.useDict = opts.useDict;
|
|
69
|
+
this.useProtobuf = opts.useProtobuf;
|
|
70
|
+
this.handshake = new HandshakeCommand(opts);
|
|
71
|
+
this.heartbeat = new HeartbeatCommand(opts);
|
|
72
|
+
this.distinctHost = opts.distinctHost;
|
|
73
|
+
this.ssl = opts.ssl;
|
|
74
|
+
|
|
75
|
+
this.switcher = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start connector to listen the specified port
|
|
80
|
+
*/
|
|
81
|
+
start(cb: () => void) {
|
|
82
|
+
let app = melo.app;
|
|
83
|
+
let self = this;
|
|
84
|
+
|
|
85
|
+
let gensocket = function (socket: IHybridSocket, request: any) {
|
|
86
|
+
let hybridsocket = new HybridSocket(curId++, socket, request, self.opts);
|
|
87
|
+
hybridsocket.on('handshake', self.handshake.handle.bind(self.handshake, hybridsocket));
|
|
88
|
+
hybridsocket.on('heartbeat', self.heartbeat.handle.bind(self.heartbeat, hybridsocket));
|
|
89
|
+
hybridsocket.on('disconnect', self.heartbeat.clear.bind(self.heartbeat, hybridsocket.id));
|
|
90
|
+
hybridsocket.on('closing', Kick.handle.bind(null, hybridsocket));
|
|
91
|
+
self.emit('connection', hybridsocket);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.connector = app.components.__connector__.connector;
|
|
95
|
+
this.dictionary = app.components.__dictionary__;
|
|
96
|
+
this.protobuf = app.components.__protobuf__;
|
|
97
|
+
this.decodeIO_protobuf = app.components.__decodeIO__protobuf__;
|
|
98
|
+
|
|
99
|
+
if (!this.ssl) {
|
|
100
|
+
this.listeningServer = net.createServer();
|
|
101
|
+
} else {
|
|
102
|
+
this.listeningServer = tls.createServer(this.ssl);
|
|
103
|
+
if (this.opts.sslWatcher) {
|
|
104
|
+
this.opts.sslWatcher((opts) => {
|
|
105
|
+
(this.listeningServer as tls.Server).setSecureContext(opts);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.switcher = new Switcher(this.listeningServer, self.opts);
|
|
110
|
+
|
|
111
|
+
this.switcher.on('connection', function (socket, request) {
|
|
112
|
+
gensocket(socket, request);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!!this.distinctHost) {
|
|
116
|
+
this.listeningServer.listen(this.port, this.host);
|
|
117
|
+
} else {
|
|
118
|
+
this.listeningServer.listen(this.port);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
process.nextTick(cb);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
stop(force: boolean, cb: () => void) {
|
|
125
|
+
this.switcher.close();
|
|
126
|
+
this.listeningServer.close();
|
|
127
|
+
|
|
128
|
+
process.nextTick(cb);
|
|
129
|
+
}
|
|
130
|
+
decode = coder.decode;
|
|
131
|
+
|
|
132
|
+
encode = coder.encode;
|
|
133
|
+
|
|
134
|
+
}
|