redux-cluster 1.10.0 → 2.0.1
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 +21 -21
- package/README.md +345 -471
- package/dist/cjs/core/backup.d.ts +10 -0
- package/dist/cjs/core/backup.d.ts.map +1 -0
- package/dist/cjs/core/backup.js +166 -0
- package/dist/cjs/core/redux-cluster.d.ts +47 -0
- package/dist/cjs/core/redux-cluster.d.ts.map +1 -0
- package/dist/cjs/core/redux-cluster.js +370 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/network/client.d.ts +23 -0
- package/dist/cjs/network/client.d.ts.map +1 -0
- package/dist/cjs/network/client.js +251 -0
- package/dist/cjs/network/server.d.ts +39 -0
- package/dist/cjs/network/server.d.ts.map +1 -0
- package/dist/cjs/network/server.js +440 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/index.d.ts +125 -0
- package/dist/cjs/types/index.d.ts.map +1 -0
- package/dist/cjs/types/index.js +20 -0
- package/dist/cjs/utils/crypto.d.ts +22 -0
- package/dist/cjs/utils/crypto.d.ts.map +1 -0
- package/dist/cjs/utils/crypto.js +404 -0
- package/dist/esm/core/backup.d.ts +10 -0
- package/dist/esm/core/backup.d.ts.map +1 -0
- package/dist/esm/core/backup.js +129 -0
- package/dist/esm/core/redux-cluster.d.ts +47 -0
- package/dist/esm/core/redux-cluster.d.ts.map +1 -0
- package/dist/esm/core/redux-cluster.js +363 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +25 -0
- package/dist/esm/network/client.d.ts +23 -0
- package/dist/esm/network/client.d.ts.map +1 -0
- package/dist/esm/network/client.js +214 -0
- package/dist/esm/network/server.d.ts +39 -0
- package/dist/esm/network/server.d.ts.map +1 -0
- package/dist/esm/network/server.js +403 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/types/index.d.ts +125 -0
- package/dist/esm/types/index.d.ts.map +1 -0
- package/dist/esm/types/index.js +17 -0
- package/dist/esm/utils/crypto.d.ts +22 -0
- package/dist/esm/utils/crypto.d.ts.map +1 -0
- package/dist/esm/utils/crypto.js +351 -0
- package/package.json +115 -34
- package/index.js +0 -678
- package/test.auto.js +0 -94
- package/test.auto.proc1.js +0 -97
- package/test.auto.proc2.js +0 -85
- package/test.visual.client.highload.js +0 -102
- package/test.visual.client.js +0 -103
- package/test.visual.error.js +0 -45
- package/test.visual.js +0 -97
- package/test.visual.server.highload.js +0 -102
- package/test.visual.server.js +0 -103
package/index.js
DELETED
|
@@ -1,678 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redux-Cluster
|
|
3
|
-
* (c) 2018 by Siarhei Dudko.
|
|
4
|
-
*
|
|
5
|
-
* Cluster (default IPC cluster channel) module for redux synchronizes all redux store in cluster processes (v.1.0.x).
|
|
6
|
-
* Cluster (default IPC cluster channel) and Socket (custom IPC or TCP channel) for redux synchronizes all redux store (v.1.1.x).
|
|
7
|
-
* Use new Parser and Zlib Stream (v.1.6.x).
|
|
8
|
-
* LICENSE MIT
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
"use strict"
|
|
12
|
-
|
|
13
|
-
var Redux = require('redux'),
|
|
14
|
-
Cluster = require('cluster'),
|
|
15
|
-
Lodash = require('lodash'),
|
|
16
|
-
Crypto = require('crypto'),
|
|
17
|
-
Net = require('net'),
|
|
18
|
-
Path = require('path'),
|
|
19
|
-
Os = require('os'),
|
|
20
|
-
Objectstream = require('@sergdudko/objectstream'),
|
|
21
|
-
Eventstream = require('event-stream'),
|
|
22
|
-
Fs = require('fs'),
|
|
23
|
-
Stream = require('stream'),
|
|
24
|
-
Zlib = require('zlib');
|
|
25
|
-
|
|
26
|
-
var ReduxClusterModule = {}; //модуль
|
|
27
|
-
Object.assign(ReduxClusterModule, Redux); //копирую свойства Redux
|
|
28
|
-
var reducers = {}; //список редьюсеров (хэш собирается по имени редьюсера для совместимости различных ОС, т.к. hasher(<function>.toString()) для разных ос дает разные суммы)
|
|
29
|
-
|
|
30
|
-
//эмулирую performance.now()
|
|
31
|
-
const hrtimeproc = process.hrtime.bigint();
|
|
32
|
-
var performance = {now:function(){
|
|
33
|
-
try{
|
|
34
|
-
let hrtimeprocnew = process.hrtime.bigint();
|
|
35
|
-
let my = [parseInt(((hrtimeprocnew - hrtimeproc) / 1000000n).toString()), parseInt(((hrtimeprocnew - hrtimeproc) % 1000000n).toString())];
|
|
36
|
-
return parseFloat(my[0]+"."+my[1]);
|
|
37
|
-
} catch(err){
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
}};
|
|
41
|
-
|
|
42
|
-
function hasher(data, algorithm = 'sha1'){ //хэширование редьюсера
|
|
43
|
-
if(typeof(data) === 'string'){
|
|
44
|
-
const hash = Crypto.createHash(algorithm);
|
|
45
|
-
hash.update(data);
|
|
46
|
-
return(hash.digest('hex'));
|
|
47
|
-
} else
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function encrypter(data, pass){ //енкриптор
|
|
52
|
-
const cipher = Crypto.createCipheriv('aes-256-ctr', hasher(pass, "sha256").toString('hex').slice(0, 32), hasher("gRy56$#hEUjs*4@h", "md5").toString('hex').slice(0, 16));
|
|
53
|
-
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
54
|
-
encrypted += cipher.final('hex');
|
|
55
|
-
return encrypted;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function decrypter(data, pass){ //декриптор
|
|
59
|
-
const cipher = Crypto.createDecipheriv('aes-256-ctr', hasher(pass, "sha256").toString('hex').slice(0, 32), hasher("gRy56$#hEUjs*4@h", "md5").toString('hex').slice(0, 16));
|
|
60
|
-
let decrypted = cipher.update(data, 'hex', 'utf8');
|
|
61
|
-
decrypted += cipher.final('utf8');
|
|
62
|
-
return decrypted;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/*
|
|
67
|
-
Типы сообщений:
|
|
68
|
-
|
|
69
|
-
МастерВВоркер:
|
|
70
|
-
СтатусСоединения{ //отправка статуса соединения с сервером в воркеры
|
|
71
|
-
_msg:"REDUX_CLUSTER_CONNSTATUS", //тип сообщения (обязательно)
|
|
72
|
-
_hash:_store.RCHash, //хэш названия редьюсера (обязательно для идентификации нужного стора)
|
|
73
|
-
_connected:true //статус соединения с сервером
|
|
74
|
-
}
|
|
75
|
-
Диспатчер{ //отправка экшена клиенту
|
|
76
|
-
_msg:"REDUX_CLUSTER_MSGTOWORKER",
|
|
77
|
-
_hash:self.store.RCHash,
|
|
78
|
-
_action:{ //диспатчер (обязательно)
|
|
79
|
-
type:"REDUX_CLUSTER_SYNC",
|
|
80
|
-
payload:self.store.getState()
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
СерверВСокет:
|
|
85
|
-
Авторизация{ //ответ на запрос авторизации в сокете
|
|
86
|
-
_msg:"REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
87
|
-
_hash:self.store.RCHash,
|
|
88
|
-
_value:false, //статус авторизации (обязательно)
|
|
89
|
-
_banned: true //IP заблокирован (не обязательно)
|
|
90
|
-
}
|
|
91
|
-
Диспатчер{ //отправка экшена клиенту
|
|
92
|
-
_msg:"REDUX_CLUSTER_MSGTOWORKER",
|
|
93
|
-
_hash:self.store.RCHash,
|
|
94
|
-
_action:{ //диспатчер (обязательно)
|
|
95
|
-
type:"REDUX_CLUSTER_SYNC",
|
|
96
|
-
payload:self.store.getState()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
КлиентВСокет:
|
|
101
|
-
Авторизация{ //запрос авторизации в сокете
|
|
102
|
-
_msg:'REDUX_CLUSTER_SOCKET_AUTH',
|
|
103
|
-
_hash:self.store.RCHash,
|
|
104
|
-
_login:self.login, //логин для авторизации в сокете
|
|
105
|
-
_password:self.password //пароль для авторизации в сокете
|
|
106
|
-
}
|
|
107
|
-
Диспатчер{ //отправка экшена серверу
|
|
108
|
-
_msg:'REDUX_CLUSTER_MSGTOMASTER',
|
|
109
|
-
_hash:self.store.RCHash,
|
|
110
|
-
_action:_data //экшн для передачи в редьюсер сервера
|
|
111
|
-
}
|
|
112
|
-
Старт{ //отправка запроса на получения снимка хранилища
|
|
113
|
-
_msg:'REDUX_CLUSTER_START',
|
|
114
|
-
_hash:self.store.RCHash
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
*/
|
|
118
|
-
|
|
119
|
-
function ReduxCluster(_reducer){
|
|
120
|
-
let self = this;
|
|
121
|
-
self.stderr = console.error; //callback для ошибок
|
|
122
|
-
self.role = []; //роль
|
|
123
|
-
self.mode = "action"; //тип синхронизации по умолчанию
|
|
124
|
-
self.connected = false; //статус соединения
|
|
125
|
-
self.resync = 1000; //количество действий для пересинхронизации
|
|
126
|
-
self.RCHash = hasher(_reducer.name); //создаю метку текущего редьюсера для каждого экземпляра
|
|
127
|
-
self.version = require('./package.json').version; //версия пакета
|
|
128
|
-
self.homepage = require('./package.json').homepage; //домашняя страница пакета
|
|
129
|
-
self.altReducer = _reducer; //оригинальный редьюсер
|
|
130
|
-
self.allsock = {}; //сервера
|
|
131
|
-
if(typeof(reducers[_reducer.name]) === 'undefined'){
|
|
132
|
-
reducers[_reducer.name] = self.RCHash;
|
|
133
|
-
} else {
|
|
134
|
-
throw new Error("Please don't use a reducer with the same name!");
|
|
135
|
-
}
|
|
136
|
-
self.sendtoall = function(_message){ //отправка снимков во все воркеры
|
|
137
|
-
if(Cluster.isMaster){
|
|
138
|
-
if(typeof(_message) ==='object'){
|
|
139
|
-
for (const id in Cluster.workers) {
|
|
140
|
-
Cluster.workers[id].send(_message);
|
|
141
|
-
}
|
|
142
|
-
} else{
|
|
143
|
-
for (const id in Cluster.workers) {
|
|
144
|
-
Cluster.workers[id].send({_msg:"REDUX_CLUSTER_MSGTOWORKER", _hash:self.RCHash, _action:{type:"REDUX_CLUSTER_SYNC", payload:self.getState()}});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
self.sendtoallsock = function(_message){ //отправка сообщений во все сокеты
|
|
150
|
-
for(const id in self.allsock){
|
|
151
|
-
if((typeof(self.allsock[id]) === 'object') && (typeof(self.allsock[id].sendtoall) === 'function'))
|
|
152
|
-
setTimeout(self.allsock[id].sendtoall, 1, _message);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
try{
|
|
156
|
-
let _d = self.altReducer(undefined, {}); //получаю значение state при старте
|
|
157
|
-
if(typeof(_d) === 'object'){
|
|
158
|
-
self.defaulstate = _d;
|
|
159
|
-
} else {
|
|
160
|
-
throw new Error('The returned value is not an object.');
|
|
161
|
-
}
|
|
162
|
-
} catch(e){
|
|
163
|
-
self.defaulstate = {};
|
|
164
|
-
};
|
|
165
|
-
self.newReducer = function(state=self.defaulstate, action){ //собственный редьюсер
|
|
166
|
-
if(self.mode === "action"){ //в режиме action отправляем action в воркеры и сокеты
|
|
167
|
-
if((typeof(self.counter) === 'undefined') || (self.counter === self.resync))
|
|
168
|
-
self.counter = 1;
|
|
169
|
-
else
|
|
170
|
-
self.counter++;
|
|
171
|
-
if(self.counter === self.resync){ //каждый N-action синхронизируем хранилище (на случай рассинхронизации)
|
|
172
|
-
if(self.role.indexOf("master") !== -1)
|
|
173
|
-
setTimeout(self.sendtoall, 100);
|
|
174
|
-
if(self.role.indexOf("server") !== -1)
|
|
175
|
-
setTimeout(self.sendtoallsock, 100);
|
|
176
|
-
}
|
|
177
|
-
if(self.role.indexOf("master") !== -1)
|
|
178
|
-
setTimeout(self.sendtoall, 1, {_msg:"REDUX_CLUSTER_MSGTOWORKER", _hash:self.RCHash, _action:action});
|
|
179
|
-
if(self.role.indexOf("server") !== -1)
|
|
180
|
-
setTimeout(self.sendtoallsock, 1, {_msg:"REDUX_CLUSTER_MSGTOWORKER", _hash:self.RCHash, _action:action});
|
|
181
|
-
}
|
|
182
|
-
if (action.type === 'REDUX_CLUSTER_SYNC'){
|
|
183
|
-
let state_new = Lodash.clone(action.payload);
|
|
184
|
-
return state_new;
|
|
185
|
-
} else {
|
|
186
|
-
return self.altReducer(state, action);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
Object.assign(self, Redux.createStore(self.newReducer)); //создаю хранилище с собственным редьюсером
|
|
190
|
-
delete self.replaceReducer; //удаляю замену редьюсера
|
|
191
|
-
self.backup = function(object){
|
|
192
|
-
let _object = Lodash.clone(object);
|
|
193
|
-
return new Promise(function(resolve, reject){
|
|
194
|
-
loadBackup().then(function(val){
|
|
195
|
-
new createBackup();
|
|
196
|
-
resolve(true);
|
|
197
|
-
}).catch(function(err){
|
|
198
|
-
if(err && err.message.toLowerCase().indexOf("no such file or directory") !== -1){
|
|
199
|
-
new createBackup();
|
|
200
|
-
resolve(true);
|
|
201
|
-
} else {
|
|
202
|
-
reject(err);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
function loadBackup(){
|
|
207
|
-
return new Promise(function(res, rej){
|
|
208
|
-
Fs.readFile(_object["path"], function(_err,_data){
|
|
209
|
-
if(_err){
|
|
210
|
-
rej(new Error('ReduxCluster.backup load error: '+_err.message));
|
|
211
|
-
} else {
|
|
212
|
-
try{
|
|
213
|
-
let _string = _data.toString();
|
|
214
|
-
if(typeof(_object["key"]) !== 'undefined')
|
|
215
|
-
_string = decrypter(_string, _object["key"]);
|
|
216
|
-
let _obj = JSON.parse(_string);
|
|
217
|
-
if(typeof(self.dispatchNEW) === 'function')
|
|
218
|
-
self.dispatchNEW({type:"REDUX_CLUSTER_SYNC", payload:_obj});
|
|
219
|
-
else
|
|
220
|
-
self.dispatch({type:"REDUX_CLUSTER_SYNC", payload:_obj});
|
|
221
|
-
setTimeout(function(){
|
|
222
|
-
res(true);
|
|
223
|
-
}, 500);
|
|
224
|
-
} catch (_e) {
|
|
225
|
-
rej(new Error('ReduxCluster.backup decoding error: '+_e.message))
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
function createBackup(){
|
|
232
|
-
let _createBackup = this;
|
|
233
|
-
_createBackup.c = 0;
|
|
234
|
-
_createBackup.allowed = true;
|
|
235
|
-
_createBackup.disable = self.subscribe(function(){ //подписываю на обновление
|
|
236
|
-
if(typeof(_object['timeout']) === 'number'){ //приоритетная настройка
|
|
237
|
-
if(_createBackup.allowed){ //запись разрешена
|
|
238
|
-
_createBackup.allowed = false; //запрещаю повторный запуск
|
|
239
|
-
setTimeout(function(){
|
|
240
|
-
_createBackup.write(true);
|
|
241
|
-
},_object['timeout']*1000);
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
let _update = false;
|
|
245
|
-
if(typeof(_object['count']) === 'number'){ //проверяю счетчик
|
|
246
|
-
_createBackup.c++;
|
|
247
|
-
if(_createBackup.c === _object['count']){
|
|
248
|
-
_createBackup.c = 0;
|
|
249
|
-
}
|
|
250
|
-
if(_createBackup.c === 0){
|
|
251
|
-
_update = true;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if(_update){
|
|
255
|
-
_createBackup.write();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
_createBackup.write = function(_restart, _callback){ //запись в fs
|
|
260
|
-
if(_createBackup.allowed || _restart){
|
|
261
|
-
try{
|
|
262
|
-
let _string = JSON.stringify(self.getState());
|
|
263
|
-
if(typeof(_object['key']) !== 'undefined'){
|
|
264
|
-
try{
|
|
265
|
-
_string = encrypter(_string, _object['key']);
|
|
266
|
-
backupwrite();
|
|
267
|
-
} catch(_err){
|
|
268
|
-
self.stderr('ReduxCluster.backup encrypt error: '+_err);
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
backupwrite();
|
|
272
|
-
}
|
|
273
|
-
function backupwrite(){
|
|
274
|
-
let _resultBackup = Fs.writeFileSync(_object['path'], _string, (_err) => {
|
|
275
|
-
if (_err) {
|
|
276
|
-
self.stderr('ReduxCluster.backup write error: '+_err);
|
|
277
|
-
_createBackup.allowed = false;
|
|
278
|
-
setTimeout(_createBackup.write, 1000, true, _callback);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
if(typeof(_resultBackup) === 'undefined'){
|
|
282
|
-
_createBackup.allowed = true;
|
|
283
|
-
if(typeof(_callback) === 'function'){
|
|
284
|
-
_callback(true);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
} catch(_e){
|
|
289
|
-
self.stderr('ReduxCluster.backup write error: '+_e);
|
|
290
|
-
_createBackup.allowed = false;
|
|
291
|
-
setTimeout(_createBackup.write, 1000, true, _callback);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if(Cluster.isMaster){ //мастер
|
|
298
|
-
if(self.role.indexOf("master") === -1) { self.role.push("master"); }
|
|
299
|
-
self.unsubscribe = self.subscribe(function(){ //подписываю отправку снимков при изменении только в режиме snapshot
|
|
300
|
-
if(self.mode === "snapshot")
|
|
301
|
-
self.sendtoall();
|
|
302
|
-
});
|
|
303
|
-
Cluster.on('message', (worker, message, handle) => { //получение сообщения мастером
|
|
304
|
-
if (arguments.length === 2) { //поддержка старой версии (без идентификатора воркера)
|
|
305
|
-
handle = message;
|
|
306
|
-
message = worker;
|
|
307
|
-
worker = undefined;
|
|
308
|
-
}
|
|
309
|
-
if(message._hash === self.RCHash){
|
|
310
|
-
switch(message._msg){
|
|
311
|
-
case 'REDUX_CLUSTER_MSGTOMASTER': //получаю диспатчер от воркера
|
|
312
|
-
if(message._action.type === 'REDUX_CLUSTER_SYNC')
|
|
313
|
-
throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
|
|
314
|
-
self.dispatch(message._action);
|
|
315
|
-
break;
|
|
316
|
-
case 'REDUX_CLUSTER_START': //получаю метку, что воркер запущен
|
|
317
|
-
if(worker){
|
|
318
|
-
Cluster.workers[worker.id].send({_msg:"REDUX_CLUSTER_MSGTOWORKER", _hash:self.RCHash, _action:{type:"REDUX_CLUSTER_SYNC", payload:self.getState()}}); //в зависимости от наличия метки воркера, отправляю снимок ему, либо всем
|
|
319
|
-
} else {
|
|
320
|
-
self.sendtoall();
|
|
321
|
-
}
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
self.connected = true;
|
|
327
|
-
} else { //воркер
|
|
328
|
-
if(self.role.indexOf("worker") === -1) { self.role.push("worker"); }
|
|
329
|
-
self.dispatchNEW = self.dispatch; //переопределяю диспатчер
|
|
330
|
-
self.dispatch = function(_data){
|
|
331
|
-
process.send({_msg:'REDUX_CLUSTER_MSGTOMASTER', _hash:self.RCHash, _action:_data}); //вместо оригинального диспатчера подкладываю IPC
|
|
332
|
-
};
|
|
333
|
-
process.on("message", function(data){ //получение сообщения воркером
|
|
334
|
-
if((data._hash === self.RCHash) && (self.role.indexOf("worker") !== -1)){ //если роль worker не активна больше не слушаем default IPC
|
|
335
|
-
switch(data._msg){
|
|
336
|
-
case 'REDUX_CLUSTER_MSGTOWORKER': //получение снимка от мастера
|
|
337
|
-
self.dispatchNEW(data._action); //запускаю диспатчер Redux
|
|
338
|
-
break;
|
|
339
|
-
case 'REDUX_CLUSTER_CONNSTATUS':
|
|
340
|
-
self.connected = data._connected;
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
self.connected = true;
|
|
346
|
-
process.send({_msg:'REDUX_CLUSTER_START', _hash:self.RCHash}); //запрашиваю у мастера снимок хранилища
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function createStore(_reducer){ //функция создания хранилища
|
|
351
|
-
let _ReduxCluster = new ReduxCluster(_reducer); //создаю экземпляр хранилища
|
|
352
|
-
_ReduxCluster.createServer = function(_settings){ //подключаю объект создания сервера
|
|
353
|
-
if((!Cluster.isMaster) && (typeof(_settings.path) === 'string') && (Os.platform() === 'win32')){ //IPC в дочернем процессе кластера не работает в windows
|
|
354
|
-
throw new Error("Named channel is not supported in the child process, please use TCP-server");
|
|
355
|
-
}
|
|
356
|
-
if(_ReduxCluster.role.indexOf("server") === -1) { _ReduxCluster.role.push("server"); }
|
|
357
|
-
_ReduxCluster.connected = false;
|
|
358
|
-
return new createServer(_ReduxCluster, _settings);
|
|
359
|
-
}
|
|
360
|
-
_ReduxCluster.createClient = function(_settings){ //подключаю объект создания клиента
|
|
361
|
-
if(_ReduxCluster.role.indexOf("client") === -1) { _ReduxCluster.role.push("client"); } else {
|
|
362
|
-
throw new Error('One storage cannot be connected to two servers at the same time.');
|
|
363
|
-
}
|
|
364
|
-
if(_ReduxCluster.role.indexOf("worker") !== -1) { _ReduxCluster.role.splice(_ReduxCluster.role.indexOf("worker"), 1); } //удаляю роль воркера, т.к. IPC Master->Worker уничтожена (не обрабатывается)
|
|
365
|
-
_ReduxCluster.connected = false;
|
|
366
|
-
return new createClient(_ReduxCluster, _settings);
|
|
367
|
-
}
|
|
368
|
-
return _ReduxCluster;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function createServer(_store, _settings){ //объект создания сервера
|
|
372
|
-
let self = this;
|
|
373
|
-
self.uid = generateUID();
|
|
374
|
-
self.sockets = {};
|
|
375
|
-
self.database = {};
|
|
376
|
-
self.ip2ban = {};
|
|
377
|
-
self.ip2banTimeout = 10800000;
|
|
378
|
-
self.ip2banGCStart = setInterval(function(){
|
|
379
|
-
for(const key in self.ip2ban){
|
|
380
|
-
if((self.ip2ban[key].time+self.ip2banTimeout) < Date.now()){
|
|
381
|
-
delete self.ip2ban[key];
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}, 60000);
|
|
385
|
-
self.ip2banGCStop = function(){ clearInterval(self.ip2banGCStart); }
|
|
386
|
-
self.listen = {port:10001}; //дефолтные настройки
|
|
387
|
-
if(typeof(_settings) === 'object'){ //переопределяю конфиг
|
|
388
|
-
if(typeof(_settings.path) === 'string'){
|
|
389
|
-
switch(Os.platform ()){
|
|
390
|
-
case 'win32':
|
|
391
|
-
self.listen = {path:Path.join('\\\\?\\pipe', _settings.path)};
|
|
392
|
-
break;
|
|
393
|
-
default:
|
|
394
|
-
self.listen = {path:Path.join(_settings.path)};
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
} else{
|
|
398
|
-
if(typeof(_settings.host) === 'string')
|
|
399
|
-
self.listen.host = _settings.host;
|
|
400
|
-
if(typeof(_settings.port) === 'number')
|
|
401
|
-
self.listen.port = _settings.port;
|
|
402
|
-
}
|
|
403
|
-
if(typeof(_settings.logins) === 'object')
|
|
404
|
-
for(const login in _settings.logins){ self.database[hasher("REDUX_CLUSTER"+login)] = hasher("REDUX_CLUSTER"+_settings.logins[login]); }
|
|
405
|
-
}
|
|
406
|
-
self.sendtoall = function(_message){
|
|
407
|
-
if(typeof(_message) === 'object'){
|
|
408
|
-
for(const uid in self.sockets){
|
|
409
|
-
self.sockets[uid].write(_message);
|
|
410
|
-
}
|
|
411
|
-
} else {
|
|
412
|
-
for(const uid in self.sockets){
|
|
413
|
-
self.sockets[uid].write({_msg:"REDUX_CLUSTER_MSGTOWORKER", _hash:_store.RCHash, _action:{type:"REDUX_CLUSTER_SYNC", payload:_store.getState()}});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if(_store.mode === "action") //переопределяю функцию отправки в сокеты (вызывается редьюсером первичного мастера)
|
|
418
|
-
_store.allsock[self.uid] = self;
|
|
419
|
-
self.unsubscribe = _store.subscribe(function(){ //подписываю сокет на изменения Redux только в режиме snapshot
|
|
420
|
-
if(_store.mode === "snapshot")
|
|
421
|
-
self.sendtoall();
|
|
422
|
-
});
|
|
423
|
-
self.server = Net.createServer((socket) => {
|
|
424
|
-
let _i2bTest = replacer(socket.remoteAddress, true);
|
|
425
|
-
let _uid = generateUID();
|
|
426
|
-
socket.uid = _uid;
|
|
427
|
-
socket.writeNEW = socket.write; //переопределяю write (объектный режим + сжатие)
|
|
428
|
-
socket.write = function(_data){
|
|
429
|
-
try{
|
|
430
|
-
return socket.writeNEW(Zlib.gzipSync(Buffer.from(JSON.stringify(_data))));
|
|
431
|
-
} catch(err){
|
|
432
|
-
_store.stderr('ReduxCluster.createServer write error: '+err.message);
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
socket.on('error', function(err){ //обработка ошибок сокета
|
|
437
|
-
_store.stderr('ReduxCluster.createServer client error: '+err.message);
|
|
438
|
-
if(typeof(socket.end) === 'function'){
|
|
439
|
-
socket.end();
|
|
440
|
-
}
|
|
441
|
-
if((typeof(socket.uid) !== 'undefined') && (typeof(self.sockets[socket.uid]) !== 'undefined')){
|
|
442
|
-
delete self.sockets[socket.uid];
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
if((typeof(_i2bTest) === 'undefined') || (typeof(self.ip2ban[_i2bTest]) === 'undefined') || ((typeof(self.ip2ban[_i2bTest]) === 'object') && ((self.ip2ban[_i2bTest].count < 5) || ((self.ip2ban[_i2bTest].time+self.ip2banTimeout) < Date.now())))){
|
|
446
|
-
self.parser = new Objectstream.Parser();
|
|
447
|
-
self.mbstring = new Stream.Transform({ //обработка мультибайтовых символов без присвоенной кодировки может срабатывать некорректно
|
|
448
|
-
transform(_buffer, encoding, callback) {
|
|
449
|
-
this.push(_buffer)
|
|
450
|
-
return callback();
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
self.mbstring.setEncoding('utf8');
|
|
454
|
-
self.gunzipper = Zlib.createGunzip(); //поток декомпрессии
|
|
455
|
-
self.event = Eventstream.map(function (data, next1) {
|
|
456
|
-
if(data._hash === _store.RCHash){ //проверяю что сообщение привязано к текущему хранилищу
|
|
457
|
-
switch(data._msg){
|
|
458
|
-
case 'REDUX_CLUSTER_MSGTOMASTER': //получаю диспатчер от клиента
|
|
459
|
-
if((typeof(socket.uid) !== 'undefined') && (typeof(self.sockets[socket.uid]) !== 'undefined')){
|
|
460
|
-
if(data._action.type === 'REDUX_CLUSTER_SYNC')
|
|
461
|
-
throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
|
|
462
|
-
_store.dispatch(data._action);
|
|
463
|
-
}
|
|
464
|
-
break;
|
|
465
|
-
case 'REDUX_CLUSTER_START': //получаю метку, что клиент запущен
|
|
466
|
-
if((typeof(socket.uid) !== 'undefined') && (typeof(self.sockets[socket.uid]) !== 'undefined')){
|
|
467
|
-
self.sockets[socket.uid].write({_msg:"REDUX_CLUSTER_MSGTOWORKER", _hash:_store.RCHash, _action:{type:"REDUX_CLUSTER_SYNC", payload:_store.getState()}});
|
|
468
|
-
}
|
|
469
|
-
break;
|
|
470
|
-
case 'REDUX_CLUSTER_SOCKET_AUTH':
|
|
471
|
-
if( (typeof(data._login) !== 'undefined') &&
|
|
472
|
-
(typeof(data._password) !== 'undefined') &&
|
|
473
|
-
(typeof(self.database[data._login]) !== 'undefined') &&
|
|
474
|
-
(self.database[data._login] === data._password)){
|
|
475
|
-
self.sockets[socket.uid] = socket;
|
|
476
|
-
if((typeof(_i2bTest) === 'string') && (typeof(self.ip2ban[_i2bTest]) === 'object')) { delete self.ip2ban[_i2bTest]; } //если логин присутствует в таблице забаненных удаляю
|
|
477
|
-
socket.write({_msg:"REDUX_CLUSTER_SOCKET_AUTHSTATE", _hash:_store.RCHash, _value:true});
|
|
478
|
-
} else {
|
|
479
|
-
if(typeof(_i2bTest) === 'string') {
|
|
480
|
-
let _tempCount = 0;
|
|
481
|
-
if(typeof(self.ip2ban[_i2bTest]) === 'object'){
|
|
482
|
-
_tempCount = self.ip2ban[_i2bTest].count;
|
|
483
|
-
if(_tempCount >= 5) { _tempCount = 0; } //по таймауту сбрасываю счетчик попыток
|
|
484
|
-
}
|
|
485
|
-
self.ip2ban[_i2bTest] = {time: Date.now(), count:_tempCount+1};
|
|
486
|
-
}
|
|
487
|
-
socket.write({_msg:"REDUX_CLUSTER_SOCKET_AUTHSTATE", _hash:_store.RCHash, _value:false});
|
|
488
|
-
if(typeof(socket.end) === 'function'){
|
|
489
|
-
socket.end();
|
|
490
|
-
}
|
|
491
|
-
if((typeof(socket.uid) !== 'undefined') && (typeof(self.sockets[socket.uid]) !== 'undefined')){
|
|
492
|
-
delete self.sockets[socket.uid];
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
break;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
next1();
|
|
499
|
-
});
|
|
500
|
-
self.gunzipper.on('error',function(err){
|
|
501
|
-
_store.stderr('ReduxCluster.createServer gunzipper error: '+err);
|
|
502
|
-
});
|
|
503
|
-
self.mbstring.on('error',function(err){
|
|
504
|
-
_store.stderr('ReduxCluster.createServer mbstring error: '+err);
|
|
505
|
-
});
|
|
506
|
-
self.parser.on('error',function(err){
|
|
507
|
-
_store.stderr('ReduxCluster.createServer parser error: '+err);
|
|
508
|
-
});
|
|
509
|
-
self.event.on('error',function(err){
|
|
510
|
-
_store.stderr('ReduxCluster.createServer event error: '+err);
|
|
511
|
-
});
|
|
512
|
-
socket.pipe(self.gunzipper).pipe(self.mbstring).pipe(self.parser).pipe(self.event);
|
|
513
|
-
} else {
|
|
514
|
-
socket.write({_msg:"REDUX_CLUSTER_SOCKET_AUTHSTATE", _hash:_store.RCHash, _value:false, _banned: true});
|
|
515
|
-
if(typeof(socket.end) === 'function'){
|
|
516
|
-
socket.end();
|
|
517
|
-
}
|
|
518
|
-
if((typeof(socket.uid) !== 'undefined') && (typeof(self.sockets[socket.uid]) !== 'undefined')){
|
|
519
|
-
delete self.sockets[socket.uid];
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}).on('listening', function(){ //сервер слушает
|
|
523
|
-
_store.connected = true;
|
|
524
|
-
_store.sendtoall({_msg:"REDUX_CLUSTER_CONNSTATUS", _hash:_store.RCHash, _connected:true});
|
|
525
|
-
}).on('close', function(){ //все коннекты уничтожены
|
|
526
|
-
_store.connected = false;
|
|
527
|
-
_store.sendtoall({_msg:"REDUX_CLUSTER_CONNSTATUS", _hash:_store.RCHash, _connected:false});
|
|
528
|
-
self.unsubscribe();
|
|
529
|
-
self.ip2banGCStop();
|
|
530
|
-
delete _store.allsock[self.uid];
|
|
531
|
-
setTimeout(function(){ new createServer(_store, _settings) }, 10000);
|
|
532
|
-
}).on('error', function(err){ //обработка ошибок сервера
|
|
533
|
-
_store.stderr('ReduxCluster.createServer socket error: '+err.message);
|
|
534
|
-
if(typeof(self.server.close) === 'function')
|
|
535
|
-
self.server.close();
|
|
536
|
-
});
|
|
537
|
-
if(typeof(self.listen.path) === 'string'){
|
|
538
|
-
Fs.unlink(self.listen.path, function(err){
|
|
539
|
-
if(err && err.message.toLowerCase().indexOf("no such file or directory") === -1)
|
|
540
|
-
_store.stderr('ReduxCluster.createServer socket error: '+err);
|
|
541
|
-
self.server.listen(self.listen);
|
|
542
|
-
});
|
|
543
|
-
} else {
|
|
544
|
-
self.server.listen(self.listen);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function createClient(_store, _settings){ //объект создания клиента
|
|
549
|
-
let self = this;
|
|
550
|
-
self.listen = {port:10001}; //дефолтные настройки
|
|
551
|
-
if(typeof(_settings) === 'object'){ //переопределяю конфиг
|
|
552
|
-
if(typeof(_settings.path) === 'string'){
|
|
553
|
-
switch(Os.platform ()){
|
|
554
|
-
case 'win32':
|
|
555
|
-
self.listen = {path:Path.join('\\\\?\\pipe', _settings.path)};
|
|
556
|
-
break;
|
|
557
|
-
default:
|
|
558
|
-
self.listen = {path:Path.join(_settings.path)};
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
} else{
|
|
562
|
-
if(typeof(_settings.host) === 'string')
|
|
563
|
-
self.listen.host = _settings.host;
|
|
564
|
-
if(typeof(_settings.port) === 'number')
|
|
565
|
-
self.listen.port = _settings.port;
|
|
566
|
-
}
|
|
567
|
-
if(typeof(_settings.login) === 'string')
|
|
568
|
-
self.login = hasher("REDUX_CLUSTER"+_settings.login);
|
|
569
|
-
if(typeof(_settings.password) === 'string')
|
|
570
|
-
self.password = hasher("REDUX_CLUSTER"+_settings.password);
|
|
571
|
-
}
|
|
572
|
-
self.client = new Net.createConnection(self.listen);
|
|
573
|
-
self.parser = new Objectstream.Parser();
|
|
574
|
-
self.mbstring = new Stream.Transform({ //обработка мультибайтовых символов без присвоенной кодировки может срабатывать некорректно
|
|
575
|
-
transform(_buffer, encoding, callback) {
|
|
576
|
-
this.push(_buffer)
|
|
577
|
-
return callback();
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
self.mbstring.setEncoding('utf8');
|
|
581
|
-
self.gunzipper = Zlib.createGunzip(); //поток декомпрессии
|
|
582
|
-
self.event = Eventstream.map(function (data, next1) {
|
|
583
|
-
if(!self.client.destroyed){
|
|
584
|
-
if(data._hash === _store.RCHash){
|
|
585
|
-
switch(data._msg){
|
|
586
|
-
case 'REDUX_CLUSTER_MSGTOWORKER':
|
|
587
|
-
_store.dispatchNEW(data._action);
|
|
588
|
-
break;
|
|
589
|
-
case 'REDUX_CLUSTER_SOCKET_AUTHSTATE':
|
|
590
|
-
if(data._value === true){
|
|
591
|
-
self.client.write({_msg:'REDUX_CLUSTER_START', _hash:_store.RCHash}); //синхронизирую хранилище
|
|
592
|
-
}else{
|
|
593
|
-
if(data._banned)
|
|
594
|
-
self.client.destroy(new Error('your ip is locked for 3 hours'));
|
|
595
|
-
else
|
|
596
|
-
self.client.destroy(new Error('authorization failed'));
|
|
597
|
-
}
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
next1();
|
|
603
|
-
});
|
|
604
|
-
self.gunzipper.on('error',function(err){
|
|
605
|
-
_store.stderr('ReduxCluster.createClient gunzipper error: '+err);
|
|
606
|
-
});
|
|
607
|
-
self.mbstring.on('error',function(err){
|
|
608
|
-
_store.stderr('ReduxCluster.createClient mbstring error: '+err);
|
|
609
|
-
});
|
|
610
|
-
self.parser.on('error',function(err){
|
|
611
|
-
_store.stderr('ReduxCluster.createClient parser error: '+err);
|
|
612
|
-
});
|
|
613
|
-
self.event.on('error',function(err){
|
|
614
|
-
_store.stderr('ReduxCluster.createClient event error: '+err);
|
|
615
|
-
});
|
|
616
|
-
self.client.on('connect', function(){
|
|
617
|
-
_store.connected = true;
|
|
618
|
-
_store.sendtoall({_msg:"REDUX_CLUSTER_CONNSTATUS", _hash:_store.RCHash, _connected:true});
|
|
619
|
-
self.client.writeNEW = self.client.write; //переопределяю write (объектный режим + сжатие)
|
|
620
|
-
self.client.write = function(_data){
|
|
621
|
-
try {
|
|
622
|
-
return self.client.writeNEW(Zlib.gzipSync(Buffer.from(JSON.stringify(_data))));
|
|
623
|
-
} catch(err){
|
|
624
|
-
_store.stderr('ReduxCluster.createClient write error: '+err.message);
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
if(typeof(_store.dispatchNEW) !== 'function'){ //переопределяю dispatch для мастера
|
|
629
|
-
_store.dispatchNEW = _store.dispatch;
|
|
630
|
-
}
|
|
631
|
-
_store.dispatch = function(_data){
|
|
632
|
-
self.client.write({_msg:'REDUX_CLUSTER_MSGTOMASTER', _hash:_store.RCHash, _action:_data});
|
|
633
|
-
}
|
|
634
|
-
self.client.write({_msg:'REDUX_CLUSTER_SOCKET_AUTH', _hash:_store.RCHash, _login:self.login, _password:self.password}); //авторизация в сокете
|
|
635
|
-
}).on('close', function(){
|
|
636
|
-
_store.connected = false;
|
|
637
|
-
_store.sendtoall({_msg:"REDUX_CLUSTER_CONNSTATUS", _hash:_store.RCHash, _connected:false});
|
|
638
|
-
setTimeout(function(){ new createClient(_store, _settings) }, 250);
|
|
639
|
-
}).on('error', function(err){ //обработка ошибок клиента
|
|
640
|
-
_store.stderr('ReduxCluster.createClient client error: '+err.message);
|
|
641
|
-
}).pipe(self.gunzipper).pipe(self.mbstring).pipe(self.parser).pipe(self.event);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
//генерация uid
|
|
645
|
-
function generateUID() {
|
|
646
|
-
let d = new Date().getTime();
|
|
647
|
-
if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
|
|
648
|
-
d += performance.now();
|
|
649
|
-
}
|
|
650
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
651
|
-
let r = (d + Math.random() * 16) % 16 | 0;
|
|
652
|
-
d = Math.floor(d / 16);
|
|
653
|
-
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
//функция замены "." на "_" и обратно
|
|
658
|
-
function replacer(data_val, value_val){
|
|
659
|
-
if(typeof(data_val) === 'string'){
|
|
660
|
-
if(value_val){
|
|
661
|
-
return data_val.replace(/\./gi,"_");
|
|
662
|
-
} else {
|
|
663
|
-
return data_val.replace(/\_/gi,".");
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
ReduxClusterModule.createStore = createStore; //переопределяю функцию создания хранилища
|
|
669
|
-
ReduxClusterModule.functions = {
|
|
670
|
-
generateUID: generateUID,
|
|
671
|
-
replacer: replacer,
|
|
672
|
-
hasher: hasher,
|
|
673
|
-
encrypter: encrypter,
|
|
674
|
-
decrypter: decrypter
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
module.exports = ReduxClusterModule;
|
|
678
|
-
|