wok-server 0.4.13 → 0.6.0
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/README.en.md +61 -0
- package/README.md +44 -29
- package/dist/cache/cache.js +98 -98
- package/dist/cache/config.js +19 -19
- package/dist/cache/index.js +27 -27
- package/dist/cache/purge-task.js +46 -46
- package/dist/cache/stat.js +47 -47
- package/dist/config/convert.js +36 -36
- package/dist/config/exception.js +14 -14
- package/dist/config/index.js +81 -81
- package/dist/http-client/index.js +136 -136
- package/dist/i18n/ar.js +17 -17
- package/dist/i18n/de.js +17 -17
- package/dist/i18n/en-us.js +17 -17
- package/dist/i18n/es.js +17 -17
- package/dist/i18n/fr.js +17 -17
- package/dist/i18n/i18n.js +231 -231
- package/dist/i18n/index.js +52 -52
- package/dist/i18n/ja.js +17 -17
- package/dist/i18n/ko.js +17 -17
- package/dist/i18n/msg.js +2 -2
- package/dist/i18n/pt.js +17 -17
- package/dist/i18n/ru.js +17 -17
- package/dist/i18n/tag.js +18 -18
- package/dist/i18n/zh-HK.js +17 -17
- package/dist/i18n/zh-TW.js +17 -17
- package/dist/i18n/zh-cn.js +17 -17
- package/dist/index.js +14 -14
- package/dist/lock/index.js +114 -114
- package/dist/log/config.js +35 -29
- package/dist/log/date.js +21 -21
- package/dist/log/file.js +198 -72
- package/dist/log/index.js +135 -105
- package/dist/log/level.js +33 -33
- package/dist/log/log.js +56 -0
- package/dist/log/store.js +19 -16
- package/dist/mongodb/collection.js +2 -2
- package/dist/mongodb/config.js +34 -34
- package/dist/mongodb/doc.js +2 -2
- package/dist/mongodb/exception.js +14 -14
- package/dist/mongodb/index.js +58 -58
- package/dist/mongodb/manager/base.js +563 -563
- package/dist/mongodb/manager/index.js +63 -63
- package/dist/mongodb/manager/tx-strict.js +84 -84
- package/dist/mongodb/manager/tx.js +30 -30
- package/dist/mongodb/migration.js +52 -52
- package/dist/mvc/access-log.js +33 -33
- package/dist/mvc/config.js +27 -27
- package/dist/mvc/exchange.js +113 -113
- package/dist/mvc/handler/index.js +7 -6
- package/dist/mvc/handler/json.js +65 -65
- package/dist/mvc/handler/restful.js +35 -35
- package/dist/mvc/handler/sse.js +65 -0
- package/dist/mvc/handler/upload.js +31 -31
- package/dist/mvc/index.js +50 -50
- package/dist/mvc/interceptor.js +2 -2
- package/dist/mvc/query.js +43 -43
- package/dist/mvc/render/file.js +132 -132
- package/dist/mvc/render/html/html.js +90 -90
- package/dist/mvc/render/html/index.js +18 -18
- package/dist/mvc/render/html/style.js +2 -2
- package/dist/mvc/render/index.js +7 -7
- package/dist/mvc/render/json.js +26 -26
- package/dist/mvc/render/text.js +16 -16
- package/dist/mvc/router.js +2 -2
- package/dist/mvc/server.js +272 -272
- package/dist/mvc/static/header.js +67 -67
- package/dist/mvc/static/index.js +6 -6
- package/dist/mvc/static/mime-type.js +84 -84
- package/dist/mvc/static/server-cache-config.js +66 -66
- package/dist/mvc/static/server-cache.js +133 -133
- package/dist/mvc/static/static-handler.js +372 -372
- package/dist/mysql/config.js +51 -51
- package/dist/mysql/exception.js +14 -14
- package/dist/mysql/index.js +87 -87
- package/dist/mysql/manager/base.js +239 -231
- package/dist/mysql/manager/index.js +107 -107
- package/dist/mysql/manager/ops/count.js +20 -20
- package/dist/mysql/manager/ops/criteria.js +356 -356
- package/dist/mysql/manager/ops/delete.js +65 -65
- package/dist/mysql/manager/ops/exist.js +26 -26
- package/dist/mysql/manager/ops/find.js +169 -130
- package/dist/mysql/manager/ops/index.js +14 -14
- package/dist/mysql/manager/ops/insert.js +106 -106
- package/dist/mysql/manager/ops/modify.js +10 -10
- package/dist/mysql/manager/ops/paginate.js +23 -23
- package/dist/mysql/manager/ops/query.js +9 -9
- package/dist/mysql/manager/ops/update.js +216 -216
- package/dist/mysql/manager/ops/utils.js +24 -24
- package/dist/mysql/manager/tx-strict.js +103 -100
- package/dist/mysql/manager/tx.js +30 -30
- package/dist/mysql/manager/utils.js +56 -56
- package/dist/mysql/migration.js +136 -136
- package/dist/mysql/table-info.js +8 -8
- package/dist/task/daily.js +59 -59
- package/dist/task/fixed-delay.js +38 -38
- package/dist/task/fixed-rate.js +42 -42
- package/dist/task/index.js +9 -9
- package/dist/task/task.js +56 -56
- package/dist/validation/exception.js +36 -36
- package/dist/validation/index.js +40 -40
- package/dist/validation/validator/array.js +34 -34
- package/dist/validation/validator/enum.js +28 -28
- package/dist/validation/validator/index.js +14 -14
- package/dist/validation/validator/length.js +40 -40
- package/dist/validation/validator/max-length.js +35 -35
- package/dist/validation/validator/max.js +29 -29
- package/dist/validation/validator/min-length.js +33 -33
- package/dist/validation/validator/min.js +29 -29
- package/dist/validation/validator/not-blank.js +33 -33
- package/dist/validation/validator/not-null.js +21 -21
- package/dist/validation/validator/plain-obj.js +32 -32
- package/dist/validation/validator/regexp.js +34 -34
- package/documentation/en/cache.md +56 -0
- package/documentation/en/config.md +96 -0
- package/documentation/en/engineering.md +256 -0
- package/documentation/en/http-client.md +32 -0
- package/documentation/en/i18n.md +143 -0
- package/documentation/en/index.md +24 -0
- package/documentation/en/lock.md +51 -0
- package/documentation/en/log.md +109 -0
- package/documentation/en/mongodb.md +256 -0
- package/documentation/en/mvc.md +688 -0
- package/documentation/en/mysql.md +552 -0
- package/documentation/en/task.md +45 -0
- package/documentation/en/test.md +56 -0
- package/documentation/en/validate.md +130 -0
- package/documentation/zh-cn/engineering.md +1 -1
- package/documentation/zh-cn/log.md +81 -8
- package/documentation/zh-cn/mvc.md +66 -24
- package/documentation/zh-cn/mysql.md +24 -23
- package/documentation/zh-cn/validate.md +2 -2
- package/package.json +3 -1
- package/skills/wok-server-api-rules/SKILL.md +350 -0
- package/skills/wok-server-cache/SKILL.md +216 -0
- package/skills/wok-server-config/SKILL.md +200 -0
- package/skills/wok-server-getting-started/SKILL.md +123 -0
- package/skills/wok-server-getting-started/references/engineering.md +169 -0
- package/skills/wok-server-http-client/SKILL.md +164 -0
- package/skills/wok-server-i18n/SKILL.md +214 -0
- package/skills/wok-server-lock/SKILL.md +144 -0
- package/skills/wok-server-log/SKILL.md +218 -0
- package/skills/wok-server-mongodb/SKILL.md +235 -0
- package/skills/wok-server-mvc/SKILL.md +251 -0
- package/skills/wok-server-mvc/references/respond-html.md +157 -0
- package/skills/wok-server-mvc/references/sse.md +121 -0
- package/skills/wok-server-mvc/references/static-files.md +47 -0
- package/skills/wok-server-mvc/references/upload.md +62 -0
- package/skills/wok-server-mvc/references/websocket.md +30 -0
- package/skills/wok-server-mysql/SKILL.md +315 -0
- package/skills/wok-server-mysql/references/multi-datasource.md +76 -0
- package/skills/wok-server-mysql/references/version-control.md +22 -0
- package/skills/wok-server-task/SKILL.md +158 -0
- package/skills/wok-server-validate/SKILL.md +167 -0
- package/src/cache/cache.ts +118 -0
- package/src/cache/config.ts +53 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/purge-task.ts +53 -0
- package/src/cache/stat.ts +47 -0
- package/src/config/convert.ts +27 -0
- package/src/config/exception.ts +8 -0
- package/src/config/index.ts +92 -0
- package/src/http-client/index.ts +202 -0
- package/src/i18n/ar.ts +16 -0
- package/src/i18n/de.ts +16 -0
- package/src/i18n/en-us.ts +16 -0
- package/src/i18n/es.ts +16 -0
- package/src/i18n/fr.ts +16 -0
- package/src/i18n/i18n.ts +230 -0
- package/src/i18n/index.ts +50 -0
- package/src/i18n/ja.ts +16 -0
- package/src/i18n/ko.ts +16 -0
- package/src/i18n/msg.ts +50 -0
- package/src/i18n/pt.ts +16 -0
- package/src/i18n/ru.ts +16 -0
- package/src/i18n/tag.ts +18 -0
- package/src/i18n/zh-HK.ts +16 -0
- package/src/i18n/zh-TW.ts +16 -0
- package/src/i18n/zh-cn.ts +16 -0
- package/src/index.ts +11 -0
- package/src/lock/index.ts +164 -0
- package/src/log/config.ts +71 -0
- package/src/log/date.ts +19 -0
- package/src/log/file.ts +215 -0
- package/src/log/index.ts +136 -0
- package/src/log/level.ts +29 -0
- package/src/log/log.ts +77 -0
- package/src/log/store.ts +31 -0
- package/src/mongodb/collection.ts +25 -0
- package/src/mongodb/config.ts +69 -0
- package/src/mongodb/doc.ts +12 -0
- package/src/mongodb/exception.ts +8 -0
- package/src/mongodb/index.ts +71 -0
- package/src/mongodb/manager/base.ts +674 -0
- package/src/mongodb/manager/index.ts +80 -0
- package/src/mongodb/manager/tx-strict.ts +153 -0
- package/src/mongodb/manager/tx.ts +34 -0
- package/src/mongodb/migration.ts +66 -0
- package/src/mvc/access-log.ts +33 -0
- package/src/mvc/config.ts +70 -0
- package/src/mvc/exchange.ts +126 -0
- package/src/mvc/handler/index.ts +4 -0
- package/src/mvc/handler/json.ts +96 -0
- package/src/mvc/handler/restful.ts +39 -0
- package/src/mvc/handler/sse.ts +90 -0
- package/src/mvc/handler/upload.ts +54 -0
- package/src/mvc/index.ts +48 -0
- package/src/mvc/interceptor.ts +12 -0
- package/src/mvc/query.ts +36 -0
- package/src/mvc/render/file.ts +148 -0
- package/src/mvc/render/html/html.ts +187 -0
- package/src/mvc/render/html/index.ts +16 -0
- package/src/mvc/render/html/style.ts +1201 -0
- package/src/mvc/render/index.ts +4 -0
- package/src/mvc/render/json.ts +24 -0
- package/src/mvc/render/text.ts +14 -0
- package/src/mvc/router.ts +13 -0
- package/src/mvc/server.ts +315 -0
- package/src/mvc/static/header.ts +86 -0
- package/src/mvc/static/index.ts +3 -0
- package/src/mvc/static/mime-type.ts +81 -0
- package/src/mvc/static/server-cache-config.ts +92 -0
- package/src/mvc/static/server-cache.ts +171 -0
- package/src/mvc/static/static-handler.ts +445 -0
- package/src/mysql/config.ts +130 -0
- package/src/mysql/exception.ts +8 -0
- package/src/mysql/index.ts +88 -0
- package/src/mysql/manager/base.ts +285 -0
- package/src/mysql/manager/index.ts +112 -0
- package/src/mysql/manager/ops/count.ts +30 -0
- package/src/mysql/manager/ops/criteria.ts +412 -0
- package/src/mysql/manager/ops/delete.ts +96 -0
- package/src/mysql/manager/ops/exist.ts +41 -0
- package/src/mysql/manager/ops/find.ts +226 -0
- package/src/mysql/manager/ops/index.ts +11 -0
- package/src/mysql/manager/ops/insert.ts +120 -0
- package/src/mysql/manager/ops/modify.ts +14 -0
- package/src/mysql/manager/ops/paginate.ts +60 -0
- package/src/mysql/manager/ops/query.ts +13 -0
- package/src/mysql/manager/ops/update.ts +294 -0
- package/src/mysql/manager/ops/utils.ts +20 -0
- package/src/mysql/manager/tx-strict.ts +138 -0
- package/src/mysql/manager/tx.ts +31 -0
- package/src/mysql/manager/utils.ts +75 -0
- package/src/mysql/migration.ts +149 -0
- package/src/mysql/table-info.ts +41 -0
- package/src/task/daily.ts +70 -0
- package/src/task/fixed-delay.ts +45 -0
- package/src/task/fixed-rate.ts +49 -0
- package/src/task/index.ts +4 -0
- package/src/task/task.ts +70 -0
- package/src/validation/exception.ts +27 -0
- package/src/validation/index.ts +61 -0
- package/src/validation/validator/array.ts +32 -0
- package/src/validation/validator/enum.ts +25 -0
- package/src/validation/validator/index.ts +11 -0
- package/src/validation/validator/length.ts +42 -0
- package/src/validation/validator/max-length.ts +33 -0
- package/src/validation/validator/max.ts +26 -0
- package/src/validation/validator/min-length.ts +31 -0
- package/src/validation/validator/min.ts +26 -0
- package/src/validation/validator/not-blank.ts +31 -0
- package/src/validation/validator/not-null.ts +19 -0
- package/src/validation/validator/plain-obj.ts +30 -0
- package/src/validation/validator/regexp.ts +32 -0
- package/types/cache/cache.d.ts +52 -52
- package/types/cache/config.d.ts +32 -32
- package/types/cache/index.d.ts +2 -2
- package/types/cache/purge-task.d.ts +11 -11
- package/types/cache/stat.d.ts +26 -26
- package/types/config/convert.d.ts +6 -6
- package/types/config/exception.d.ts +7 -7
- package/types/config/index.d.ts +25 -25
- package/types/http-client/index.d.ts +71 -71
- package/types/i18n/ar.d.ts +2 -2
- package/types/i18n/de.d.ts +2 -2
- package/types/i18n/en-us.d.ts +2 -2
- package/types/i18n/es.d.ts +2 -2
- package/types/i18n/fr.d.ts +2 -2
- package/types/i18n/i18n.d.ts +102 -102
- package/types/i18n/index.d.ts +9 -9
- package/types/i18n/ja.d.ts +2 -2
- package/types/i18n/ko.d.ts +2 -2
- package/types/i18n/msg.d.ts +50 -50
- package/types/i18n/pt.d.ts +2 -2
- package/types/i18n/ru.d.ts +2 -2
- package/types/i18n/tag.d.ts +11 -11
- package/types/i18n/zh-HK.d.ts +2 -2
- package/types/i18n/zh-TW.d.ts +2 -2
- package/types/i18n/zh-cn.d.ts +2 -2
- package/types/index.d.ts +11 -11
- package/types/lock/index.d.ts +64 -64
- package/types/log/config.d.ts +35 -27
- package/types/log/date.d.ts +2 -2
- package/types/log/file.d.ts +13 -5
- package/types/log/index.d.ts +53 -34
- package/types/log/level.d.ts +14 -14
- package/types/log/log.d.ts +40 -0
- package/types/log/store.d.ts +19 -12
- package/types/mongodb/collection.d.ts +25 -25
- package/types/mongodb/config.d.ts +45 -45
- package/types/mongodb/doc.d.ts +11 -11
- package/types/mongodb/exception.d.ts +7 -7
- package/types/mongodb/index.d.ts +29 -29
- package/types/mongodb/manager/base.d.ts +188 -188
- package/types/mongodb/manager/index.d.ts +38 -38
- package/types/mongodb/manager/tx-strict.d.ts +41 -41
- package/types/mongodb/manager/tx.d.ts +21 -21
- package/types/mongodb/migration.d.ts +12 -12
- package/types/mvc/access-log.d.ts +7 -7
- package/types/mvc/config.d.ts +42 -42
- package/types/mvc/exchange.d.ts +72 -72
- package/types/mvc/handler/index.d.ts +4 -3
- package/types/mvc/handler/json.d.ts +44 -44
- package/types/mvc/handler/restful.d.ts +11 -11
- package/types/mvc/handler/sse.d.ts +34 -0
- package/types/mvc/handler/upload.d.ts +36 -36
- package/types/mvc/index.d.ts +22 -22
- package/types/mvc/interceptor.d.ts +11 -11
- package/types/mvc/query.d.ts +13 -13
- package/types/mvc/render/file.d.ts +10 -10
- package/types/mvc/render/html/html.d.ts +98 -98
- package/types/mvc/render/html/index.d.ts +11 -11
- package/types/mvc/render/html/style.d.ts +1201 -1201
- package/types/mvc/render/index.d.ts +4 -4
- package/types/mvc/render/json.d.ts +17 -17
- package/types/mvc/render/text.d.ts +10 -10
- package/types/mvc/router.d.ts +11 -11
- package/types/mvc/server.d.ts +90 -90
- package/types/mvc/static/header.d.ts +27 -27
- package/types/mvc/static/index.d.ts +3 -3
- package/types/mvc/static/mime-type.d.ts +2 -2
- package/types/mvc/static/server-cache-config.d.ts +30 -30
- package/types/mvc/static/server-cache.d.ts +76 -76
- package/types/mvc/static/static-handler.d.ts +77 -77
- package/types/mysql/config.d.ts +90 -90
- package/types/mysql/exception.d.ts +7 -7
- package/types/mysql/index.d.ts +16 -16
- package/types/mysql/manager/base.d.ts +165 -159
- package/types/mysql/manager/index.d.ts +36 -36
- package/types/mysql/manager/ops/count.d.ts +13 -13
- package/types/mysql/manager/ops/criteria.d.ts +134 -134
- package/types/mysql/manager/ops/delete.d.ts +46 -46
- package/types/mysql/manager/ops/exist.d.ts +6 -6
- package/types/mysql/manager/ops/find.d.ts +86 -70
- package/types/mysql/manager/ops/index.d.ts +10 -10
- package/types/mysql/manager/ops/insert.d.ts +18 -18
- package/types/mysql/manager/ops/modify.d.ts +3 -3
- package/types/mysql/manager/ops/paginate.d.ts +36 -36
- package/types/mysql/manager/ops/query.d.ts +3 -3
- package/types/mysql/manager/ops/update.d.ts +76 -76
- package/types/mysql/manager/ops/utils.d.ts +5 -5
- package/types/mysql/manager/tx-strict.d.ts +36 -35
- package/types/mysql/manager/tx.d.ts +15 -15
- package/types/mysql/manager/utils.d.ts +17 -17
- package/types/mysql/migration.d.ts +8 -8
- package/types/mysql/table-info.d.ts +36 -36
- package/types/task/daily.d.ts +16 -16
- package/types/task/fixed-delay.d.ts +9 -9
- package/types/task/fixed-rate.d.ts +9 -9
- package/types/task/index.d.ts +4 -4
- package/types/task/task.d.ts +34 -34
- package/types/validation/exception.d.ts +38 -38
- package/types/validation/index.d.ts +32 -32
- package/types/validation/validator/array.d.ts +5 -5
- package/types/validation/validator/enum.d.ts +8 -8
- package/types/validation/validator/index.d.ts +11 -11
- package/types/validation/validator/length.d.ts +10 -10
- package/types/validation/validator/max-length.d.ts +8 -8
- package/types/validation/validator/max.d.ts +7 -7
- package/types/validation/validator/min-length.d.ts +6 -6
- package/types/validation/validator/min.d.ts +7 -7
- package/types/validation/validator/not-blank.d.ts +7 -7
- package/types/validation/validator/not-null.d.ts +6 -6
- package/types/validation/validator/plain-obj.d.ts +7 -7
- package/types/validation/validator/regexp.d.ts +8 -8
|
@@ -1,372 +1,372 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StaticHandler = void 0;
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
const promises_1 = require("fs/promises");
|
|
6
|
-
const path_1 = require("path");
|
|
7
|
-
const zlib_1 = require("zlib");
|
|
8
|
-
const render_1 = require("../render");
|
|
9
|
-
const header_1 = require("./header");
|
|
10
|
-
const mime_type_1 = require("./mime-type");
|
|
11
|
-
const server_cache_1 = require("./server-cache");
|
|
12
|
-
const server_cache_config_1 = require("./server-cache-config");
|
|
13
|
-
class StaticHandler {
|
|
14
|
-
DEFAULT_CONTENT_TYPE = 'application/octet-stream';
|
|
15
|
-
maxFileSize;
|
|
16
|
-
cache;
|
|
17
|
-
rules;
|
|
18
|
-
/**
|
|
19
|
-
* 静态处理器。规则说明:
|
|
20
|
-
* 请求路径仅支持前缀匹配,不支持通配符,比如 /a/demo.html 可以匹配 /a 路径,响应配置的文件目录下的 demo.html 文件。
|
|
21
|
-
* 路径配置是有优先级的,如果访问 /a/b/music.mp3 则会匹配到 /a/b 的配置,而不是 /a ,因为 /a/b 的配置更详细,优先级也更高,
|
|
22
|
-
* 并且如果从 /a/b 配置的目录下没有找到文件,也不会再尝试 /a 的配置。
|
|
23
|
-
*
|
|
24
|
-
* 静态文件同时也支持主页自动映射,比如访问 /a/b/c ,会匹配到 /a/b 的配置,然后在配置的文件目录下寻找文件 c ,
|
|
25
|
-
* 如果找不到则尝试寻找目录 c 下的 index.html 文件。
|
|
26
|
-
* @param rules 规则设置
|
|
27
|
-
*/
|
|
28
|
-
constructor(rules) {
|
|
29
|
-
const config = (0, server_cache_config_1.getConfig)();
|
|
30
|
-
this.maxFileSize = (0, server_cache_config_1.parseSize)(config.maxFileSize);
|
|
31
|
-
if (config.enable) {
|
|
32
|
-
this.cache = new server_cache_1.ServerCache({ maxSize: (0, server_cache_config_1.parseSize)(config.maxSize), maxAge: config.maxAge });
|
|
33
|
-
}
|
|
34
|
-
this.rules = [];
|
|
35
|
-
// 规则解析,路径判定,设置的目录必须存在或可以被创建
|
|
36
|
-
// 重复记录表 ,作用是为了路径去重判定,可以提示哪些路径是重复的
|
|
37
|
-
const duplicateMap = new Map();
|
|
38
|
-
for (const entry of Object.entries(rules)) {
|
|
39
|
-
const [path, setting] = entry;
|
|
40
|
-
const dir = (0, path_1.isAbsolute)(setting.dir) ? setting.dir : (0, path_1.resolve)(process.cwd(), setting.dir);
|
|
41
|
-
if (!(0, fs_1.existsSync)(dir)) {
|
|
42
|
-
throw new Error(`Static file configuration error,path ${dir} does not exist,config dir:${setting.dir}`);
|
|
43
|
-
}
|
|
44
|
-
const statRes = (0, fs_1.statSync)(dir);
|
|
45
|
-
if (!statRes.isDirectory()) {
|
|
46
|
-
throw new Error(`Static file configuration error,path ${dir} is not a directory,config dir:${setting.dir}`);
|
|
47
|
-
}
|
|
48
|
-
let finalPath = path.startsWith('/') ? path : '/' + path;
|
|
49
|
-
// 保持以 / 结尾,为了匹配方便
|
|
50
|
-
if (!finalPath.endsWith('/')) {
|
|
51
|
-
finalPath += '/';
|
|
52
|
-
}
|
|
53
|
-
if (duplicateMap.has(finalPath)) {
|
|
54
|
-
throw new Error(`Static path duplicated: ${duplicateMap.get(finalPath)} and ${path}`);
|
|
55
|
-
}
|
|
56
|
-
duplicateMap.set(finalPath, path);
|
|
57
|
-
this.rules.push({ path: finalPath, dir, cacheAge: setting.cacheAge || 0 });
|
|
58
|
-
}
|
|
59
|
-
// 优先级排序
|
|
60
|
-
this.rules.sort((o1, o2) => {
|
|
61
|
-
let priority1 = o1.path === '/' ? -1 : o1.path.split('/').length;
|
|
62
|
-
let priority2 = o2.path === '/' ? -1 : o2.path.split('/').length;
|
|
63
|
-
// 如果 o1 优先级高,就应该排前面,返回小于0的值,反之亦然\
|
|
64
|
-
// 前面的优先级值是值越大优先级越高,反过来减
|
|
65
|
-
return priority2 - priority1;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* 处理静态文件 get 请求
|
|
70
|
-
* @param request
|
|
71
|
-
* @param response
|
|
72
|
-
* @param path
|
|
73
|
-
* @returns 是否能够处理,如果因为找不到文件或其它原因无法处理则返回 false ,由后续流程继续处理
|
|
74
|
-
*/
|
|
75
|
-
async handleGet(request, response, path) {
|
|
76
|
-
// 解析消息头
|
|
77
|
-
const headersInfo = (0, header_1.parseHeaders)(request.headers);
|
|
78
|
-
// 构建文件信息
|
|
79
|
-
let file = await this.buildRespFile(path, headersInfo);
|
|
80
|
-
if (!file) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
// content-type
|
|
84
|
-
response.setHeader('Content-Type', file.mimeType);
|
|
85
|
-
// client cache
|
|
86
|
-
if (file.maxAge === 0) {
|
|
87
|
-
response.setHeader('Cache-Control', 'no-store');
|
|
88
|
-
}
|
|
89
|
-
else if (file.maxAge) {
|
|
90
|
-
response.setHeader('Cache-Control', `max-age=${file.maxAge}`);
|
|
91
|
-
}
|
|
92
|
-
response.setHeader('Last-Modified', file.mtime.toUTCString());
|
|
93
|
-
// 开始响应,先判定 if-modified-since
|
|
94
|
-
if (headersInfo.ifModifiedSince) {
|
|
95
|
-
if (headersInfo.ifModifiedSince >= file.mtime) {
|
|
96
|
-
response.statusCode = 304;
|
|
97
|
-
response.end();
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (headersInfo.range) {
|
|
102
|
-
const { start } = headersInfo.range;
|
|
103
|
-
if (isNaN(start) || start < 0) {
|
|
104
|
-
(0, render_1.renderError)(response, `Range not satisfiable,start is ${start}.`, 416);
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
const maxEnd = file.size - 1;
|
|
108
|
-
let end = headersInfo.range.end ? headersInfo.range.end : maxEnd;
|
|
109
|
-
// 校验 end
|
|
110
|
-
if (end <= start) {
|
|
111
|
-
(0, render_1.renderError)(response, `Range not satisfiable,end must be greater than start.`, 416);
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
if (end > maxEnd) {
|
|
115
|
-
(0, render_1.renderError)(response, `Range not satisfiable,end must not be greater than ${maxEnd}.`, 416);
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
// range 是前后都包含的,详细可以参考规范:
|
|
119
|
-
// https://www.rfc-editor.org/rfc/rfc9110#field.range
|
|
120
|
-
// 但是 buffer.subarray 中包含前不包含后,createReadStream 是前后都包含的,需要注意
|
|
121
|
-
// 还需要注意 gzip 编码
|
|
122
|
-
response.setHeader('Content-Range', `bytes ${start}-${end}/${file.size}`);
|
|
123
|
-
response.statusCode = 206;
|
|
124
|
-
if (file.bufferOrPath instanceof Buffer) {
|
|
125
|
-
let buffer = file.bufferOrPath.subarray(start, end + 1);
|
|
126
|
-
if (headersInfo.gzip) {
|
|
127
|
-
buffer = await new Promise((resolve, reject) => {
|
|
128
|
-
(0, zlib_1.gzip)(buffer, (err, res) => {
|
|
129
|
-
if (err) {
|
|
130
|
-
reject(err);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
resolve(res);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
response.setHeader('Content-Encoding', 'gzip');
|
|
137
|
-
}
|
|
138
|
-
await this.endRespWithBuffer(response, buffer);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
const filePath = file.bufferOrPath;
|
|
142
|
-
// 文件处理
|
|
143
|
-
await new Promise((resolve, reject) => {
|
|
144
|
-
response.setHeader('Content-Encoding', 'gzip');
|
|
145
|
-
response.once('finish', resolve).once('error', reject);
|
|
146
|
-
if (headersInfo.gzip) {
|
|
147
|
-
(0, fs_1.createReadStream)(filePath, { start, end }).pipe((0, zlib_1.createGzip)()).pipe(response);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
(0, fs_1.createReadStream)(filePath, { start, end }).pipe(response);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
// gzip
|
|
157
|
-
if (headersInfo.gzip) {
|
|
158
|
-
response.setHeader('Content-Encoding', 'gzip');
|
|
159
|
-
if (file.bufferOrPath instanceof Buffer) {
|
|
160
|
-
// buffer 是缓存的文件,gzip 编码缓存的文件即是已经压缩后的文件
|
|
161
|
-
await this.endRespWithBuffer(response, file.bufferOrPath);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
const path = file.bufferOrPath;
|
|
165
|
-
await new Promise((resolve, reject) => {
|
|
166
|
-
response.once('finish', resolve).once('error', reject);
|
|
167
|
-
(0, fs_1.createReadStream)(path).pipe((0, zlib_1.createGzip)()).pipe(response);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
// 普通的整个文件请求处理
|
|
173
|
-
if (file.bufferOrPath instanceof Buffer) {
|
|
174
|
-
await this.endRespWithBuffer(response, file.bufferOrPath);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
const filePath = file.bufferOrPath;
|
|
178
|
-
await new Promise((resolve, reject) => {
|
|
179
|
-
response.once('finish', resolve).once('error', reject);
|
|
180
|
-
(0, fs_1.createReadStream)(filePath).pipe(response);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* 处理 head 请求,不响应正文内容,如果文件可以被找到,仅仅响应以下的消息头:
|
|
187
|
-
* Content-Length
|
|
188
|
-
* Content-Type
|
|
189
|
-
* Last-Modified
|
|
190
|
-
* Cache-Control
|
|
191
|
-
*
|
|
192
|
-
* @param path 请求路径
|
|
193
|
-
* @param response
|
|
194
|
-
* @returns 是否成功处理
|
|
195
|
-
*/
|
|
196
|
-
async handleHead(path, response) {
|
|
197
|
-
// 构建文件信息
|
|
198
|
-
let file = await this.buildRespFile(path, { gzip: false });
|
|
199
|
-
if (!file) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
response.setHeader('Content-Length', file.size);
|
|
203
|
-
response.setHeader('Content-Type', file.mimeType);
|
|
204
|
-
response.setHeader('Last-Modified', file.mtime.toUTCString());
|
|
205
|
-
if (file.maxAge === 0) {
|
|
206
|
-
response.setHeader('Cache-Control', 'no-store');
|
|
207
|
-
}
|
|
208
|
-
else if (file.maxAge) {
|
|
209
|
-
response.setHeader('Cache-Control', `max-age=${file.maxAge}`);
|
|
210
|
-
}
|
|
211
|
-
response.end();
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
async endRespWithBuffer(response, buffer) {
|
|
215
|
-
if (response.writableEnded || response.destroyed) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
await new Promise((resolve, reject) => {
|
|
219
|
-
response.write(buffer, err => {
|
|
220
|
-
if (err) {
|
|
221
|
-
reject(err);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
resolve();
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
await new Promise((resolve, reject) => {
|
|
229
|
-
response.end(resolve);
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* 构建响应文件
|
|
234
|
-
*
|
|
235
|
-
* @param path
|
|
236
|
-
* @param headers
|
|
237
|
-
* @returns
|
|
238
|
-
*/
|
|
239
|
-
async buildRespFile(path, headers) {
|
|
240
|
-
if (!this.cache) {
|
|
241
|
-
const file = await this.findFile(path);
|
|
242
|
-
if (!file) {
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
let { mtime } = file.stats;
|
|
246
|
-
mtime.setMilliseconds(0);
|
|
247
|
-
return {
|
|
248
|
-
maxAge: file.maxAge,
|
|
249
|
-
mtime: mtime,
|
|
250
|
-
size: file.stats.size,
|
|
251
|
-
bufferOrPath: file.filePath,
|
|
252
|
-
mimeType: (0, mime_type_1.decideContentType)(file.filePath) || this.DEFAULT_CONTENT_TYPE
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
const cacheingGzip = headers.gzip && !headers.range;
|
|
256
|
-
const key = cacheingGzip ? `gzip-${path}` : path;
|
|
257
|
-
let file;
|
|
258
|
-
const cachedVal = await this.cache.computeIfAbsent(key, async () => {
|
|
259
|
-
file = (await this.findFile(path)) || undefined;
|
|
260
|
-
if (!file) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
// 文件太大不能被缓存
|
|
264
|
-
if (file.stats.size > this.maxFileSize) {
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
let buffer = await (0, promises_1.readFile)(file.filePath);
|
|
268
|
-
// gzip 存储压缩后的文件
|
|
269
|
-
if (cacheingGzip) {
|
|
270
|
-
buffer = await new Promise((resolve, reject) => {
|
|
271
|
-
(0, zlib_1.gzip)(buffer, (err, res) => {
|
|
272
|
-
if (err) {
|
|
273
|
-
reject(err);
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
resolve(res);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
let { mtime } = file.stats;
|
|
282
|
-
mtime.setMilliseconds(0);
|
|
283
|
-
return {
|
|
284
|
-
buffer,
|
|
285
|
-
mtime: mtime,
|
|
286
|
-
cacheAge: file.maxAge,
|
|
287
|
-
mimeType: (0, mime_type_1.decideContentType)(file.filePath) || this.DEFAULT_CONTENT_TYPE
|
|
288
|
-
};
|
|
289
|
-
});
|
|
290
|
-
if (cachedVal) {
|
|
291
|
-
return {
|
|
292
|
-
maxAge: cachedVal.cacheAge,
|
|
293
|
-
mtime: cachedVal.mtime,
|
|
294
|
-
size: cachedVal.buffer.length,
|
|
295
|
-
bufferOrPath: cachedVal.buffer,
|
|
296
|
-
mimeType: cachedVal.mimeType
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
// 判定文件大小
|
|
300
|
-
if (file) {
|
|
301
|
-
let { mtime } = file.stats;
|
|
302
|
-
mtime.setMilliseconds(0);
|
|
303
|
-
return {
|
|
304
|
-
maxAge: file.maxAge,
|
|
305
|
-
mtime: mtime,
|
|
306
|
-
size: file.stats.size,
|
|
307
|
-
bufferOrPath: file.filePath,
|
|
308
|
-
mimeType: (0, mime_type_1.decideContentType)(file.filePath) || this.DEFAULT_CONTENT_TYPE
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* 根据路径查找文件
|
|
315
|
-
* @param path 访问路径
|
|
316
|
-
* @returns 返回被查询到的文件信息,如果找不到则返回 null
|
|
317
|
-
*/
|
|
318
|
-
async findFile(path) {
|
|
319
|
-
let matchedRule;
|
|
320
|
-
for (const rule of this.rules) {
|
|
321
|
-
if (rule.path === '/') {
|
|
322
|
-
matchedRule = rule;
|
|
323
|
-
break;
|
|
324
|
-
}
|
|
325
|
-
if (path.startsWith(rule.path)) {
|
|
326
|
-
matchedRule = rule;
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
// 匹配失败
|
|
331
|
-
if (!matchedRule) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
// 构建地址
|
|
335
|
-
let finalPath = matchedRule.path === '/' ? path : path.substring(matchedRule.path.length);
|
|
336
|
-
if (finalPath.startsWith('/')) {
|
|
337
|
-
finalPath = finalPath.substring(1);
|
|
338
|
-
}
|
|
339
|
-
let filePath = (0, path_1.resolve)(matchedRule.dir, finalPath);
|
|
340
|
-
if (!(0, fs_1.existsSync)(filePath)) {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
const fileStat = await (0, promises_1.stat)(filePath);
|
|
344
|
-
// 如果是目录,尝试查找 index.html
|
|
345
|
-
if (fileStat.isDirectory()) {
|
|
346
|
-
const indexPath = (0, path_1.resolve)(filePath, 'index.html');
|
|
347
|
-
if (!(0, fs_1.existsSync)(indexPath)) {
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
const indexStat = await (0, promises_1.stat)(indexPath);
|
|
351
|
-
if (!indexStat.isFile()) {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
return { filePath: indexPath, stats: indexStat };
|
|
355
|
-
}
|
|
356
|
-
if (!fileStat.isFile()) {
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
return { filePath, stats: fileStat, maxAge: matchedRule.cacheAge };
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* 删除服务器端静态资源缓存
|
|
363
|
-
* @param path
|
|
364
|
-
*/
|
|
365
|
-
removeServerCache(path) {
|
|
366
|
-
if (this.cache) {
|
|
367
|
-
this.cache.remove(path);
|
|
368
|
-
this.cache.remove(`gzip-${path}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
exports.StaticHandler = StaticHandler;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StaticHandler = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const zlib_1 = require("zlib");
|
|
8
|
+
const render_1 = require("../render");
|
|
9
|
+
const header_1 = require("./header");
|
|
10
|
+
const mime_type_1 = require("./mime-type");
|
|
11
|
+
const server_cache_1 = require("./server-cache");
|
|
12
|
+
const server_cache_config_1 = require("./server-cache-config");
|
|
13
|
+
class StaticHandler {
|
|
14
|
+
DEFAULT_CONTENT_TYPE = 'application/octet-stream';
|
|
15
|
+
maxFileSize;
|
|
16
|
+
cache;
|
|
17
|
+
rules;
|
|
18
|
+
/**
|
|
19
|
+
* 静态处理器。规则说明:
|
|
20
|
+
* 请求路径仅支持前缀匹配,不支持通配符,比如 /a/demo.html 可以匹配 /a 路径,响应配置的文件目录下的 demo.html 文件。
|
|
21
|
+
* 路径配置是有优先级的,如果访问 /a/b/music.mp3 则会匹配到 /a/b 的配置,而不是 /a ,因为 /a/b 的配置更详细,优先级也更高,
|
|
22
|
+
* 并且如果从 /a/b 配置的目录下没有找到文件,也不会再尝试 /a 的配置。
|
|
23
|
+
*
|
|
24
|
+
* 静态文件同时也支持主页自动映射,比如访问 /a/b/c ,会匹配到 /a/b 的配置,然后在配置的文件目录下寻找文件 c ,
|
|
25
|
+
* 如果找不到则尝试寻找目录 c 下的 index.html 文件。
|
|
26
|
+
* @param rules 规则设置
|
|
27
|
+
*/
|
|
28
|
+
constructor(rules) {
|
|
29
|
+
const config = (0, server_cache_config_1.getConfig)();
|
|
30
|
+
this.maxFileSize = (0, server_cache_config_1.parseSize)(config.maxFileSize);
|
|
31
|
+
if (config.enable) {
|
|
32
|
+
this.cache = new server_cache_1.ServerCache({ maxSize: (0, server_cache_config_1.parseSize)(config.maxSize), maxAge: config.maxAge });
|
|
33
|
+
}
|
|
34
|
+
this.rules = [];
|
|
35
|
+
// 规则解析,路径判定,设置的目录必须存在或可以被创建
|
|
36
|
+
// 重复记录表 ,作用是为了路径去重判定,可以提示哪些路径是重复的
|
|
37
|
+
const duplicateMap = new Map();
|
|
38
|
+
for (const entry of Object.entries(rules)) {
|
|
39
|
+
const [path, setting] = entry;
|
|
40
|
+
const dir = (0, path_1.isAbsolute)(setting.dir) ? setting.dir : (0, path_1.resolve)(process.cwd(), setting.dir);
|
|
41
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
42
|
+
throw new Error(`Static file configuration error,path ${dir} does not exist,config dir:${setting.dir}`);
|
|
43
|
+
}
|
|
44
|
+
const statRes = (0, fs_1.statSync)(dir);
|
|
45
|
+
if (!statRes.isDirectory()) {
|
|
46
|
+
throw new Error(`Static file configuration error,path ${dir} is not a directory,config dir:${setting.dir}`);
|
|
47
|
+
}
|
|
48
|
+
let finalPath = path.startsWith('/') ? path : '/' + path;
|
|
49
|
+
// 保持以 / 结尾,为了匹配方便
|
|
50
|
+
if (!finalPath.endsWith('/')) {
|
|
51
|
+
finalPath += '/';
|
|
52
|
+
}
|
|
53
|
+
if (duplicateMap.has(finalPath)) {
|
|
54
|
+
throw new Error(`Static path duplicated: ${duplicateMap.get(finalPath)} and ${path}`);
|
|
55
|
+
}
|
|
56
|
+
duplicateMap.set(finalPath, path);
|
|
57
|
+
this.rules.push({ path: finalPath, dir, cacheAge: setting.cacheAge || 0 });
|
|
58
|
+
}
|
|
59
|
+
// 优先级排序
|
|
60
|
+
this.rules.sort((o1, o2) => {
|
|
61
|
+
let priority1 = o1.path === '/' ? -1 : o1.path.split('/').length;
|
|
62
|
+
let priority2 = o2.path === '/' ? -1 : o2.path.split('/').length;
|
|
63
|
+
// 如果 o1 优先级高,就应该排前面,返回小于0的值,反之亦然\
|
|
64
|
+
// 前面的优先级值是值越大优先级越高,反过来减
|
|
65
|
+
return priority2 - priority1;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 处理静态文件 get 请求
|
|
70
|
+
* @param request
|
|
71
|
+
* @param response
|
|
72
|
+
* @param path
|
|
73
|
+
* @returns 是否能够处理,如果因为找不到文件或其它原因无法处理则返回 false ,由后续流程继续处理
|
|
74
|
+
*/
|
|
75
|
+
async handleGet(request, response, path) {
|
|
76
|
+
// 解析消息头
|
|
77
|
+
const headersInfo = (0, header_1.parseHeaders)(request.headers);
|
|
78
|
+
// 构建文件信息
|
|
79
|
+
let file = await this.buildRespFile(path, headersInfo);
|
|
80
|
+
if (!file) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
// content-type
|
|
84
|
+
response.setHeader('Content-Type', file.mimeType);
|
|
85
|
+
// client cache
|
|
86
|
+
if (file.maxAge === 0) {
|
|
87
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
88
|
+
}
|
|
89
|
+
else if (file.maxAge) {
|
|
90
|
+
response.setHeader('Cache-Control', `max-age=${file.maxAge}`);
|
|
91
|
+
}
|
|
92
|
+
response.setHeader('Last-Modified', file.mtime.toUTCString());
|
|
93
|
+
// 开始响应,先判定 if-modified-since
|
|
94
|
+
if (headersInfo.ifModifiedSince) {
|
|
95
|
+
if (headersInfo.ifModifiedSince >= file.mtime) {
|
|
96
|
+
response.statusCode = 304;
|
|
97
|
+
response.end();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (headersInfo.range) {
|
|
102
|
+
const { start } = headersInfo.range;
|
|
103
|
+
if (isNaN(start) || start < 0) {
|
|
104
|
+
(0, render_1.renderError)(response, `Range not satisfiable,start is ${start}.`, 416);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const maxEnd = file.size - 1;
|
|
108
|
+
let end = headersInfo.range.end ? headersInfo.range.end : maxEnd;
|
|
109
|
+
// 校验 end
|
|
110
|
+
if (end <= start) {
|
|
111
|
+
(0, render_1.renderError)(response, `Range not satisfiable,end must be greater than start.`, 416);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (end > maxEnd) {
|
|
115
|
+
(0, render_1.renderError)(response, `Range not satisfiable,end must not be greater than ${maxEnd}.`, 416);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// range 是前后都包含的,详细可以参考规范:
|
|
119
|
+
// https://www.rfc-editor.org/rfc/rfc9110#field.range
|
|
120
|
+
// 但是 buffer.subarray 中包含前不包含后,createReadStream 是前后都包含的,需要注意
|
|
121
|
+
// 还需要注意 gzip 编码
|
|
122
|
+
response.setHeader('Content-Range', `bytes ${start}-${end}/${file.size}`);
|
|
123
|
+
response.statusCode = 206;
|
|
124
|
+
if (file.bufferOrPath instanceof Buffer) {
|
|
125
|
+
let buffer = file.bufferOrPath.subarray(start, end + 1);
|
|
126
|
+
if (headersInfo.gzip) {
|
|
127
|
+
buffer = await new Promise((resolve, reject) => {
|
|
128
|
+
(0, zlib_1.gzip)(buffer, (err, res) => {
|
|
129
|
+
if (err) {
|
|
130
|
+
reject(err);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
resolve(res);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
137
|
+
}
|
|
138
|
+
await this.endRespWithBuffer(response, buffer);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const filePath = file.bufferOrPath;
|
|
142
|
+
// 文件处理
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
145
|
+
response.once('finish', resolve).once('error', reject);
|
|
146
|
+
if (headersInfo.gzip) {
|
|
147
|
+
(0, fs_1.createReadStream)(filePath, { start, end }).pipe((0, zlib_1.createGzip)()).pipe(response);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
(0, fs_1.createReadStream)(filePath, { start, end }).pipe(response);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
// gzip
|
|
157
|
+
if (headersInfo.gzip) {
|
|
158
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
159
|
+
if (file.bufferOrPath instanceof Buffer) {
|
|
160
|
+
// buffer 是缓存的文件,gzip 编码缓存的文件即是已经压缩后的文件
|
|
161
|
+
await this.endRespWithBuffer(response, file.bufferOrPath);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const path = file.bufferOrPath;
|
|
165
|
+
await new Promise((resolve, reject) => {
|
|
166
|
+
response.once('finish', resolve).once('error', reject);
|
|
167
|
+
(0, fs_1.createReadStream)(path).pipe((0, zlib_1.createGzip)()).pipe(response);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
// 普通的整个文件请求处理
|
|
173
|
+
if (file.bufferOrPath instanceof Buffer) {
|
|
174
|
+
await this.endRespWithBuffer(response, file.bufferOrPath);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const filePath = file.bufferOrPath;
|
|
178
|
+
await new Promise((resolve, reject) => {
|
|
179
|
+
response.once('finish', resolve).once('error', reject);
|
|
180
|
+
(0, fs_1.createReadStream)(filePath).pipe(response);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 处理 head 请求,不响应正文内容,如果文件可以被找到,仅仅响应以下的消息头:
|
|
187
|
+
* Content-Length
|
|
188
|
+
* Content-Type
|
|
189
|
+
* Last-Modified
|
|
190
|
+
* Cache-Control
|
|
191
|
+
*
|
|
192
|
+
* @param path 请求路径
|
|
193
|
+
* @param response
|
|
194
|
+
* @returns 是否成功处理
|
|
195
|
+
*/
|
|
196
|
+
async handleHead(path, response) {
|
|
197
|
+
// 构建文件信息
|
|
198
|
+
let file = await this.buildRespFile(path, { gzip: false });
|
|
199
|
+
if (!file) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
response.setHeader('Content-Length', file.size);
|
|
203
|
+
response.setHeader('Content-Type', file.mimeType);
|
|
204
|
+
response.setHeader('Last-Modified', file.mtime.toUTCString());
|
|
205
|
+
if (file.maxAge === 0) {
|
|
206
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
207
|
+
}
|
|
208
|
+
else if (file.maxAge) {
|
|
209
|
+
response.setHeader('Cache-Control', `max-age=${file.maxAge}`);
|
|
210
|
+
}
|
|
211
|
+
response.end();
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
async endRespWithBuffer(response, buffer) {
|
|
215
|
+
if (response.writableEnded || response.destroyed) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
await new Promise((resolve, reject) => {
|
|
219
|
+
response.write(buffer, err => {
|
|
220
|
+
if (err) {
|
|
221
|
+
reject(err);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
resolve();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
await new Promise((resolve, reject) => {
|
|
229
|
+
response.end(resolve);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 构建响应文件
|
|
234
|
+
*
|
|
235
|
+
* @param path
|
|
236
|
+
* @param headers
|
|
237
|
+
* @returns
|
|
238
|
+
*/
|
|
239
|
+
async buildRespFile(path, headers) {
|
|
240
|
+
if (!this.cache) {
|
|
241
|
+
const file = await this.findFile(path);
|
|
242
|
+
if (!file) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
let { mtime } = file.stats;
|
|
246
|
+
mtime.setMilliseconds(0);
|
|
247
|
+
return {
|
|
248
|
+
maxAge: file.maxAge,
|
|
249
|
+
mtime: mtime,
|
|
250
|
+
size: file.stats.size,
|
|
251
|
+
bufferOrPath: file.filePath,
|
|
252
|
+
mimeType: (0, mime_type_1.decideContentType)(file.filePath) || this.DEFAULT_CONTENT_TYPE
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const cacheingGzip = headers.gzip && !headers.range;
|
|
256
|
+
const key = cacheingGzip ? `gzip-${path}` : path;
|
|
257
|
+
let file;
|
|
258
|
+
const cachedVal = await this.cache.computeIfAbsent(key, async () => {
|
|
259
|
+
file = (await this.findFile(path)) || undefined;
|
|
260
|
+
if (!file) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
// 文件太大不能被缓存
|
|
264
|
+
if (file.stats.size > this.maxFileSize) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
let buffer = await (0, promises_1.readFile)(file.filePath);
|
|
268
|
+
// gzip 存储压缩后的文件
|
|
269
|
+
if (cacheingGzip) {
|
|
270
|
+
buffer = await new Promise((resolve, reject) => {
|
|
271
|
+
(0, zlib_1.gzip)(buffer, (err, res) => {
|
|
272
|
+
if (err) {
|
|
273
|
+
reject(err);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
resolve(res);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
let { mtime } = file.stats;
|
|
282
|
+
mtime.setMilliseconds(0);
|
|
283
|
+
return {
|
|
284
|
+
buffer,
|
|
285
|
+
mtime: mtime,
|
|
286
|
+
cacheAge: file.maxAge,
|
|
287
|
+
mimeType: (0, mime_type_1.decideContentType)(file.filePath) || this.DEFAULT_CONTENT_TYPE
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
if (cachedVal) {
|
|
291
|
+
return {
|
|
292
|
+
maxAge: cachedVal.cacheAge,
|
|
293
|
+
mtime: cachedVal.mtime,
|
|
294
|
+
size: cachedVal.buffer.length,
|
|
295
|
+
bufferOrPath: cachedVal.buffer,
|
|
296
|
+
mimeType: cachedVal.mimeType
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// 判定文件大小
|
|
300
|
+
if (file) {
|
|
301
|
+
let { mtime } = file.stats;
|
|
302
|
+
mtime.setMilliseconds(0);
|
|
303
|
+
return {
|
|
304
|
+
maxAge: file.maxAge,
|
|
305
|
+
mtime: mtime,
|
|
306
|
+
size: file.stats.size,
|
|
307
|
+
bufferOrPath: file.filePath,
|
|
308
|
+
mimeType: (0, mime_type_1.decideContentType)(file.filePath) || this.DEFAULT_CONTENT_TYPE
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 根据路径查找文件
|
|
315
|
+
* @param path 访问路径
|
|
316
|
+
* @returns 返回被查询到的文件信息,如果找不到则返回 null
|
|
317
|
+
*/
|
|
318
|
+
async findFile(path) {
|
|
319
|
+
let matchedRule;
|
|
320
|
+
for (const rule of this.rules) {
|
|
321
|
+
if (rule.path === '/') {
|
|
322
|
+
matchedRule = rule;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
if (path.startsWith(rule.path)) {
|
|
326
|
+
matchedRule = rule;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// 匹配失败
|
|
331
|
+
if (!matchedRule) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
// 构建地址
|
|
335
|
+
let finalPath = matchedRule.path === '/' ? path : path.substring(matchedRule.path.length);
|
|
336
|
+
if (finalPath.startsWith('/')) {
|
|
337
|
+
finalPath = finalPath.substring(1);
|
|
338
|
+
}
|
|
339
|
+
let filePath = (0, path_1.resolve)(matchedRule.dir, finalPath);
|
|
340
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const fileStat = await (0, promises_1.stat)(filePath);
|
|
344
|
+
// 如果是目录,尝试查找 index.html
|
|
345
|
+
if (fileStat.isDirectory()) {
|
|
346
|
+
const indexPath = (0, path_1.resolve)(filePath, 'index.html');
|
|
347
|
+
if (!(0, fs_1.existsSync)(indexPath)) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
const indexStat = await (0, promises_1.stat)(indexPath);
|
|
351
|
+
if (!indexStat.isFile()) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return { filePath: indexPath, stats: indexStat };
|
|
355
|
+
}
|
|
356
|
+
if (!fileStat.isFile()) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
return { filePath, stats: fileStat, maxAge: matchedRule.cacheAge };
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 删除服务器端静态资源缓存
|
|
363
|
+
* @param path
|
|
364
|
+
*/
|
|
365
|
+
removeServerCache(path) {
|
|
366
|
+
if (this.cache) {
|
|
367
|
+
this.cache.remove(path);
|
|
368
|
+
this.cache.remove(`gzip-${path}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
exports.StaticHandler = StaticHandler;
|