wok-server 0.2.2 → 0.3.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.
Files changed (37) hide show
  1. package/dist/config/index.js +17 -3
  2. package/dist/log/file.js +4 -11
  3. package/dist/mvc/config.js +23 -16
  4. package/dist/mvc/handler/json.js +27 -0
  5. package/dist/mvc/index.js +6 -295
  6. package/dist/mvc/render/file.js +3 -85
  7. package/dist/mvc/server.js +263 -0
  8. package/dist/mvc/static/header.js +67 -0
  9. package/dist/mvc/static/index.js +6 -0
  10. package/dist/mvc/static/mime-type.js +84 -0
  11. package/dist/mvc/static/server-cache-config.js +66 -0
  12. package/dist/mvc/static/server-cache.js +133 -0
  13. package/dist/mvc/static/static-handler.js +355 -0
  14. package/dist/mysql/manager/ops/update.js +15 -1
  15. package/dist/validation/index.js +12 -1
  16. package/dist/validation/validator/array.js +7 -11
  17. package/dist/validation/validator/min.js +2 -2
  18. package/documentation/zh-cn/config.md +31 -0
  19. package/documentation/zh-cn/mvc.md +48 -0
  20. package/documentation/zh-cn/mysql.md +72 -0
  21. package/documentation/zh-cn/validate.md +21 -15
  22. package/package.json +1 -1
  23. package/types/config/index.d.ts +10 -0
  24. package/types/mvc/config.d.ts +13 -1
  25. package/types/mvc/handler/json.d.ts +14 -0
  26. package/types/mvc/index.d.ts +3 -36
  27. package/types/mvc/server.d.ts +85 -0
  28. package/types/mvc/static/header.d.ts +27 -0
  29. package/types/mvc/static/index.d.ts +3 -0
  30. package/types/mvc/static/mime-type.d.ts +2 -0
  31. package/types/mvc/static/server-cache-config.d.ts +30 -0
  32. package/types/mvc/static/server-cache.d.ts +76 -0
  33. package/types/mvc/static/static-handler.d.ts +72 -0
  34. package/types/mysql/manager/ops/update.d.ts +7 -1
  35. package/types/validation/validator/array.d.ts +2 -2
  36. package/types/validation/validator/min.d.ts +2 -2
  37. package/types/validation/validator/plain-obj.d.ts +1 -1
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WokServer = void 0;
4
+ const fs_1 = require("fs");
5
+ const http_1 = require("http");
6
+ const https_1 = require("https");
7
+ const os_1 = require("os");
8
+ const path_1 = require("path");
9
+ const log_1 = require("../log");
10
+ const access_log_1 = require("./access-log");
11
+ const config_1 = require("./config");
12
+ const exchange_1 = require("./exchange");
13
+ const render_1 = require("./render");
14
+ const static_1 = require("./static");
15
+ const validation_1 = require("../validation");
16
+ function resolvePath(path) {
17
+ if ((0, path_1.isAbsolute)(path)) {
18
+ return path;
19
+ }
20
+ return (0, path_1.resolve)(process.cwd(), path);
21
+ }
22
+ /**
23
+ * web 服务
24
+ */
25
+ class WokServer {
26
+ opts;
27
+ config;
28
+ server;
29
+ interceptors;
30
+ routers;
31
+ defaultRouter;
32
+ staticHandler;
33
+ constructor(opts) {
34
+ this.opts = opts;
35
+ this.config = (0, config_1.getConfig)();
36
+ // https
37
+ let tls;
38
+ if (this.config.tlsEnable) {
39
+ (0, validation_1.validate)(this.config, {
40
+ tlsCert: [(0, validation_1.notBlank)()],
41
+ tlsKey: [(0, validation_1.notBlank)()]
42
+ });
43
+ tls = {
44
+ cert: (0, fs_1.readFileSync)(resolvePath(this.config.tlsCert)),
45
+ key: (0, fs_1.readFileSync)(resolvePath(this.config.tlsKey))
46
+ };
47
+ }
48
+ // 路由
49
+ this.routers = opts.routers;
50
+ this.defaultRouter = this.routers['*'];
51
+ // 拦截器
52
+ this.interceptors = [];
53
+ if (this.config.accessLog) {
54
+ this.interceptors.push(access_log_1.accessLogInterceptor);
55
+ }
56
+ if (opts.interceptors) {
57
+ this.interceptors.push(...opts.interceptors);
58
+ }
59
+ // 静态处理
60
+ if (opts.static) {
61
+ this.staticHandler = new static_1.StaticHandler(opts.static);
62
+ }
63
+ // 主服务
64
+ if (!tls) {
65
+ this.server = (0, http_1.createServer)((req, res) => {
66
+ res.setHeader('Server', 'Wok Server');
67
+ res.on('error', error => {
68
+ // 如果响应流发生错误,只能把信息记录下来
69
+ (0, log_1.getLogger)().error(`Response Error:${req.url}`, error);
70
+ });
71
+ this.handleRequest(req, res).catch(error => {
72
+ (0, log_1.getLogger)().error(`Handle request failed:${req.url}`, error);
73
+ if (!res.writableEnded) {
74
+ // 响应 500
75
+ (0, render_1.renderError)(res, error.message ? error.message : 'Internal Server Error', 500);
76
+ }
77
+ });
78
+ });
79
+ }
80
+ else {
81
+ this.server = (0, https_1.createServer)({
82
+ key: tls.key,
83
+ cert: tls.cert
84
+ }, (req, res) => {
85
+ res.setHeader('Server', 'Wok Server');
86
+ res.on('error', error => {
87
+ // 如果响应流发生错误,只能把信息记录下来
88
+ (0, log_1.getLogger)().error(`Response Error:${req.url}`, error);
89
+ });
90
+ this.handleRequest(req, res).catch(error => {
91
+ (0, log_1.getLogger)().error(`Handle request failed:${req.url}`, error);
92
+ if (!res.writableEnded) {
93
+ // 响应 500
94
+ (0, render_1.renderError)(res, error.message ? error.message : 'Internal Server Error', 500);
95
+ }
96
+ });
97
+ });
98
+ }
99
+ this.server.setTimeout(this.config.timeout);
100
+ this.server.on('timeout', (socket) => {
101
+ socket.end('HTTP/1.1 408 Timeout\r\ncontent-type: application/json; charset=utf-8\r\n\r\n{"message":"Request timeout"}');
102
+ });
103
+ }
104
+ /**
105
+ * 处理请求,完成拦截器和路由的流程.
106
+ *
107
+ * @param req
108
+ * @param res
109
+ */
110
+ async handleRequest(req, res) {
111
+ req.socket.remoteAddress;
112
+ const { method } = req;
113
+ // cros 支持
114
+ res.setHeader('Access-Control-Allow-Origin', this.config.corsAllowOrigin);
115
+ res.setHeader('Access-Control-Allow-Headers', this.config.corsAllowHeaders);
116
+ res.setHeader('Access-Control-Allow-Methods', this.config.corsAllowMethods);
117
+ if (method === 'OPTIONS') {
118
+ res.statusCode = 200;
119
+ res.end();
120
+ return;
121
+ }
122
+ const exchange = new exchange_1.ServerExchange(req, res);
123
+ // 顺序执行拦截器
124
+ await this.handleInterceptor(0, exchange, req, res);
125
+ }
126
+ /**
127
+ * 处理拦截器.
128
+ * @param idx 当前要执行的拦截器下标
129
+ * @param exchange 传输对象
130
+ * @param req
131
+ * @param res
132
+ */
133
+ async handleInterceptor(idx, exchange, req, res) {
134
+ const interceptor = this.interceptors[idx];
135
+ // 到最后一个了,那么执行路由处理
136
+ if (!interceptor) {
137
+ await this.handleRouter(exchange);
138
+ return;
139
+ }
140
+ await interceptor(exchange, () => this.handleInterceptor(idx + 1, exchange, req, res));
141
+ }
142
+ /**
143
+ * 处理路由.
144
+ * @param exchange
145
+ * @param routers
146
+ * @param staticSettings
147
+ * @returns
148
+ */
149
+ async handleRouter(exchange) {
150
+ const url = exchange.request.url;
151
+ if (url === undefined) {
152
+ return;
153
+ }
154
+ // 判定路由
155
+ const idx = url.indexOf('?');
156
+ let path = idx === -1 ? url : url.substring(0, idx);
157
+ const router = this.routers[path];
158
+ if (!router) {
159
+ // 路由找不不到,尝试静态文件
160
+ if (this.staticHandler) {
161
+ const method = (exchange.request.method || '').toLowerCase();
162
+ if (method === 'head') {
163
+ if (await this.staticHandler.handleHead(path, exchange.response)) {
164
+ return;
165
+ }
166
+ }
167
+ if (method === 'get') {
168
+ if (await this.staticHandler.handleGet(exchange.request, exchange.response, path)) {
169
+ return;
170
+ }
171
+ }
172
+ }
173
+ this.respond404(exchange, path);
174
+ return;
175
+ }
176
+ // 执行路由
177
+ await router(exchange);
178
+ // 在路由顺利处理的情况下,如果 res 没有 end ,就表示响应没有完成
179
+ // 也就是说路由没有做响应处理,或处理没有完成就结束了,给予错误提示
180
+ if (!exchange.response.writableEnded) {
181
+ throw new Error(`RouterHandler unresponsive, url: ${url}`);
182
+ }
183
+ }
184
+ /**
185
+ * 404响应
186
+ *
187
+ * @param exchange
188
+ * @param path
189
+ */
190
+ async respond404(exchange, path) {
191
+ if (this.defaultRouter) {
192
+ await this.defaultRouter(exchange);
193
+ }
194
+ else {
195
+ exchange.respondErrMsg(`${path} not found`, 404);
196
+ }
197
+ }
198
+ /**
199
+ * 启动
200
+ */
201
+ async start() {
202
+ if (this.opts.preHandler) {
203
+ await this.opts.preHandler(this.server);
204
+ }
205
+ await new Promise((resolve, reject) => {
206
+ this.server.on('error', e => {
207
+ if (e.code === 'EADDRINUSE') {
208
+ reject(`Port ${this.config.port} is already in use.`);
209
+ }
210
+ else {
211
+ reject(e);
212
+ }
213
+ });
214
+ this.server.listen(this.config.port, resolve);
215
+ });
216
+ console.log('App running at: ');
217
+ let portOmitted = (this.server instanceof https_1.Server && this.config.port === 443) ||
218
+ (this.server instanceof http_1.Server && this.config.port === 80);
219
+ this.getIpv4List().forEach(ip => {
220
+ if (portOmitted) {
221
+ console.log(`http://${ip}`);
222
+ }
223
+ else {
224
+ console.log(`http://${ip}:${this.config.port}`);
225
+ }
226
+ });
227
+ }
228
+ /**
229
+ * 获取 ipv4 地址列表
230
+ * @returns
231
+ */
232
+ getIpv4List() {
233
+ const ifs = (0, os_1.networkInterfaces)();
234
+ const res = [];
235
+ for (const name in ifs) {
236
+ const list = ifs[name];
237
+ if (!list) {
238
+ continue;
239
+ }
240
+ res.push(...list.filter(info => info.family === 'IPv4').map(info => info.address));
241
+ }
242
+ return res;
243
+ }
244
+ /**
245
+ * 停止
246
+ */
247
+ async stop() {
248
+ if (!this.server.listening) {
249
+ return;
250
+ }
251
+ await new Promise((res, rej) => {
252
+ this.server.close(err => {
253
+ if (err) {
254
+ rej(err);
255
+ }
256
+ else {
257
+ res();
258
+ }
259
+ });
260
+ });
261
+ }
262
+ }
263
+ exports.WokServer = WokServer;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseHeaders = void 0;
4
+ /**
5
+ * 解析 range 消息头,如果解析失败,返回 null
6
+ */
7
+ function parseRange(rangeHeader) {
8
+ // 解析,range 示例:bytes=200-1000, 2000-6576, 19000-
9
+ // 多段的情况,暂时不做支持,非常麻烦,段数多还可能会有效率问题
10
+ // 如果不是字节范围不是以字节为单位,暂时也不做支持
11
+ const ranges = rangeHeader.split(',');
12
+ if (!ranges.length) {
13
+ return null;
14
+ }
15
+ let range = ranges[0].trim();
16
+ if (!range.startsWith('bytes=')) {
17
+ return null;
18
+ }
19
+ range = range.substring(6);
20
+ const strs = range.split('-');
21
+ let start = strs[0] ? parseInt(strs[0], 10) : NaN;
22
+ let end = strs[1] ? parseInt(strs[1], 10) : undefined;
23
+ return { start, end };
24
+ }
25
+ /**
26
+ * 解析消息头,返回静态文件处理需要的信息
27
+ * @param headers
28
+ */
29
+ function parseHeaders(headers) {
30
+ // 是否支持 gzip
31
+ const acceptEncoding = headers['accept-encoding'];
32
+ let acceptGzip = false;
33
+ if (acceptEncoding) {
34
+ // Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
35
+ const acceptEncodings = acceptEncoding
36
+ .trim()
37
+ .split(',')
38
+ .map(item => item.trim())
39
+ .map(item => item.split(';')[0]);
40
+ if (acceptEncodings.includes('gzip') || acceptEncodings.includes('*')) {
41
+ acceptGzip = true;
42
+ }
43
+ }
44
+ let res = {
45
+ gzip: acceptGzip
46
+ };
47
+ // 支持 If-Modified-Since
48
+ // 由于只是简单的文件映射,没有 etag,不能支持 If-None-Match
49
+ const ifModifiedSince = headers['if-modified-since'];
50
+ if (ifModifiedSince) {
51
+ const modifiedSince = new Date(ifModifiedSince);
52
+ if (modifiedSince instanceof Date && !isNaN(modifiedSince.getTime())) {
53
+ res.ifModifiedSince = modifiedSince;
54
+ }
55
+ }
56
+ // 支持 Range
57
+ // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Range
58
+ const rangeHeader = headers['range'];
59
+ if (rangeHeader) {
60
+ const rangeRes = parseRange(rangeHeader);
61
+ if (rangeRes) {
62
+ res.range = rangeRes;
63
+ }
64
+ }
65
+ return res;
66
+ }
67
+ exports.parseHeaders = parseHeaders;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./mime-type"), exports);
5
+ tslib_1.__exportStar(require("./header"), exports);
6
+ tslib_1.__exportStar(require("./static-handler"), exports);
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decideContentType = exports.MIME_TYPES = void 0;
4
+ exports.MIME_TYPES = {
5
+ aac: 'audio/aac',
6
+ abw: 'application/x-abiword',
7
+ arc: 'application/x-freearc',
8
+ avi: 'video/x-msvideo',
9
+ azw: 'application/vnd.amazon.ebook',
10
+ bin: 'application/octet-stream',
11
+ bmp: 'image/bmp',
12
+ bz: 'application/x-bzip',
13
+ bz2: 'application/x-bzip2',
14
+ csh: 'application/x-csh',
15
+ css: 'text/css',
16
+ csv: 'text/csv',
17
+ doc: 'application/msword',
18
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
19
+ eot: 'application/vnd.ms-fontobject',
20
+ epub: 'application/epub+zip',
21
+ gif: 'image/gif',
22
+ htm: 'text/html',
23
+ html: 'text/html',
24
+ ico: 'image/vnd.microsoft.icon',
25
+ ics: 'text/calendar',
26
+ jar: 'application/java-archive',
27
+ jpeg: 'image/jpeg',
28
+ jpg: 'image/jpeg',
29
+ js: 'text/javascript',
30
+ json: 'application/json',
31
+ jsonld: 'application/ld+json',
32
+ mid: 'audio/midi',
33
+ midi: 'audio/midi',
34
+ mjs: 'text/javascript',
35
+ mp3: 'audio/mpeg',
36
+ mpeg: 'video/mpeg',
37
+ mpkg: 'application/vnd.apple.installer+xml',
38
+ odp: 'application/vnd.oasis.opendocument.presentation',
39
+ ods: 'application/vnd.oasis.opendocument.spreadsheet',
40
+ odt: 'application/vnd.oasis.opendocument.text',
41
+ oga: 'audio/ogg',
42
+ ogv: 'video/ogg',
43
+ ogx: 'application/ogg',
44
+ otf: 'font/otf',
45
+ png: 'image/png',
46
+ pdf: 'application/pdf',
47
+ ppt: 'application/vnd.ms-powerpoint',
48
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
49
+ rar: 'application/x-rar-compressed',
50
+ rtf: 'application/rtf',
51
+ sh: 'application/x-sh',
52
+ svg: 'image/svg+xml',
53
+ swf: 'application/x-shockwave-flash',
54
+ tar: 'application/x-tar',
55
+ tif: 'image/tiff',
56
+ tiff: 'image/tiff',
57
+ ttf: 'font/ttf',
58
+ txt: 'text/plain',
59
+ vsd: 'application/vnd.visio',
60
+ wav: 'audio/wav',
61
+ weba: 'audio/webm',
62
+ webm: 'video/webm',
63
+ webp: 'image/webp',
64
+ woff: 'font/woff',
65
+ woff2: 'font/woff2',
66
+ xhtml: 'application/xhtml+xml',
67
+ xls: 'application/vnd.ms-excel',
68
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
69
+ xml: 'application/xml',
70
+ xul: 'application/vnd.mozilla.xul+xml',
71
+ zip: 'application/zip',
72
+ '3gp': 'video/3gpp',
73
+ '3g2': 'video/3gpp2',
74
+ '7z': 'application/x-7z-compressed'
75
+ };
76
+ function decideContentType(fileName) {
77
+ const idx = fileName.lastIndexOf('.');
78
+ if (idx === -1) {
79
+ return undefined;
80
+ }
81
+ const suffix = fileName.substring(idx + 1);
82
+ return exports.MIME_TYPES[suffix];
83
+ }
84
+ exports.decideContentType = decideContentType;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getConfig = exports.parseSize = void 0;
4
+ const config_1 = require("../../config");
5
+ const validation_1 = require("../../validation");
6
+ function parseSize(size) {
7
+ if (typeof size === 'number') {
8
+ return size;
9
+ }
10
+ const match = size.match(/^(\d+)([kmg]?)$/i);
11
+ if (!match) {
12
+ throw new Error(`Invalid size: ${size}`);
13
+ }
14
+ const [, num, unit] = match;
15
+ let sizeNum = parseInt(num, 10);
16
+ switch (unit.toLowerCase()) {
17
+ case 'k':
18
+ sizeNum *= 1024;
19
+ break;
20
+ case 'm':
21
+ sizeNum *= 1024 * 1024;
22
+ break;
23
+ case 'g':
24
+ sizeNum *= 1024 * 1024 * 1024;
25
+ break;
26
+ default:
27
+ break;
28
+ }
29
+ return sizeNum;
30
+ }
31
+ exports.parseSize = parseSize;
32
+ function validateSize(max) {
33
+ const maxNum = parseSize(max);
34
+ const validator = 'size';
35
+ return val => {
36
+ if (val === null || val === undefined) {
37
+ return { ok: true };
38
+ }
39
+ const sizeNum = typeof val === 'number' ? val : parseSize(val);
40
+ if (sizeNum < 1) {
41
+ return { ok: false, validator, message: `size must greater than 1` };
42
+ }
43
+ if (sizeNum > maxNum) {
44
+ return { ok: false, validator, message: `size must less than ${max}` };
45
+ }
46
+ return { ok: true };
47
+ };
48
+ }
49
+ /**
50
+ * 获取缓存的配置信息
51
+ * @returns
52
+ */
53
+ function getConfig() {
54
+ return (0, config_1.generateConfig)({
55
+ enable: false,
56
+ maxAge: 600,
57
+ maxSize: '100m',
58
+ maxFileSize: '10m'
59
+ }, 'SERVER_STATIC_CACHE', {
60
+ enable: [(0, validation_1.notNull)()],
61
+ maxAge: [(0, validation_1.notNull)(), (0, validation_1.min)(1), (0, validation_1.max)(31536000)],
62
+ maxSize: [(0, validation_1.notNull)(), validateSize('1024g')],
63
+ maxFileSize: [(0, validation_1.notNull)(), validateSize('1g')]
64
+ });
65
+ }
66
+ exports.getConfig = getConfig;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServerCache = void 0;
4
+ /**
5
+ * 文件的服务器缓存
6
+ */
7
+ class ServerCache {
8
+ opts;
9
+ /**
10
+ * 当前的缓存内容大小,单位字节
11
+ */
12
+ size = 0;
13
+ /**
14
+ * 缓存内容
15
+ */
16
+ cacheMap = new Map();
17
+ /**
18
+ * promise 表,作用是处理异步并发问题,如果有相同的 key 同时请求,保证异步的 provider 只执行一次
19
+ */
20
+ promiseMap = new Map();
21
+ constructor(opts) {
22
+ this.opts = opts;
23
+ }
24
+ /**
25
+ * 获取缓存
26
+ */
27
+ get(key) {
28
+ const data = this.cacheMap.get(key);
29
+ if (data) {
30
+ if (data.expireAt < Date.now()) {
31
+ this.cacheMap.delete(key);
32
+ return null;
33
+ }
34
+ return data;
35
+ }
36
+ return null;
37
+ }
38
+ /**
39
+ * 设置缓存内容,如果缓存内容超过最大缓存大小,则触发清除缓存操作
40
+ * @param key
41
+ * @param value
42
+ * @returns
43
+ */
44
+ set(key, value) {
45
+ const expireAt = Date.now() + this.opts.maxAge * 1000;
46
+ const content = value;
47
+ content.expireAt = expireAt;
48
+ this.cacheMap.set(key, content);
49
+ this.size += content.buffer.length;
50
+ if (this.size > this.opts.maxSize) {
51
+ setTimeout(() => this.clean(), 0);
52
+ }
53
+ }
54
+ /**
55
+ * 删除缓存
56
+ */
57
+ remove(key) {
58
+ const data = this.cacheMap.get(key);
59
+ if (data) {
60
+ this.cacheMap.delete(key);
61
+ this.size -= data.buffer.length;
62
+ }
63
+ }
64
+ /**
65
+ * 如果缓存不存在,则计算缓存内容并放入缓存
66
+ * @param key
67
+ * @param provider 计算函数,返回值将放入缓存,如果返回 null 则表示未能计算出缓存内容,不进行缓存
68
+ */
69
+ async computeIfAbsent(key, provider) {
70
+ const data = this.get(key);
71
+ if (data) {
72
+ return data;
73
+ }
74
+ // 如果已经在处理中,则直接返回 promise
75
+ const ep = this.promiseMap.get(key);
76
+ if (ep) {
77
+ return ep;
78
+ }
79
+ const promise = Promise.resolve().then(async () => {
80
+ try {
81
+ const res = await provider();
82
+ if (res) {
83
+ this.set(key, res);
84
+ }
85
+ return res;
86
+ }
87
+ finally {
88
+ this.promiseMap.delete(key);
89
+ }
90
+ });
91
+ this.promiseMap.set(key, promise);
92
+ return promise;
93
+ }
94
+ /**
95
+ * 清理无用的缓存内容
96
+ */
97
+ clean() {
98
+ // 先清理掉过期的
99
+ const keys = Array.from(this.cacheMap.keys());
100
+ for (const key of keys) {
101
+ const data = this.cacheMap.get(key);
102
+ if (data) {
103
+ if (data.expireAt < Date.now()) {
104
+ this.cacheMap.delete(key);
105
+ this.size -= data.buffer.length;
106
+ }
107
+ }
108
+ }
109
+ if (this.size < this.opts.maxSize) {
110
+ return;
111
+ }
112
+ const keys2 = Array.from(this.cacheMap.keys());
113
+ // 再逐个清理,直到空间不会超出最大缓存大小
114
+ for (const key of keys2) {
115
+ if (this.size < this.opts.maxSize * 0.8) {
116
+ break;
117
+ }
118
+ const data = this.cacheMap.get(key);
119
+ if (data) {
120
+ this.cacheMap.delete(key);
121
+ this.size -= data.buffer.length;
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * 清除缓存内容
127
+ */
128
+ clear() {
129
+ this.cacheMap.clear();
130
+ this.size = 0;
131
+ }
132
+ }
133
+ exports.ServerCache = ServerCache;