lupine.api 1.1.58 → 1.1.60

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.
Files changed (137) hide show
  1. package/README.md +3 -3
  2. package/admin/admin-about.tsx +12 -16
  3. package/admin/admin-config.tsx +47 -44
  4. package/admin/admin-css.tsx +3 -3
  5. package/admin/admin-db.tsx +75 -75
  6. package/admin/admin-frame-helper.tsx +364 -364
  7. package/admin/admin-frame.tsx +164 -164
  8. package/admin/admin-index.tsx +65 -65
  9. package/admin/admin-login.tsx +111 -111
  10. package/admin/admin-menu-edit.tsx +637 -637
  11. package/admin/admin-menu-list.tsx +87 -87
  12. package/admin/admin-page-edit.tsx +564 -564
  13. package/admin/admin-page-list.tsx +83 -83
  14. package/admin/admin-performance.tsx +28 -28
  15. package/admin/admin-release.tsx +427 -426
  16. package/admin/admin-resources.tsx +382 -382
  17. package/admin/admin-shell.tsx +89 -89
  18. package/admin/admin-table-data.tsx +146 -146
  19. package/admin/admin-table-list.tsx +230 -230
  20. package/admin/admin-test-animations.tsx +395 -395
  21. package/admin/admin-test-component.tsx +823 -808
  22. package/admin/admin-test-edit.tsx +319 -319
  23. package/admin/admin-test-themes.tsx +56 -56
  24. package/admin/admin-tokens.tsx +338 -338
  25. package/admin/design/admin-design.tsx +174 -174
  26. package/admin/design/block-grid.tsx +36 -36
  27. package/admin/design/block-grid1.tsx +21 -21
  28. package/admin/design/block-paragraph.tsx +19 -19
  29. package/admin/design/block-title.tsx +19 -19
  30. package/admin/design/design-block-box.tsx +140 -140
  31. package/admin/design/drag-data.tsx +24 -24
  32. package/admin/index.ts +9 -9
  33. package/admin/package.json +15 -15
  34. package/admin/tsconfig.json +127 -127
  35. package/dev/copy-folder.js +32 -32
  36. package/dev/cp-index-html.js +69 -69
  37. package/dev/file-utils.js +12 -12
  38. package/dev/index.js +18 -19
  39. package/dev/package.json +12 -12
  40. package/dev/plugin-ifelse.js +168 -168
  41. package/dev/plugin-ifelse.test.js +37 -37
  42. package/dev/run-cmd.js +14 -14
  43. package/dev/send-request.js +12 -12
  44. package/package.json +55 -55
  45. package/src/admin-api/admin-api-helper.ts +210 -205
  46. package/src/admin-api/admin-api.ts +65 -65
  47. package/src/admin-api/admin-auth.ts +152 -146
  48. package/src/admin-api/admin-config.ts +94 -84
  49. package/src/admin-api/admin-csv.ts +94 -94
  50. package/src/admin-api/admin-db.ts +269 -269
  51. package/src/admin-api/admin-menu.ts +135 -135
  52. package/src/admin-api/admin-page.ts +135 -135
  53. package/src/admin-api/admin-performance.ts +128 -128
  54. package/src/admin-api/admin-release.ts +706 -700
  55. package/src/admin-api/admin-resources.ts +318 -318
  56. package/src/admin-api/admin-token-helper.ts +82 -79
  57. package/src/admin-api/admin-tokens.ts +90 -90
  58. package/src/admin-api/index.ts +2 -2
  59. package/src/admin-api/web-config-api.ts +19 -19
  60. package/src/api/api-cache.ts +103 -103
  61. package/src/api/api-helper.ts +44 -44
  62. package/src/api/api-module.ts +67 -60
  63. package/src/api/api-router.ts +177 -177
  64. package/src/api/api-shared-storage.ts +64 -64
  65. package/src/api/async-storage.ts +5 -5
  66. package/src/api/debug-service.ts +56 -56
  67. package/src/api/encode-html.ts +27 -27
  68. package/src/api/handle-status.ts +75 -75
  69. package/src/api/index.ts +15 -16
  70. package/src/api/mini-web-socket.ts +270 -270
  71. package/src/api/server-content-type.ts +82 -82
  72. package/src/api/server-render.ts +235 -215
  73. package/src/api/shell-service.ts +74 -74
  74. package/src/api/simple-storage.ts +80 -80
  75. package/src/api/static-server.ts +128 -125
  76. package/src/api/to-client-delivery.ts +26 -26
  77. package/src/app/app-cache.ts +55 -55
  78. package/src/app/app-helper.ts +62 -62
  79. package/src/app/app-message.ts +109 -109
  80. package/src/app/app-shared-storage.ts +363 -363
  81. package/src/app/app-start.ts +136 -136
  82. package/src/app/cleanup-exit.ts +16 -16
  83. package/src/app/host-to-path.ts +38 -38
  84. package/src/app/index.ts +11 -11
  85. package/src/app/process-dev-requests.ts +130 -130
  86. package/src/app/web-listener.ts +294 -294
  87. package/src/app/web-processor.ts +47 -42
  88. package/src/app/web-server.ts +100 -100
  89. package/src/common-js/web-env.js +104 -104
  90. package/src/index.ts +7 -7
  91. package/src/lang/api-lang-en.ts +26 -26
  92. package/src/lang/api-lang-zh-cn.ts +27 -27
  93. package/src/lang/index.ts +2 -2
  94. package/src/lang/lang-helper.ts +76 -76
  95. package/src/lang/lang-props.ts +6 -6
  96. package/src/lib/db/db-helper.ts +23 -23
  97. package/src/lib/db/db-mysql.ts +249 -250
  98. package/src/lib/db/db-sqlite.ts +101 -101
  99. package/src/lib/db/db.spec.ts +28 -28
  100. package/src/lib/db/db.ts +325 -325
  101. package/src/lib/db/index.ts +5 -5
  102. package/src/lib/index.ts +3 -3
  103. package/src/lib/logger.spec.ts +214 -214
  104. package/src/lib/logger.ts +281 -281
  105. package/src/lib/runtime-require.ts +37 -37
  106. package/src/lib/utils/cookie-util.ts +34 -34
  107. package/src/lib/utils/crypto.ts +58 -58
  108. package/src/lib/utils/date-utils.ts +317 -317
  109. package/src/lib/utils/deep-merge.ts +37 -37
  110. package/src/lib/utils/delay.ts +12 -12
  111. package/src/lib/utils/file-setting.ts +55 -55
  112. package/src/lib/utils/format-bytes.ts +11 -11
  113. package/src/lib/utils/fs-utils.ts +158 -158
  114. package/src/lib/utils/get-env.ts +27 -27
  115. package/src/lib/utils/index.ts +12 -12
  116. package/src/lib/utils/is-type.ts +48 -48
  117. package/src/lib/utils/load-env.ts +14 -14
  118. package/src/lib/utils/pad.ts +6 -6
  119. package/src/models/api-base.ts +5 -5
  120. package/src/models/api-module-props.ts +10 -11
  121. package/src/models/api-router-props.ts +26 -26
  122. package/src/models/app-cache-props.ts +33 -33
  123. package/src/models/app-data-props.ts +10 -10
  124. package/src/models/app-helper-props.ts +6 -6
  125. package/src/models/app-shared-storage-props.ts +38 -38
  126. package/src/models/app-start-props.ts +18 -18
  127. package/src/models/async-storage-props.ts +13 -13
  128. package/src/models/db-config.ts +30 -30
  129. package/src/models/host-to-path-props.ts +12 -12
  130. package/src/models/index.ts +16 -16
  131. package/src/models/json-object.ts +8 -8
  132. package/src/models/locals-props.ts +36 -36
  133. package/src/models/logger-props.ts +84 -84
  134. package/src/models/simple-storage-props.ts +13 -14
  135. package/src/models/to-client-delivery-props.ts +6 -6
  136. package/tsconfig.json +115 -115
  137. package/dev/plugin-gen-versions.js +0 -20
