lupine.api 1.1.51 → 1.1.53
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/admin/admin-release.tsx +47 -2
- package/package.json +1 -1
- package/src/admin-api/admin-auth.ts +15 -14
- package/src/admin-api/admin-release.ts +58 -0
- package/src/api/debug-service.ts +1 -1
- package/src/api/static-server.ts +1 -1
- package/src/app/app-loader.ts +1 -1
- package/src/app/app-message.ts +25 -8
- package/src/app/app-shared-storage.ts +4 -4
- package/src/app/app-start.ts +32 -13
- package/src/app/cleanup-exit.ts +4 -0
- package/src/app/process-dev-requests.ts +44 -15
- package/src/app/web-server.ts +4 -2
package/admin/admin-release.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
NotificationMessage,
|
|
9
9
|
formatBytes,
|
|
10
10
|
downloadStream,
|
|
11
|
+
ActionSheetSelectPromise,
|
|
11
12
|
} from 'lupine.components';
|
|
12
13
|
|
|
13
14
|
interface ReleaseListProps {
|
|
@@ -284,7 +285,7 @@ export const AdminReleasePage = () => {
|
|
|
284
285
|
isLocal,
|
|
285
286
|
});
|
|
286
287
|
const dataResponse = await response.json;
|
|
287
|
-
console.log('
|
|
288
|
+
console.log('refresh-cache', dataResponse);
|
|
288
289
|
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
289
290
|
NotificationMessage.sendMessage(dataResponse.message || 'Failed to refresh cache', NotificationColor.Error);
|
|
290
291
|
return;
|
|
@@ -293,6 +294,44 @@ export const AdminReleasePage = () => {
|
|
|
293
294
|
NotificationMessage.sendMessage('Cache refreshed successfully', NotificationColor.Success);
|
|
294
295
|
};
|
|
295
296
|
|
|
297
|
+
const onRestartAppLocal = async () => {
|
|
298
|
+
return onRestartApp(true);
|
|
299
|
+
};
|
|
300
|
+
const onRestartAppRemote = async () => {
|
|
301
|
+
return onRestartApp(false);
|
|
302
|
+
};
|
|
303
|
+
const onRestartApp = async (isLocal?: boolean) => {
|
|
304
|
+
const data = getDomData();
|
|
305
|
+
if (!isLocal) {
|
|
306
|
+
if (!data.targetUrl || !data.accessToken) {
|
|
307
|
+
NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const index = await ActionSheetSelectPromise({
|
|
313
|
+
title: 'Restart App (users may get disconnected errors) ?',
|
|
314
|
+
options: ['OK'],
|
|
315
|
+
cancelButtonText: 'Cancel',
|
|
316
|
+
});
|
|
317
|
+
if (index !== 0) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/restart-app', {
|
|
322
|
+
...data,
|
|
323
|
+
isLocal,
|
|
324
|
+
});
|
|
325
|
+
const dataResponse = await response.json;
|
|
326
|
+
console.log('restart-app', dataResponse);
|
|
327
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
328
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to Restart App', NotificationColor.Error);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
|
|
332
|
+
NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
|
|
333
|
+
};
|
|
334
|
+
|
|
296
335
|
const ref: RefProps = {
|
|
297
336
|
onLoad: async () => {
|
|
298
337
|
const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
|
|
@@ -321,9 +360,15 @@ export const AdminReleasePage = () => {
|
|
|
321
360
|
<button onClick={onRefreshCacheRemote} class='button-base mr-m'>
|
|
322
361
|
Refresh Cache (Remote)
|
|
323
362
|
</button>
|
|
324
|
-
<button onClick={
|
|
363
|
+
<button onClick={onRestartAppRemote} class='button-base mr-m color-red'>
|
|
364
|
+
Restart App (Remote)
|
|
365
|
+
</button>
|
|
366
|
+
<button onClick={onRefreshCacheLocal} class='button-base mr-m'>
|
|
325
367
|
Refresh Cache (Local)
|
|
326
368
|
</button>
|
|
369
|
+
<button onClick={onRestartAppLocal} class='button-base color-red'>
|
|
370
|
+
Restart App (Local)
|
|
371
|
+
</button>
|
|
327
372
|
</div>
|
|
328
373
|
{domUpdate.node}
|
|
329
374
|
{domLog.node}
|
package/package.json
CHANGED
|
@@ -69,14 +69,15 @@ export const devAdminAuth = async (req: ServerRequest, res: ServerResponse) => {
|
|
|
69
69
|
ApiHelper.sendJson(req, res, response);
|
|
70
70
|
return true;
|
|
71
71
|
}
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
// on set app cookie when login, not every time
|
|
73
|
+
// // if it's dev admin, then set app admin cookie as well
|
|
74
|
+
// let addLoginResponse = {};
|
|
75
|
+
// const appAdminHookSetCookie = adminApiHelper.getAppAdminHookSetCookie();
|
|
76
|
+
// if (appAdminHookSetCookie) {
|
|
77
|
+
// addLoginResponse = await appAdminHookSetCookie(req, res, devAdminSession.u);
|
|
78
|
+
// }
|
|
78
79
|
const response = {
|
|
79
|
-
...addLoginResponse,
|
|
80
|
+
// ...addLoginResponse,
|
|
80
81
|
status: 'ok',
|
|
81
82
|
message: langHelper.getLang('shared:login_success'),
|
|
82
83
|
devLogin: CryptoUtils.encrypt(JSON.stringify(devAdminSession), cryptoKey),
|
|
@@ -109,13 +110,13 @@ export const devAdminAuth = async (req: ServerRequest, res: ServerResponse) => {
|
|
|
109
110
|
message: langHelper.getLang('shared:login_success'),
|
|
110
111
|
devLogin: tokenCookie,
|
|
111
112
|
};
|
|
112
|
-
req.locals.setCookie('_token', tokenCookie, {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
113
|
+
// req.locals.setCookie('_token', tokenCookie, {
|
|
114
|
+
// expireDays: 360,
|
|
115
|
+
// path: '/',
|
|
116
|
+
// httpOnly: false,
|
|
117
|
+
// secure: true,
|
|
118
|
+
// sameSite: 'none',
|
|
119
|
+
// });
|
|
119
120
|
ApiHelper.sendJson(req, res, response);
|
|
120
121
|
return true;
|
|
121
122
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
adminApiHelper,
|
|
12
12
|
processRefreshCache,
|
|
13
13
|
apiStorage,
|
|
14
|
+
processRestartApp,
|
|
14
15
|
} from 'lupine.api';
|
|
15
16
|
import path from 'path';
|
|
16
17
|
import { needDevAdminSession } from './admin-auth';
|
|
@@ -36,11 +37,13 @@ export class AdminRelease implements IApiBase {
|
|
|
36
37
|
this.router.use('/view-log', needDevAdminSession, this.viewLog.bind(this));
|
|
37
38
|
// called online or by clients
|
|
38
39
|
this.router.use('/refresh-cache', needDevAdminSession, this.refreshCache.bind(this));
|
|
40
|
+
this.router.use('/restart-app', needDevAdminSession, this.restartApp.bind(this));
|
|
39
41
|
|
|
40
42
|
// ...ByClient will verify credentials from post, so it doesn't need AdminSession
|
|
41
43
|
this.router.use('/byClientCheck', this.byClientCheck.bind(this));
|
|
42
44
|
this.router.use('/byClientUpdate', this.byClientUpdate.bind(this));
|
|
43
45
|
this.router.use('/byClientRefreshCache', this.byClientRefreshCache.bind(this));
|
|
46
|
+
this.router.use('/byClientRestartApp', this.byClientRestartApp.bind(this));
|
|
44
47
|
this.router.use('/byClientViewLog', this.byClientViewLog.bind(this));
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -126,6 +129,47 @@ export class AdminRelease implements IApiBase {
|
|
|
126
129
|
return true;
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
async restartApp(req: ServerRequest, res: ServerResponse) {
|
|
133
|
+
// check whether it's from online admin
|
|
134
|
+
const json = await adminApiHelper.getDevAdminFromCookie(req, res, false);
|
|
135
|
+
const jsonData = req.locals.json();
|
|
136
|
+
if (json && jsonData && !Array.isArray(jsonData) && jsonData.isLocal) {
|
|
137
|
+
await processRestartApp(req);
|
|
138
|
+
const response = {
|
|
139
|
+
status: 'ok',
|
|
140
|
+
message: 'Restart app successfully.',
|
|
141
|
+
};
|
|
142
|
+
ApiHelper.sendJson(req, res, response);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
147
|
+
if (!data) return true;
|
|
148
|
+
|
|
149
|
+
let targetUrl = data.targetUrl as string;
|
|
150
|
+
if (targetUrl.endsWith('/')) {
|
|
151
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
152
|
+
}
|
|
153
|
+
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientRestartApp', {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
body: JSON.stringify(data),
|
|
156
|
+
});
|
|
157
|
+
const resultText = await remoteData.text();
|
|
158
|
+
let remoteResult: any;
|
|
159
|
+
try {
|
|
160
|
+
remoteResult = JSON.parse(resultText);
|
|
161
|
+
} catch (e: any) {
|
|
162
|
+
remoteResult = { status: 'error', message: resultText };
|
|
163
|
+
}
|
|
164
|
+
const response = {
|
|
165
|
+
status: 'ok',
|
|
166
|
+
message: 'check.',
|
|
167
|
+
...remoteResult,
|
|
168
|
+
};
|
|
169
|
+
ApiHelper.sendJson(req, res, response);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
129
173
|
public async chkData(data: any, req: ServerRequest, res: ServerResponse, chkCredential: boolean) {
|
|
130
174
|
// add access token
|
|
131
175
|
if (!data || Array.isArray(data) || typeof data !== 'object' || !data.accessToken || !data.targetUrl) {
|
|
@@ -569,4 +613,18 @@ export class AdminRelease implements IApiBase {
|
|
|
569
613
|
ApiHelper.sendJson(req, res, response);
|
|
570
614
|
return true;
|
|
571
615
|
}
|
|
616
|
+
|
|
617
|
+
async byClientRestartApp(req: ServerRequest, res: ServerResponse) {
|
|
618
|
+
const jsonData = req.locals.json();
|
|
619
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
620
|
+
if (!data) return true;
|
|
621
|
+
|
|
622
|
+
await processRestartApp(req);
|
|
623
|
+
const response = {
|
|
624
|
+
status: 'ok',
|
|
625
|
+
message: 'Restart app successfully.',
|
|
626
|
+
};
|
|
627
|
+
ApiHelper.sendJson(req, res, response);
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
572
630
|
}
|
package/src/api/debug-service.ts
CHANGED
|
@@ -48,7 +48,7 @@ export class DebugService {
|
|
|
48
48
|
|
|
49
49
|
// broadcast to all frontend clients
|
|
50
50
|
public static broadcastRefresh() {
|
|
51
|
-
console.log(
|
|
51
|
+
console.log(`${process.pid} - broadcast refresh request to clients.`);
|
|
52
52
|
this.clientRefreshFlag = Date.now();
|
|
53
53
|
const msg = { message: 'Refresh', flag: this.clientRefreshFlag };
|
|
54
54
|
this.miniWebSocket.broadcast(JSON.stringify(msg));
|
package/src/api/static-server.ts
CHANGED
|
@@ -71,7 +71,7 @@ export class StaticServer {
|
|
|
71
71
|
|
|
72
72
|
// if fullPath doesn't exist, it will throw ENOENT error
|
|
73
73
|
const realPath = await fs.promises.realpath(fullPath);
|
|
74
|
-
console.log(
|
|
74
|
+
console.log(`${process.pid} - request: ${realPath}`);
|
|
75
75
|
// for security reason, the requested file should be inside of wwwRoot
|
|
76
76
|
if (realPath.substring(0, hostPath.webPath.length) !== hostPath.webPath) {
|
|
77
77
|
this.logger.warn(`ACCESS DENIED: ${urlSplit[0]}`);
|
package/src/app/app-loader.ts
CHANGED
|
@@ -51,7 +51,7 @@ class AppLoader {
|
|
|
51
51
|
const apiPath = path.join(process.cwd(), 'dist/server_root', appConfig.appName + '_api/index.js');
|
|
52
52
|
for (const path in require.cache) {
|
|
53
53
|
if (path.endsWith('.js') && path.indexOf(apiPath) <= 0) {
|
|
54
|
-
console.log(
|
|
54
|
+
console.log(`${process.pid} - clear cache: ${path}`);
|
|
55
55
|
delete require.cache[path];
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/app/app-message.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import cluster from 'cluster';
|
|
2
2
|
import { Logger, LogWriter, LogWriterMessageId } from '../lib';
|
|
3
|
-
import { processDebugMessage } from './process-dev-requests';
|
|
3
|
+
import { processDebugMessage, snedRestartAppMsgToLoader } from './process-dev-requests';
|
|
4
4
|
import { cleanupAndExit } from './cleanup-exit';
|
|
5
5
|
|
|
6
6
|
export type AppMessageProps = {
|
|
@@ -78,15 +78,32 @@ export const processMessageFromWorker = (msgObject: AppMessageProps) => {
|
|
|
78
78
|
logger.debug(
|
|
79
79
|
`Message from worker ${cluster.worker?.id}, message: ${msgObject.message}, appName: ${msgObject.appName}`
|
|
80
80
|
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
if (msgObject.message === 'restartApp') {
|
|
82
|
+
snedRestartAppMsgToLoader();
|
|
83
|
+
return;
|
|
84
|
+
} else if (msgObject.message === 'refresh') {
|
|
85
|
+
broadcast(msgObject);
|
|
86
|
+
} else if (msgObject.message === 'shutdown') {
|
|
87
|
+
broadcast(msgObject);
|
|
88
|
+
// if it's shutdown, the primary process will exit
|
|
89
|
+
setTimeout(async () => {
|
|
90
|
+
console.log(`[server primary] Received shutdown command.`, cluster.workers);
|
|
91
|
+
await cleanupAndExit();
|
|
87
92
|
}, 100);
|
|
93
|
+
} else {
|
|
94
|
+
logger.warn(`Unknown message: ${msgObject.id}`);
|
|
88
95
|
}
|
|
89
96
|
} else {
|
|
90
|
-
logger.warn(`Unknown message: ${msgObject.id}`);
|
|
97
|
+
logger.warn(`Unknown message id: ${msgObject.id}`);
|
|
91
98
|
}
|
|
92
99
|
};
|
|
100
|
+
|
|
101
|
+
// this is primary, and receive messages from loader
|
|
102
|
+
export const receiveMessageFromLoader = () => {
|
|
103
|
+
process.on('message', async (msg: AppMessageProps) => {
|
|
104
|
+
if (msg?.id === 'debug' && msg?.message === 'shutdown') {
|
|
105
|
+
console.log(`App ${process.pid}: received shutdown message from loader.`);
|
|
106
|
+
processMessageFromWorker(msg);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
@@ -166,7 +166,7 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
166
166
|
// this can be called in primary or worker
|
|
167
167
|
get(appName: string, key: string): Promise<string> {
|
|
168
168
|
return new Promise((resolve, reject) => {
|
|
169
|
-
console.log(
|
|
169
|
+
console.log(`${process.pid} - AppStorage get value for key: ${key}`);
|
|
170
170
|
|
|
171
171
|
if (!cluster.isPrimary) {
|
|
172
172
|
AppSharedStorageWorker.get(appName, key, resolve, reject);
|
|
@@ -196,7 +196,7 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
196
196
|
}
|
|
197
197
|
getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
|
|
198
198
|
return new Promise((resolve, reject) => {
|
|
199
|
-
console.log(
|
|
199
|
+
console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
|
|
200
200
|
|
|
201
201
|
if (!cluster.isPrimary) {
|
|
202
202
|
AppSharedStorageWorker.getWithPrefix(appName, prefixKey, resolve, reject);
|
|
@@ -238,7 +238,7 @@ class AppSharedStorageWorker {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
if (msgObject.action === 'get') {
|
|
241
|
-
console.log(
|
|
241
|
+
console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
242
242
|
|
|
243
243
|
const value = msgObject.value;
|
|
244
244
|
// how to pass the value to the caller
|
|
@@ -250,7 +250,7 @@ class AppSharedStorageWorker {
|
|
|
250
250
|
throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
|
|
251
251
|
}
|
|
252
252
|
} else if (msgObject.action === 'getWithPrefix') {
|
|
253
|
-
console.log(
|
|
253
|
+
console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
254
254
|
const value = JSON.parse(msgObject.value);
|
|
255
255
|
// how to pass the value to the caller
|
|
256
256
|
const map = this.handleMap[msgObject.uniqueKey];
|
package/src/app/app-start.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { appCache } from './app-cache';
|
|
|
8
8
|
import { AppStartProps, InitStartProps, AppCacheGlobal, AppCacheKeys } from '../models';
|
|
9
9
|
import { appStorage } from './app-shared-storage';
|
|
10
10
|
import { HostToPath } from './host-to-path';
|
|
11
|
-
import { cleanupAndExit } from './cleanup-exit';
|
|
11
|
+
import { _exitApp, cleanupAndExit } from './cleanup-exit';
|
|
12
|
+
import { receiveMessageFromLoader } from './app-message';
|
|
12
13
|
|
|
13
14
|
// Don't use logger before set process message
|
|
14
15
|
class AppStart {
|
|
@@ -16,10 +17,16 @@ class AppStart {
|
|
|
16
17
|
webServer: WebServer | undefined;
|
|
17
18
|
|
|
18
19
|
getWorkerId() {
|
|
19
|
-
return cluster.worker ? cluster.worker.id :
|
|
20
|
+
return cluster.worker ? cluster.worker.id : -1;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
async start(props: AppStartProps, webServer?: WebServer) {
|
|
24
|
+
// if it's started from spawn, wait for old master to clear ports
|
|
25
|
+
if (cluster.isPrimary && process.env.RESTARTING === '1') {
|
|
26
|
+
console.log(`New app ${process.pid} RESTARTING.`);
|
|
27
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
this.debug = props.debug;
|
|
24
31
|
this.bindProcess();
|
|
25
32
|
|
|
@@ -34,9 +41,9 @@ class AppStart {
|
|
|
34
41
|
|
|
35
42
|
// call the Logger after initLog
|
|
36
43
|
console.log(
|
|
37
|
-
`${
|
|
38
|
-
|
|
39
|
-
}, path: ${process.cwd()}`
|
|
44
|
+
`${process.pid} - ${
|
|
45
|
+
cluster.isPrimary ? 'Primary Process' : 'Worker Process'
|
|
46
|
+
}, Starting Server, path: ${process.cwd()}`
|
|
40
47
|
);
|
|
41
48
|
|
|
42
49
|
// when it's cluster.isPrimary or props.debug, initialize the shared storage first
|
|
@@ -47,7 +54,7 @@ class AppStart {
|
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
if (!cluster.isPrimary) {
|
|
50
|
-
console.log(
|
|
57
|
+
console.log(`${process.pid} - Worker id ${this.getWorkerId()}`);
|
|
51
58
|
|
|
52
59
|
process.on('message', processMessageFromPrimary);
|
|
53
60
|
|
|
@@ -56,7 +63,9 @@ class AppStart {
|
|
|
56
63
|
this.initServer(props.serverConfig);
|
|
57
64
|
} else if (cluster.isPrimary) {
|
|
58
65
|
const numCPUs = props.debug ? 1 : require('os').cpus().length;
|
|
59
|
-
console.log(
|
|
66
|
+
console.log(`${process.pid} - Primary Process is trying to fork ${numCPUs} processes`);
|
|
67
|
+
|
|
68
|
+
receiveMessageFromLoader();
|
|
60
69
|
|
|
61
70
|
for (let i = 0; i < numCPUs; i++) {
|
|
62
71
|
let worker = cluster.fork();
|
|
@@ -64,8 +73,12 @@ class AppStart {
|
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
cluster.on('death', (worker: any) => {
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
if (!_exitApp.isExiting) {
|
|
77
|
+
console.log(`${worker.pid} - Worker died; starting a new one...`);
|
|
78
|
+
cluster.fork();
|
|
79
|
+
} else {
|
|
80
|
+
console.log(`${worker.pid} - Worker exited during restart`);
|
|
81
|
+
}
|
|
69
82
|
});
|
|
70
83
|
}
|
|
71
84
|
}
|
|
@@ -82,7 +95,7 @@ class AppStart {
|
|
|
82
95
|
|
|
83
96
|
// do something when app is closing
|
|
84
97
|
process.on('beforeExit', async () => {
|
|
85
|
-
cleanupAndExit();
|
|
98
|
+
await cleanupAndExit();
|
|
86
99
|
});
|
|
87
100
|
process.on('exit', (ret) => {
|
|
88
101
|
console.log(`${process.pid} - Process on exit, code: ${ret}`);
|
|
@@ -103,14 +116,20 @@ class AppStart {
|
|
|
103
116
|
const sslKeyPath = config.sslKeyPath || '';
|
|
104
117
|
const sslCrtPath = config.sslCrtPath || '';
|
|
105
118
|
|
|
106
|
-
console.log(
|
|
119
|
+
console.log(`${process.pid} - Starting Web Server, httpPort: ${httpPort}, httpsPort: ${httpsPort}`);
|
|
107
120
|
// for dev to refresh the FE or stop the server
|
|
108
121
|
if (this.debug) {
|
|
109
122
|
WebProcessor.enableDebug('/debug', processDevRequests);
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
httpPort && this.webServer!.startHttp(httpPort, bindIp);
|
|
113
|
-
httpsPort && this.webServer!.startHttps(httpsPort, bindIp, sslKeyPath, sslCrtPath);
|
|
125
|
+
const httpServer = httpPort && this.webServer!.startHttp(httpPort, bindIp);
|
|
126
|
+
const heepsServer = httpsPort && this.webServer!.startHttps(httpsPort, bindIp, sslKeyPath, sslCrtPath);
|
|
127
|
+
|
|
128
|
+
process.on('SIGTERM', () => {
|
|
129
|
+
console.log(`${process.pid} - Worker closing servers...`);
|
|
130
|
+
httpServer && httpServer.close();
|
|
131
|
+
heepsServer && heepsServer.close();
|
|
132
|
+
});
|
|
114
133
|
}
|
|
115
134
|
}
|
|
116
135
|
|
package/src/app/cleanup-exit.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import cluster from 'cluster';
|
|
2
2
|
import { appStorage } from './app-shared-storage';
|
|
3
3
|
|
|
4
|
+
export const _exitApp = {
|
|
5
|
+
isExiting: false,
|
|
6
|
+
};
|
|
4
7
|
export const cleanupAndExit = async () => {
|
|
5
8
|
console.log(`${process.pid} - Process on SIGINT, exit.`);
|
|
6
9
|
// save shared storage first
|
|
@@ -8,5 +11,6 @@ export const cleanupAndExit = async () => {
|
|
|
8
11
|
// save only happens once
|
|
9
12
|
await appStorage.save('', true);
|
|
10
13
|
}
|
|
14
|
+
_exitApp.isExiting = true;
|
|
11
15
|
process.exit(0);
|
|
12
16
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import cluster from 'cluster';
|
|
1
2
|
import { Logger } from '../lib/logger';
|
|
2
3
|
import { ServerResponse } from 'http';
|
|
3
4
|
import { AddressInfo } from 'net';
|
|
@@ -42,20 +43,20 @@ export const processDebugMessage = async (msgObject: any) => {
|
|
|
42
43
|
// this only works in debug mode (no clusters)
|
|
43
44
|
DebugService.broadcastRefresh();
|
|
44
45
|
}
|
|
45
|
-
if (msgObject.id === 'debug' && msgObject.message === '
|
|
46
|
-
// Only when it's debug mode, it can go here, otherwise
|
|
47
|
-
console.log(
|
|
48
|
-
cleanupAndExit();
|
|
46
|
+
if (msgObject.id === 'debug' && msgObject.message === 'shutdown') {
|
|
47
|
+
// Only when it's debug mode, it can go here, otherwise shutdown should be processed in processMessageFromWorker
|
|
48
|
+
console.log(`${process.pid} - [server] Received shutdown command.`);
|
|
49
|
+
await cleanupAndExit();
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
export async function processRefreshCache(req: ServerRequest) {
|
|
53
|
-
// if this is a child process, we need to notice parent process to broadcast to all clients
|
|
54
|
-
if (process.send) {
|
|
54
|
+
// if this is a child process, we need to notice parent process to broadcast to all clients
|
|
55
|
+
if (!cluster.isPrimary && process.send) {
|
|
55
56
|
const appName = req.locals.query.get('appName');
|
|
56
57
|
process.send({ id: 'debug', message: 'refresh', appName });
|
|
57
58
|
}
|
|
58
|
-
// if it's
|
|
59
|
+
// in case if it's only one process (primary process)
|
|
59
60
|
else {
|
|
60
61
|
// if (getAppCache().get(APP_GLOBAL, AppCacheKeys.API_DEBUG) === true)
|
|
61
62
|
const appName = req.locals.query.get('appName');
|
|
@@ -63,28 +64,56 @@ export async function processRefreshCache(req: ServerRequest) {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
export async function processRestartApp(req: ServerRequest) {
|
|
68
|
+
// if this is a child process, we need to notice parent process to broadcast to all clients
|
|
69
|
+
if (!cluster.isPrimary && process.send) {
|
|
70
|
+
// send message to Primary to handle it
|
|
71
|
+
process.send({ id: 'debug', message: 'restartApp' });
|
|
72
|
+
}
|
|
73
|
+
// in case if it's only one process (primary process)
|
|
74
|
+
else {
|
|
75
|
+
snedRestartAppMsgToLoader();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// this is called from a request in debug mode
|
|
67
80
|
export async function processDevRequests(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
|
|
68
81
|
res.end();
|
|
69
82
|
const address = req.socket.address() as AddressInfo;
|
|
70
83
|
if (address.address !== '127.0.0.1') {
|
|
71
|
-
console.log(
|
|
84
|
+
console.log(`${process.pid} - [server] Ignore request from: `, req.url, address.address);
|
|
72
85
|
return true;
|
|
73
86
|
}
|
|
74
|
-
if (req.url === '/debug/
|
|
75
|
-
console.log(
|
|
87
|
+
if (req.url === '/debug/shutdown') {
|
|
88
|
+
console.log(`${process.pid} - [server] Received shutdown command.`);
|
|
76
89
|
if (process.send) {
|
|
77
90
|
// send to parent process to kill all
|
|
78
|
-
process.send({ id: 'debug', message: '
|
|
91
|
+
process.send({ id: 'debug', message: 'shutdown' });
|
|
79
92
|
}
|
|
80
93
|
// if it's debug mode (only one process)
|
|
81
94
|
else if (getAppCache().get(AppCacheGlobal, AppCacheKeys.APP_DEBUG) === true) {
|
|
82
|
-
await processDebugMessage({ id: 'debug', message: '
|
|
95
|
+
await processDebugMessage({ id: 'debug', message: 'shutdown' });
|
|
83
96
|
}
|
|
84
97
|
} else if (req.url === '/debug/refresh') {
|
|
85
98
|
await processRefreshCache(req);
|
|
86
99
|
}
|
|
87
|
-
if (req.url === '/debug/client') {
|
|
88
|
-
}
|
|
100
|
+
// else if (req.url === '/debug/client') {
|
|
101
|
+
// }
|
|
89
102
|
return true;
|
|
90
103
|
}
|
|
104
|
+
|
|
105
|
+
// this is called from a request and passes the restartApp message to loader
|
|
106
|
+
export const snedRestartAppMsgToLoader = async () => {
|
|
107
|
+
if (!cluster.isPrimary) {
|
|
108
|
+
console.warn(`restartApp: shouldn't come here`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!process.send) {
|
|
113
|
+
console.log(`${process.pid} - The primary process is not focked from loader, so cannot restart.`);
|
|
114
|
+
} else {
|
|
115
|
+
console.log(`${process.pid} - Old app sends restartApp to loader (${process.execPath})`);
|
|
116
|
+
|
|
117
|
+
process.send({ id: 'debug', message: 'restartApp' });
|
|
118
|
+
}
|
|
119
|
+
};
|
package/src/app/web-server.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { IncomingMessage, ServerResponse } from 'http';
|
|
|
8
8
|
import { Duplex } from 'stream';
|
|
9
9
|
import { WebProcessor } from './web-processor';
|
|
10
10
|
import { DebugService } from '../api/debug-service';
|
|
11
|
+
import cluster from 'cluster';
|
|
11
12
|
const logger = new Logger('web-server');
|
|
12
13
|
|
|
13
14
|
export class WebServer {
|
|
@@ -47,7 +48,7 @@ export class WebServer {
|
|
|
47
48
|
httpServer.on('upgrade', this.handleUpgrade.bind(this));
|
|
48
49
|
|
|
49
50
|
httpServer.listen(httpPort, bindIp, () => {
|
|
50
|
-
logger.info(`Http Server is started: http://localhost:${httpPort}`);
|
|
51
|
+
logger.info(`Http Server ${cluster.worker ? cluster.worker.id : -1} is started: http://localhost:${httpPort}`);
|
|
51
52
|
});
|
|
52
53
|
httpServer.on('error', (error: any) => {
|
|
53
54
|
logger.error('Error occurred on http server', error);
|
|
@@ -88,11 +89,12 @@ export class WebServer {
|
|
|
88
89
|
httpsServer.setTimeout(timeout);
|
|
89
90
|
}
|
|
90
91
|
httpsServer.listen(httpsPort, bindIp, () => {
|
|
91
|
-
logger.info(`Https Server is started: https://localhost:${httpsPort}`);
|
|
92
|
+
logger.info(`Https Server ${cluster.worker ? cluster.worker.id : -1} is started: https://localhost:${httpsPort}`);
|
|
92
93
|
});
|
|
93
94
|
httpsServer.on('error', (error: any) => {
|
|
94
95
|
logger.error('Error occurred on https server', error);
|
|
95
96
|
});
|
|
97
|
+
|
|
96
98
|
return httpsServer;
|
|
97
99
|
}
|
|
98
100
|
}
|