Работа над WebWorker и DbSearcher
This commit is contained in:
@@ -98,13 +98,10 @@ class WebSocketController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getConfig(req, ws) {
|
async getConfig(req, ws) {
|
||||||
if (Array.isArray(req.params)) {
|
const config = _.pick(this.config, this.config.webConfigParams);
|
||||||
const paramsSet = new Set(req.params);
|
config.dbConfig = await this.webWorker.dbConfig();
|
||||||
|
|
||||||
this.send(_.pick(this.config, this.config.webConfigParams.filter(x => paramsSet.has(x))), req, ws);
|
this.send(config, req, ws);
|
||||||
} else {
|
|
||||||
throw new Error('params is not an array');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkerState(req, ws) {
|
async getWorkerState(req, ws) {
|
||||||
|
|||||||
28
server/core/DbSearcher.js
Normal file
28
server/core/DbSearcher.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
class DbSearcher {
|
||||||
|
constructor(db) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query) {
|
||||||
|
const db = this.db;
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
if (query.author) {
|
||||||
|
//
|
||||||
|
} else {
|
||||||
|
result = await db.select({
|
||||||
|
table: 'author',
|
||||||
|
map: `(r) => ({id: r.id, author: r.author})`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.limit) {
|
||||||
|
result = result.slice(0, query.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DbSearcher;
|
||||||
240
server/core/WebSocketConnection.js
Normal file
240
server/core/WebSocketConnection.js
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
const isBrowser = (typeof window !== 'undefined');
|
||||||
|
|
||||||
|
const utils = {
|
||||||
|
sleep: (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanPeriod = 5*1000;//5 секунд
|
||||||
|
|
||||||
|
class WebSocketConnection {
|
||||||
|
//messageLifeTime в секундах (проверка каждый cleanPeriod интервал)
|
||||||
|
constructor(url, openTimeoutSecs = 10, messageLifeTimeSecs = 30, webSocketOptions = {}) {
|
||||||
|
this.WebSocket = (isBrowser ? WebSocket : require('ws'));
|
||||||
|
this.url = url;
|
||||||
|
this.webSocketOptions = webSocketOptions;
|
||||||
|
|
||||||
|
this.ws = null;
|
||||||
|
|
||||||
|
this.listeners = [];
|
||||||
|
this.messageQueue = [];
|
||||||
|
this.messageLifeTime = messageLifeTimeSecs*1000;
|
||||||
|
this.openTimeout = openTimeoutSecs*1000;
|
||||||
|
this.requestId = 0;
|
||||||
|
|
||||||
|
this.wsErrored = false;
|
||||||
|
this.closed = false;
|
||||||
|
|
||||||
|
this.connecting = false;
|
||||||
|
this.periodicClean();//no await
|
||||||
|
}
|
||||||
|
|
||||||
|
//рассылаем сообщение и удаляем те обработчики, которые его получили
|
||||||
|
emit(mes, isError) {
|
||||||
|
const len = this.listeners.length;
|
||||||
|
if (len > 0) {
|
||||||
|
let newListeners = [];
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
let emitted = false;
|
||||||
|
if (isError) {
|
||||||
|
listener.onError(mes);
|
||||||
|
emitted = true;
|
||||||
|
} else {
|
||||||
|
if ( (listener.requestId && mes.requestId && listener.requestId === mes.requestId) ||
|
||||||
|
(!listener.requestId && !mes.requestId) ) {
|
||||||
|
listener.onMessage(mes);
|
||||||
|
emitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emitted)
|
||||||
|
newListeners.push(listener);
|
||||||
|
}
|
||||||
|
this.listeners = newListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.listeners.length != len;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOpen() {
|
||||||
|
return (this.ws && this.ws.readyState == this.WebSocket.OPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessageQueue() {
|
||||||
|
let newMessageQueue = [];
|
||||||
|
for (const message of this.messageQueue) {
|
||||||
|
if (!this.emit(message.mes)) {
|
||||||
|
newMessageQueue.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageQueue = newMessageQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_open() {
|
||||||
|
return new Promise((resolve, reject) => { (async() => {
|
||||||
|
if (this.closed)
|
||||||
|
reject(new Error('Этот экземпляр класса уничтожен. Пожалуйста, создайте новый.'));
|
||||||
|
|
||||||
|
if (this.connecting) {
|
||||||
|
let i = this.openTimeout/100;
|
||||||
|
while (i-- > 0 && this.connecting) {
|
||||||
|
await utils.sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//проверим подключение, и если нет, то подключимся заново
|
||||||
|
if (this.isOpen) {
|
||||||
|
resolve(this.ws);
|
||||||
|
} else {
|
||||||
|
this.connecting = true;
|
||||||
|
this.terminate();
|
||||||
|
|
||||||
|
if (isBrowser) {
|
||||||
|
const protocol = (window.location.protocol == 'https:' ? 'wss:' : 'ws:');
|
||||||
|
const url = this.url || `${protocol}//${window.location.host}/ws`;
|
||||||
|
this.ws = new this.WebSocket(url);
|
||||||
|
} else {
|
||||||
|
this.ws = new this.WebSocket(this.url, this.webSocketOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onopen = () => {
|
||||||
|
this.connecting = false;
|
||||||
|
resolve(this.ws);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onmessage = (data) => {
|
||||||
|
try {
|
||||||
|
if (isBrowser)
|
||||||
|
data = data.data;
|
||||||
|
const mes = JSON.parse(data);
|
||||||
|
this.messageQueue.push({regTime: Date.now(), mes});
|
||||||
|
|
||||||
|
this.processMessageQueue();
|
||||||
|
} catch (e) {
|
||||||
|
this.emit(e.message, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onerror = (e) => {
|
||||||
|
this.emit(e.message, true);
|
||||||
|
reject(new Error(e.message));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onclose = (e) => {
|
||||||
|
this.emit(e.message, true);
|
||||||
|
reject(new Error(e.message));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isBrowser) {
|
||||||
|
this.ws.onopen = onopen;
|
||||||
|
this.ws.onmessage = onmessage;
|
||||||
|
this.ws.onerror = onerror;
|
||||||
|
this.ws.onclose = onclose;
|
||||||
|
} else {
|
||||||
|
this.ws.on('open', onopen);
|
||||||
|
this.ws.on('message', onmessage);
|
||||||
|
this.ws.on('error', onerror);
|
||||||
|
this.ws.on('close', onclose);
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.sleep(this.openTimeout);
|
||||||
|
reject(new Error('Соединение не удалось'));
|
||||||
|
}
|
||||||
|
})() });
|
||||||
|
}
|
||||||
|
|
||||||
|
//timeout в секундах (проверка каждый cleanPeriod интервал)
|
||||||
|
message(requestId, timeoutSecs = 4) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.listeners.push({
|
||||||
|
regTime: Date.now(),
|
||||||
|
requestId,
|
||||||
|
timeout: timeoutSecs*1000,
|
||||||
|
onMessage: (mes) => {
|
||||||
|
resolve(mes);
|
||||||
|
},
|
||||||
|
onError: (mes) => {
|
||||||
|
reject(new Error(mes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.processMessageQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(req, timeoutSecs = 4) {
|
||||||
|
await this._open();
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.requestId = (this.requestId < 1000000 ? this.requestId + 1 : 1);
|
||||||
|
const requestId = this.requestId;//реентерабельность!!!
|
||||||
|
|
||||||
|
this.ws.send(JSON.stringify(Object.assign({requestId}, req)));
|
||||||
|
|
||||||
|
let resp = {};
|
||||||
|
try {
|
||||||
|
resp = await this.message(requestId, timeoutSecs);
|
||||||
|
} catch(e) {
|
||||||
|
this.terminate();
|
||||||
|
throw new Error('WebSocket не отвечает');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp._rok) {
|
||||||
|
return requestId;
|
||||||
|
} else {
|
||||||
|
throw new Error('Запрос не принят сервером');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('WebSocket коннект закрыт');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate() {
|
||||||
|
if (this.ws) {
|
||||||
|
if (isBrowser) {
|
||||||
|
this.ws.close();
|
||||||
|
} else {
|
||||||
|
this.ws.terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.terminate();
|
||||||
|
this.closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async periodicClean() {
|
||||||
|
while (!this.closed) {
|
||||||
|
try {
|
||||||
|
const now = Date.now();
|
||||||
|
//чистка listeners
|
||||||
|
let newListeners = [];
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
if (now - listener.regTime < listener.timeout) {
|
||||||
|
newListeners.push(listener);
|
||||||
|
} else {
|
||||||
|
if (listener.onError)
|
||||||
|
listener.onError('Время ожидания ответа истекло');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.listeners = newListeners;
|
||||||
|
|
||||||
|
//чистка messageQueue
|
||||||
|
let newMessageQueue = [];
|
||||||
|
for (const message of this.messageQueue) {
|
||||||
|
if (now - message.regTime < this.messageLifeTime) {
|
||||||
|
newMessageQueue.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.messageQueue = newMessageQueue;
|
||||||
|
} catch(e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.sleep(cleanPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebSocketConnection;
|
||||||
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
|
|||||||
const WorkerState = require('./WorkerState');
|
const WorkerState = require('./WorkerState');
|
||||||
const { JembaDbThread } = require('jembadb');
|
const { JembaDbThread } = require('jembadb');
|
||||||
const DbCreator = require('./DbCreator');
|
const DbCreator = require('./DbCreator');
|
||||||
|
const DbSearcher = require('./DbSearcher');
|
||||||
|
|
||||||
const ayncExit = new (require('./AsyncExit'))();
|
const ayncExit = new (require('./AsyncExit'))();
|
||||||
const log = new (require('./AppLogger'))().log;//singleton
|
const log = new (require('./AppLogger'))().log;//singleton
|
||||||
@@ -32,6 +33,7 @@ class WebWorker {
|
|||||||
this.wState = this.workerState.getControl('server_state');
|
this.wState = this.workerState.getControl('server_state');
|
||||||
this.myState = '';
|
this.myState = '';
|
||||||
this.db = null;
|
this.db = null;
|
||||||
|
this.dbSearcher = null;
|
||||||
|
|
||||||
ayncExit.add(this.closeDb.bind(this));
|
ayncExit.add(this.closeDb.bind(this));
|
||||||
|
|
||||||
@@ -125,8 +127,8 @@ class WebWorker {
|
|||||||
this.setMyState(ssDbLoading);
|
this.setMyState(ssDbLoading);
|
||||||
log('Searcher DB open');
|
log('Searcher DB open');
|
||||||
|
|
||||||
this.db = new JembaDbThread();
|
const db = new JembaDbThread();
|
||||||
await this.db.lock({
|
await db.lock({
|
||||||
dbPath,
|
dbPath,
|
||||||
softLock: true,
|
softLock: true,
|
||||||
|
|
||||||
@@ -136,8 +138,16 @@ class WebWorker {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//открываем все таблицы
|
//открываем все таблицы
|
||||||
await this.db.openAll();
|
await db.openAll();
|
||||||
await this.db.close({table: 'title'});
|
|
||||||
|
//закроем title для экономии памяти, откроем при необходимости
|
||||||
|
await db.close({table: 'title'});
|
||||||
|
this.titleOpen = false;
|
||||||
|
|
||||||
|
this.dbSearcher = new DbSearcher(db);
|
||||||
|
|
||||||
|
db.wwCache = {};
|
||||||
|
this.db = db;
|
||||||
|
|
||||||
log('Searcher DB is ready');
|
log('Searcher DB is ready');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -148,6 +158,24 @@ class WebWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async dbConfig() {
|
||||||
|
this.checkMyState();
|
||||||
|
|
||||||
|
const db = this.db;
|
||||||
|
if (!db.wwCache.config) {
|
||||||
|
const rows = await db.select({table: 'config'});
|
||||||
|
const config = {};
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
config[row.id] = row.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.wwCache.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.wwCache.config;
|
||||||
|
}
|
||||||
|
|
||||||
async logServerStats() {
|
async logServerStats() {
|
||||||
while (1) {// eslint-disable-line
|
while (1) {// eslint-disable-line
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user