@@ -1,294 +1,294 @@
1
- import { IncomingMessage, ServerResponse } from 'http';
2
- import { Logger } from '../lib/logger';
3
- import crypto from 'crypto';
4
- import { parseCookies } from '../lib/utils/cookie-util';
5
- import { WebProcessor } from './web-processor';
6
- import { handler403, handler404, handler500, handler503, SimpleStorage } from '../api';
7
- import { JsonObject, AsyncStorageProps, ServerRequest, SetCookieProps } from '../models';
8
- import { HostToPath } from './host-to-path';
9
- import { serializeCookie } from '../lib/utils/cookie-util';
10
- const logger = new Logger('listener');
11
-
12
- let MAX_REQUEST_SIZE = 1024 * 1024 * 5;
13
- export const setMaxRequestSize = (size: number) => {
14
- MAX_REQUEST_SIZE = size;
15
- };
16
-
17
- // The maximum number of requests being processed. If there are no requests for 10 minutes, this number will be reset to 0.
18
- let MAX_REQUEST_COUNT = 100;
19
- let REQUEST_COUNT = 0;
20
- export const setMaxRequestCount = (count: number) => {
21
- MAX_REQUEST_COUNT = count;
22
- };
23
- export const getRequestCount = () => {
24
- return REQUEST_COUNT;
25
- };
26
-
27
- let REQUEST_TIMEOUT = 1000 * 30;
28
- export const setRequestTimeout = (timeout: number) => {
29
- REQUEST_TIMEOUT = timeout;
30
- };
31
-
32
- let accessControlAllowHosts: string[] = [];
33
- export const setAccessControlAllowHost = (allowHosts: string[]) => {
34
- accessControlAllowHosts = allowHosts;
35
- };
36
-
37
- let SERVER_NAME: string = 'nginx/1.19.2';
38
- export const setServerName = (serverName: string) => {
39
- SERVER_NAME = serverName;
40
- };
41
-
42
- export type RawMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => Promise<void>;
43
-
44
- /*
45
- IP limit
46
-
47
- let IP_LIMIT_ENABLED = false;
48
- export const setIpLimitEnabled = (enabled: boolean) => {
49
- IP_LIMIT_ENABLED = enabled;
50
- };
51
-
52
- let IP_LIMIT_RATE = 60;
53
- let IP_LIMIT_DURATION = 1000 * 30;
54
- export const setIpLimitRateAndDuration = (rate: number, durationSeconds: number) => {
55
- IP_LIMIT_RATE = rate;
56
- IP_LIMIT_DURATION = durationSeconds * 1000;
57
- };
58
-
59
-
60
- class TTLMap<K, V> extends Map<K, V> {
61
- set(key: K, value: V): this;
62
- set(key: K, value: V, ttl: number): this;
63
- set(key: K, value: V, ttl?: number): this {
64
- const isNew = !this.has(key);
65
- super.set(key, value);
66
- if (isNew && ttl) {
67
- setTimeout(() => this.delete(key), ttl).unref?.();
68
- }
69
- return this;
70
- }
71
- }
72
- const counts = new TTLMap<string, number>();
73
-
74
- function checkFixedWindow(ip: string, limit = IP_LIMIT_RATE) {
75
- const now = Date.now();
76
- const key = `${ip}:${Math.floor(now / IP_LIMIT_DURATION)}`;
77
- const count = (counts.get(key) || 0) + 1;
78
- counts.set(key, count, IP_LIMIT_DURATION * 2); // key自动过期
79
- return count > limit;
80
- }
81
-
82
- */
83
-
84
- let lastRequestTime = new Date().getTime();
85
-
86
- // type ProcessRequest = (req: ServerRequest, res: ServerResponse) => void;
87
- export class WebListener {
88
- // process requests before business logic, for example IP filter, rate limit, etc.
89
- rawMiddlewares: RawMiddleware[];
90
- processor: WebProcessor;
91
-
92
- constructor(processRequest: WebProcessor) {
93
- this.rawMiddlewares = [];
94
- this.processor = processRequest;
95
- }
96
-
97
- addRawMiddlewareChain(middleware: RawMiddleware) {
98
- this.rawMiddlewares.push(middleware);
99
- }
100
-
101
- runMiddlewareChain(list: RawMiddleware[], context: { req: IncomingMessage; res: ServerResponse }) {
102
- const dispatch = async (i: number) => {
103
- const fn = list[i];
104
- if (!fn) return;
105
- await fn(context.req, context.res, () => dispatch(i + 1));
106
- };
107
- return dispatch(0);
108
- }
109
-
110
- async listener(reqOrigin: IncomingMessage, res: ServerResponse) {
111
- // If there is no request in the last 10 minutes, reset the request count.
112
- if (new Date().getTime() - lastRequestTime > 1000 * 60 * 10) {
113
- if (REQUEST_COUNT != 0) {
114
- // in case any errors skipped (--REQUEST_COUNT)
115
- logger.warn(`!!!!!!!!!! ========== REQUEST_COUNT is not counted properly: ${REQUEST_COUNT}`);
116
- }
117
- REQUEST_COUNT = 0;
118
- lastRequestTime = new Date().getTime();
119
- }
120
-
121
- // back-pressure
122
- if (REQUEST_COUNT > MAX_REQUEST_COUNT) {
123
- logger.warn(`Too many requests, count: ${REQUEST_COUNT} > ${MAX_REQUEST_COUNT}`);
124
- handler503(res, 'Server is busy, please retry later.');
125
- return;
126
- }
127
-
128
- await this.runMiddlewareChain(this.rawMiddlewares, { req: reqOrigin, res });
129
- if (res.writableEnded || res.headersSent) {
130
- return;
131
- }
132
-
133
- // const requestStart = process.hrtime.bigint();
134
- const uuid = crypto.randomUUID();
135
- const url = reqOrigin.url || '';
136
- const requestInfo = `uuid: ${uuid}, Access url: ${url}`;
137
- const req = reqOrigin as ServerRequest;
138
-
139
- const host = (req.headers.host || '').split(':')[0]; // req.headers.host contains port
140
- const hostPath = HostToPath.findHostPath(host);
141
- if (!hostPath || !hostPath.webPath || !hostPath.appName) {
142
- const msg = `Web root is not defined properly for host: ${host}.`;
143
- logger.error(msg);
144
- handler404(res, msg);
145
- return;
146
- }
147
-
148
- REQUEST_COUNT++;
149
- logger.debug(
150
- `Request started. Count: ${REQUEST_COUNT}, Log uuid: ${uuid}, access: ${
151
- req.headers.host
152
- }, url: ${url}, time: ${new Date().toISOString()}, from: ${req.socket.remoteAddress}`
153
- );
154
-
155
- const urlSplit = url.split('?');
156
- req.setTimeout(REQUEST_TIMEOUT);
157
- req.on('timeout', () => {
158
- REQUEST_COUNT--;
159
- logger.warn('timeout');
160
- req.destroy(new Error('timeout handling'));
161
- });
162
-
163
- const jsonFn = (): JsonObject | undefined => {
164
- if (!req.locals._json && req.locals.body) {
165
- const sBody = req.locals.body.toString();
166
- if (!sBody) {
167
- req.locals._json = undefined;
168
- } else {
169
- try {
170
- req.locals._json = JSON.parse(sBody);
171
- } catch (err: any) {
172
- logger.warn(`JSON.parse error: ${err.message}`);
173
- }
174
- }
175
- }
176
- return req.locals._json;
177
- };
178
- const cookiesFn = (): SimpleStorage => {
179
- if (!req.locals._cookies) {
180
- req.locals._cookies = new SimpleStorage(req.headers ? parseCookies(req.headers.cookie) : {});
181
- }
182
- return req.locals._cookies;
183
- };
184
- const setCookieFn = (name: string, value: string, options: SetCookieProps): void => {
185
- const cookies: string[] = [];
186
- const cookiesOld = res.getHeader('Set-Cookie');
187
- if (cookiesOld) {
188
- if (!Array.isArray(cookiesOld)) {
189
- cookies.push(cookiesOld as any);
190
- } else {
191
- cookies.push(...cookiesOld);
192
- }
193
- }
194
-
195
- const cookiePair = serializeCookie(name, value, options);
196
- cookies.push(cookiePair);
197
- res.setHeader('Set-Cookie', cookies);
198
-
199
- const localCookies = req.locals.cookies();
200
- localCookies.set(name, value);
201
- };
202
-
203
- req.locals = {
204
- uuid,
205
- host,
206
- url,
207
- hostPath,
208
- // urlSections: urlSplit[0].split('/').filter((i) => !!i),
209
- query: new URLSearchParams(urlSplit[1] || ''),
210
- urlWithoutQuery: urlSplit[0],
211
- urlParameters: new SimpleStorage({}),
212
- body: undefined,
213
- json: jsonFn,
214
- cookies: cookiesFn,
215
- setCookie: setCookieFn,
216
- clearCookie: (name: string) => {
217
- res.setHeader('Set-Cookie', `${name}=; max-age=0`);
218
- },
219
- };
220
-
221
- let bigRequest = false;
222
- let totalLength = 0;
223
- const bodyData: any[] = [];
224
- req.on('error', (err: any) => {
225
- REQUEST_COUNT--;
226
- logger.error(`${requestInfo}, count: ${REQUEST_COUNT}, Request Error: `, err);
227
- handler500(res, `listener error: ${err && err.message}`);
228
- });
229
-
230
- req.on('data', (chunk: any) => {
231
- totalLength += chunk.length;
232
- logger.debug(`${requestInfo}, Request data length: ${chunk.length}, total: ${totalLength}`);
233
- // Limit Request Size
234
- if (!bigRequest && totalLength < MAX_REQUEST_SIZE) {
235
- bodyData.push(chunk);
236
- } else {
237
- if (!bigRequest) {
238
- bigRequest = true;
239
- logger.warn(`Warn, request data is too big: ${totalLength} > ${MAX_REQUEST_SIZE}`);
240
- }
241
- req.socket.destroy();
242
- }
243
- });
244
-
245
- req.on('end', async () => {
246
- try {
247
- if (bigRequest) {
248
- logger.warn(`Request data is too big to process, url: ${req.locals.url}`);
249
- handler403(res, `Request data is too big to process, url: ${req.locals.url}`);
250
- return;
251
- }
252
-
253
- const body = Buffer.concat(bodyData);
254
- const contentType = req.headers['content-type'];
255
- logger.debug(`url: ${url}, Request body length: ${body.length}, contentType: ${contentType}`);
256
- req.locals.body = body;
257
-
258
- res.setHeader('Server', SERVER_NAME);
259
- if (accessControlAllowHosts.includes(host)) {
260
- const allowOrigin = req.headers.origin && req.headers.origin !== 'null' ? req.headers.origin : '*';
261
- res.setHeader('Access-Control-Allow-Origin', allowOrigin);
262
- res.setHeader('Access-Control-Allow-Credentials', 'true');
263
- }
264
-
265
- const store: AsyncStorageProps = {
266
- uuid: uuid,
267
- hostPath: hostPath,
268
- appName: hostPath.appName,
269
- locals: req.locals,
270
- lang: req.locals.cookies().get('lang', 'en') || 'en',
271
- };
272
- await this.processor.processRequest(store, req, res);
273
- } finally {
274
- REQUEST_COUNT--;
275
- }
276
- // await new Promise(resolve => setTimeout(resolve, 3000));
277
-
278
- // asyncLocalStorage.run(store, async () => {
279
- // try {
280
- // await onEnd();
281
- // } catch (error: any) {
282
- // logger.error(`url: ${url}, Request end error: `, error.message);
283
- // }
284
-
285
- // lastRequestTime = new Date().getTime();
286
- // const requestEnd = process.hrtime.bigint();
287
- // REQUEST_COUNT--;
288
- // logger.debug(
289
- // `Request finished. Count: ${REQUEST_COUNT}, url: ${url}, time: ${new Date().toISOString()}, duration: ${Number(requestEnd - requestStart) / 1000000} ms`
290
- // );
291
- // });
292
- });
293
- }
294
- }
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { Logger } from '../lib/logger';
3
+ import crypto from 'crypto';
4
+ import { parseCookies } from '../lib/utils/cookie-util';
5
+ import { WebProcessor } from './web-processor';
6
+ import { handler403, handler404, handler500, handler503, SimpleStorage } from '../api';
7
+ import { JsonObject, AsyncStorageProps, ServerRequest, SetCookieProps } from '../models';
8
+ import { HostToPath } from './host-to-path';
9
+ import { serializeCookie } from '../lib/utils/cookie-util';
10
+ const logger = new Logger('listener');
11
+
12
+ let MAX_REQUEST_SIZE = 1024 * 1024 * 5;
13
+ export const setMaxRequestSize = (size: number) => {
14
+ MAX_REQUEST_SIZE = size;
15
+ };
16
+
17
+ // The maximum number of requests being processed. If there are no requests for 10 minutes, this number will be reset to 0.
18
+ let MAX_REQUEST_COUNT = 100;
19
+ let REQUEST_COUNT = 0;
20
+ export const setMaxRequestCount = (count: number) => {
21
+ MAX_REQUEST_COUNT = count;
22
+ };
23
+ export const getRequestCount = () => {
24
+ return REQUEST_COUNT;
25
+ };
26
+
27
+ let REQUEST_TIMEOUT = 1000 * 30;
28
+ export const setRequestTimeout = (timeout: number) => {
29
+ REQUEST_TIMEOUT = timeout;
30
+ };
31
+
32
+ let accessControlAllowHosts: string[] = [];
33
+ export const setAccessControlAllowHost = (allowHosts: string[]) => {
34
+ accessControlAllowHosts = allowHosts;
35
+ };
36
+
37
+ let SERVER_NAME: string = 'nginx/1.19.2';
38
+ export const setServerName = (serverName: string) => {
39
+ SERVER_NAME = serverName;
40
+ };
41
+
42
+ export type RawMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => Promise<void>;
43
+
44
+ /*
45
+ IP limit
46
+
47
+ let IP_LIMIT_ENABLED = false;
48
+ export const setIpLimitEnabled = (enabled: boolean) => {
49
+ IP_LIMIT_ENABLED = enabled;
50
+ };
51
+
52
+ let IP_LIMIT_RATE = 60;
53
+ let IP_LIMIT_DURATION = 1000 * 30;
54
+ export const setIpLimitRateAndDuration = (rate: number, durationSeconds: number) => {
55
+ IP_LIMIT_RATE = rate;
56
+ IP_LIMIT_DURATION = durationSeconds * 1000;
57
+ };
58
+
59
+
60
+ class TTLMap<K, V> extends Map<K, V> {
61
+ set(key: K, value: V): this;
62
+ set(key: K, value: V, ttl: number): this;
63
+ set(key: K, value: V, ttl?: number): this {
64
+ const isNew = !this.has(key);
65
+ super.set(key, value);
66
+ if (isNew && ttl) {
67
+ setTimeout(() => this.delete(key), ttl).unref?.();
68
+ }
69
+ return this;
70
+ }
71
+ }
72
+ const counts = new TTLMap<string, number>();
73
+
74
+ function checkFixedWindow(ip: string, limit = IP_LIMIT_RATE) {
75
+ const now = Date.now();
76
+ const key = `${ip}:${Math.floor(now / IP_LIMIT_DURATION)}`;
77
+ const count = (counts.get(key) || 0) + 1;
78
+ counts.set(key, count, IP_LIMIT_DURATION * 2); // key自动过期
79
+ return count > limit;
80
+ }
81
+
82
+ */
83
+
84
+ let lastRequestTime = new Date().getTime();
85
+
86
+ // type ProcessRequest = (req: ServerRequest, res: ServerResponse) => void;
87
+ export class WebListener {
88
+ // process requests before business logic, for example IP filter, rate limit, etc.
89
+ rawMiddlewares: RawMiddleware[];
90
+ processor: WebProcessor;
91
+
92
+ constructor(processRequest: WebProcessor) {
93
+ this.rawMiddlewares = [];
94
+ this.processor = processRequest;
95
+ }
96
+
97
+ addRawMiddlewareChain(middleware: RawMiddleware) {
98
+ this.rawMiddlewares.push(middleware);
99
+ }
100
+
101
+ runMiddlewareChain(list: RawMiddleware[], context: { req: IncomingMessage; res: ServerResponse }) {
102
+ const dispatch = async (i: number) => {
103
+ const fn = list[i];
104
+ if (!fn) return;
105
+ await fn(context.req, context.res, () => dispatch(i + 1));
106
+ };
107
+ return dispatch(0);
108
+ }
109
+
110
+ async listener(reqOrigin: IncomingMessage, res: ServerResponse) {
111
+ // If there is no request in the last 10 minutes, reset the request count.
112
+ if (new Date().getTime() - lastRequestTime > 1000 * 60 * 10) {
113
+ if (REQUEST_COUNT != 0) {
114
+ // in case any errors skipped (--REQUEST_COUNT)
115
+ logger.warn(`!!!!!!!!!! ========== REQUEST_COUNT is not counted properly: ${REQUEST_COUNT}`);
116
+ }
117
+ REQUEST_COUNT = 0;
118
+ lastRequestTime = new Date().getTime();
119
+ }
120
+
121
+ // back-pressure
122
+ if (REQUEST_COUNT > MAX_REQUEST_COUNT) {
123
+ logger.warn(`Too many requests, count: ${REQUEST_COUNT} > ${MAX_REQUEST_COUNT}`);
124
+ handler503(res, 'Server is busy, please retry later.');
125
+ return;
126
+ }
127
+
128
+ await this.runMiddlewareChain(this.rawMiddlewares, { req: reqOrigin, res });
129
+ if (res.writableEnded || res.headersSent) {
130
+ return;
131
+ }
132
+
133
+ // const requestStart = process.hrtime.bigint();
134
+ const uuid = crypto.randomUUID();
135
+ const url = reqOrigin.url || '';
136
+ const requestInfo = `uuid: ${uuid}, Access url: ${url}`;
137
+ const req = reqOrigin as ServerRequest;
138
+
139
+ const host = (req.headers.host || '').split(':')[0]; // req.headers.host contains port
140
+ const hostPath = HostToPath.findHostPath(host);
141
+ if (!hostPath || !hostPath.webPath || !hostPath.appName) {
142
+ const msg = `Web root is not defined properly for host: ${host}.`;
143
+ logger.error(msg);
144
+ handler404(res, msg);
145
+ return;
146
+ }
147
+
148
+ REQUEST_COUNT++;
149
+ logger.debug(
150
+ `Request started. Count: ${REQUEST_COUNT}, Log uuid: ${uuid}, access: ${
151
+ req.headers.host
152
+ }, url: ${url}, time: ${new Date().toISOString()}, from: ${req.socket.remoteAddress}`
153
+ );
154
+
155
+ const urlSplit = url.split('?');
156
+ req.setTimeout(REQUEST_TIMEOUT);
157
+ req.on('timeout', () => {
158
+ REQUEST_COUNT--;
159
+ logger.warn('timeout');
160
+ req.destroy(new Error('timeout handling'));
161
+ });
162
+
163
+ const jsonFn = (): JsonObject | undefined => {
164
+ if (!req.locals._json && req.locals.body) {
165
+ const sBody = req.locals.body.toString();
166
+ if (!sBody) {
167
+ req.locals._json = undefined;
168
+ } else {
169
+ try {
170
+ req.locals._json = JSON.parse(sBody);
171
+ } catch (err: any) {
172
+ logger.warn(`JSON.parse error: ${err.message}`);
173
+ }
174
+ }
175
+ }
176
+ return req.locals._json;
177
+ };
178
+ const cookiesFn = (): SimpleStorage => {
179
+ if (!req.locals._cookies) {
180
+ req.locals._cookies = new SimpleStorage(req.headers ? parseCookies(req.headers.cookie) : {});
181
+ }
182
+ return req.locals._cookies;
183
+ };
184
+ const setCookieFn = (name: string, value: string, options: SetCookieProps): void => {
185
+ const cookies: string[] = [];
186
+ const cookiesOld = res.getHeader('Set-Cookie');
187
+ if (cookiesOld) {
188
+ if (!Array.isArray(cookiesOld)) {
189
+ cookies.push(cookiesOld as any);
190
+ } else {
191
+ cookies.push(...cookiesOld);
192
+ }
193
+ }
194
+
195
+ const cookiePair = serializeCookie(name, value, options);
196
+ cookies.push(cookiePair);
197
+ res.setHeader('Set-Cookie', cookies);
198
+
199
+ const localCookies = req.locals.cookies();
200
+ localCookies.set(name, value);
201
+ };
202
+
203
+ req.locals = {
204
+ uuid,
205
+ host,
206
+ url,
207
+ hostPath,
208
+ // urlSections: urlSplit[0].split('/').filter((i) => !!i),
209
+ query: new URLSearchParams(urlSplit[1] || ''),
210
+ urlWithoutQuery: urlSplit[0],
211
+ urlParameters: new SimpleStorage({}),
212
+ body: undefined,
213
+ json: jsonFn,
214
+ cookies: cookiesFn,
215
+ setCookie: setCookieFn,
216
+ clearCookie: (name: string) => {
217
+ res.setHeader('Set-Cookie', `${name}=; max-age=0`);
218
+ },
219
+ };
220
+
221
+ let bigRequest = false;
222
+ let totalLength = 0;
223
+ const bodyData: any[] = [];
224
+ req.on('error', (err: any) => {
225
+ REQUEST_COUNT--;
226
+ logger.error(`${requestInfo}, count: ${REQUEST_COUNT}, Request Error: `, err);
227
+ handler500(res, `listener error: ${err && err.message}`);
228
+ });
229
+
230
+ req.on('data', (chunk: any) => {
231
+ totalLength += chunk.length;
232
+ logger.debug(`${requestInfo}, Request data length: ${chunk.length}, total: ${totalLength}`);
233
+ // Limit Request Size
234
+ if (!bigRequest && totalLength < MAX_REQUEST_SIZE) {
235
+ bodyData.push(chunk);
236
+ } else {
237
+ if (!bigRequest) {
238
+ bigRequest = true;
239
+ logger.warn(`Warn, request data is too big: ${totalLength} > ${MAX_REQUEST_SIZE}`);
240
+ }
241
+ req.socket.destroy();
242
+ }
243
+ });
244
+
245
+ req.on('end', async () => {
246
+ try {
247
+ if (bigRequest) {
248
+ logger.warn(`Request data is too big to process, url: ${req.locals.url}`);
249
+ handler403(res, `Request data is too big to process, url: ${req.locals.url}`);
250
+ return;
251
+ }
252
+
253
+ const body = Buffer.concat(bodyData);
254
+ const contentType = req.headers['content-type'];
255
+ logger.debug(`url: ${url}, Request body length: ${body.length}, contentType: ${contentType}`);
256
+ req.locals.body = body;
257
+
258
+ res.setHeader('Server', SERVER_NAME);
259
+ if (accessControlAllowHosts.includes(host)) {
260
+ const allowOrigin = req.headers.origin && req.headers.origin !== 'null' ? req.headers.origin : '*';
261
+ res.setHeader('Access-Control-Allow-Origin', allowOrigin);
262
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
263
+ }
264
+
265
+ const store: AsyncStorageProps = {
266
+ uuid: uuid,
267
+ hostPath: hostPath,
268
+ appName: hostPath.appName,
269
+ locals: req.locals,
270
+ lang: req.locals.cookies().get('lang', 'en') || 'en',
271
+ };
272
+ await this.processor.processRequest(store, req, res);
273
+ } finally {
274
+ REQUEST_COUNT--;
275
+ }
276
+ // await new Promise(resolve => setTimeout(resolve, 3000));
277
+
278
+ // asyncLocalStorage.run(store, async () => {
279
+ // try {
280
+ // await onEnd();
281
+ // } catch (error: any) {
282
+ // logger.error(`url: ${url}, Request end error: `, error.message);
283
+ // }
284
+
285
+ // lastRequestTime = new Date().getTime();
286
+ // const requestEnd = process.hrtime.bigint();
287
+ // REQUEST_COUNT--;
288
+ // logger.debug(
289
+ // `Request finished. Count: ${REQUEST_COUNT}, url: ${url}, time: ${new Date().toISOString()}, duration: ${Number(requestEnd - requestStart) / 1000000} ms`
290
+ // );
291
+ // });
292
+ });
293
+ }
294
+ }