wok-server 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/cache/cache.js +94 -0
- package/dist/cache/config.js +19 -0
- package/dist/cache/index.js +27 -0
- package/dist/cache/purge-task.js +56 -0
- package/dist/cache/stat.js +47 -0
- package/dist/config/convert.js +36 -0
- package/dist/config/exception.js +14 -0
- package/dist/config/index.js +67 -0
- package/dist/http-client/index.js +132 -0
- package/dist/i18n/ar.js +17 -0
- package/dist/i18n/de.js +17 -0
- package/dist/i18n/en-us.js +17 -0
- package/dist/i18n/es.js +17 -0
- package/dist/i18n/fr.js +17 -0
- package/dist/i18n/i18n.js +231 -0
- package/dist/i18n/index.js +52 -0
- package/dist/i18n/ja.js +17 -0
- package/dist/i18n/ko.js +17 -0
- package/dist/i18n/msg.js +2 -0
- package/dist/i18n/pt.js +17 -0
- package/dist/i18n/ru.js +17 -0
- package/dist/i18n/tag.js +18 -0
- package/dist/i18n/zh-HK.js +17 -0
- package/dist/i18n/zh-TW.js +17 -0
- package/dist/i18n/zh-cn.js +17 -0
- package/dist/index.js +13 -0
- package/dist/log/config.js +28 -0
- package/dist/log/date.js +21 -0
- package/dist/log/file.js +79 -0
- package/dist/log/index.js +109 -0
- package/dist/log/level.js +39 -0
- package/dist/log/store.js +16 -0
- package/dist/mongodb/collection.js +2 -0
- package/dist/mongodb/config.js +34 -0
- package/dist/mongodb/doc.js +2 -0
- package/dist/mongodb/exception.js +14 -0
- package/dist/mongodb/index.js +58 -0
- package/dist/mongodb/manager/base.js +563 -0
- package/dist/mongodb/manager/index.js +63 -0
- package/dist/mongodb/manager/tx-strict.js +84 -0
- package/dist/mongodb/manager/tx.js +30 -0
- package/dist/mongodb/migration.js +52 -0
- package/dist/mvc/access-log.js +31 -0
- package/dist/mvc/config.js +20 -0
- package/dist/mvc/exchange.js +113 -0
- package/dist/mvc/handler/index.js +6 -0
- package/dist/mvc/handler/json.js +33 -0
- package/dist/mvc/handler/restful.js +35 -0
- package/dist/mvc/handler/upload.js +33 -0
- package/dist/mvc/index.js +316 -0
- package/dist/mvc/interceptor.js +2 -0
- package/dist/mvc/query.js +43 -0
- package/dist/mvc/render/file.js +177 -0
- package/dist/mvc/render/html/html.js +90 -0
- package/dist/mvc/render/html/index.js +18 -0
- package/dist/mvc/render/html/style.js +2 -0
- package/dist/mvc/render/index.js +7 -0
- package/dist/mvc/render/json.js +26 -0
- package/dist/mvc/render/text.js +16 -0
- package/dist/mvc/router.js +2 -0
- package/dist/mysql/config.js +49 -0
- package/dist/mysql/exception.js +14 -0
- package/dist/mysql/index.js +85 -0
- package/dist/mysql/manager/base.js +233 -0
- package/dist/mysql/manager/index.js +107 -0
- package/dist/mysql/manager/ops/count.js +20 -0
- package/dist/mysql/manager/ops/criteria.js +326 -0
- package/dist/mysql/manager/ops/delete.js +65 -0
- package/dist/mysql/manager/ops/exist.js +26 -0
- package/dist/mysql/manager/ops/find.js +111 -0
- package/dist/mysql/manager/ops/index.js +14 -0
- package/dist/mysql/manager/ops/insert.js +101 -0
- package/dist/mysql/manager/ops/modify.js +10 -0
- package/dist/mysql/manager/ops/paginate.js +23 -0
- package/dist/mysql/manager/ops/query.js +9 -0
- package/dist/mysql/manager/ops/update.js +201 -0
- package/dist/mysql/manager/tx-strict.js +98 -0
- package/dist/mysql/manager/tx.js +30 -0
- package/dist/mysql/manager/utils.js +56 -0
- package/dist/mysql/migration.js +136 -0
- package/dist/mysql/table-info.js +8 -0
- package/dist/task/daily.js +58 -0
- package/dist/task/fixed-delay.js +33 -0
- package/dist/task/fixed-rate.js +37 -0
- package/dist/task/index.js +9 -0
- package/dist/task/task.js +39 -0
- package/dist/validation/exception.js +44 -0
- package/dist/validation/index.js +29 -0
- package/dist/validation/validator/array.js +38 -0
- package/dist/validation/validator/enum.js +28 -0
- package/dist/validation/validator/index.js +14 -0
- package/dist/validation/validator/length.js +40 -0
- package/dist/validation/validator/max-length.js +35 -0
- package/dist/validation/validator/max.js +29 -0
- package/dist/validation/validator/min-length.js +33 -0
- package/dist/validation/validator/min.js +29 -0
- package/dist/validation/validator/not-blank.js +33 -0
- package/dist/validation/validator/not-null.js +21 -0
- package/dist/validation/validator/plain-obj.js +32 -0
- package/dist/validation/validator/regexp.js +30 -0
- package/documentation/en/index.md +1 -0
- package/documentation/zh-cn/cache.md +59 -0
- package/documentation/zh-cn/config.md +68 -0
- package/documentation/zh-cn/http-client.md +33 -0
- package/documentation/zh-cn/i18n.md +154 -0
- package/documentation/zh-cn/index.md +25 -0
- package/documentation/zh-cn/log.md +40 -0
- package/documentation/zh-cn/mongodb.md +262 -0
- package/documentation/zh-cn/mvc.md +430 -0
- package/documentation/zh-cn/mysql.md +389 -0
- package/documentation/zh-cn/task.md +50 -0
- package/documentation/zh-cn/test.md +57 -0
- package/documentation/zh-cn/validate.md +125 -0
- package/package.json +46 -0
- package/types/cache/cache.d.ts +52 -0
- package/types/cache/config.d.ts +32 -0
- package/types/cache/index.d.ts +2 -0
- package/types/cache/purge-task.d.ts +11 -0
- package/types/cache/stat.d.ts +26 -0
- package/types/config/convert.d.ts +6 -0
- package/types/config/exception.d.ts +7 -0
- package/types/config/index.d.ts +15 -0
- package/types/http-client/index.d.ts +71 -0
- package/types/i18n/ar.d.ts +2 -0
- package/types/i18n/de.d.ts +2 -0
- package/types/i18n/en-us.d.ts +2 -0
- package/types/i18n/es.d.ts +2 -0
- package/types/i18n/fr.d.ts +2 -0
- package/types/i18n/i18n.d.ts +102 -0
- package/types/i18n/index.d.ts +9 -0
- package/types/i18n/ja.d.ts +2 -0
- package/types/i18n/ko.d.ts +2 -0
- package/types/i18n/msg.d.ts +50 -0
- package/types/i18n/pt.d.ts +2 -0
- package/types/i18n/ru.d.ts +2 -0
- package/types/i18n/tag.d.ts +11 -0
- package/types/i18n/zh-HK.d.ts +2 -0
- package/types/i18n/zh-TW.d.ts +2 -0
- package/types/i18n/zh-cn.d.ts +2 -0
- package/types/index.d.ts +10 -0
- package/types/log/config.d.ts +27 -0
- package/types/log/date.d.ts +2 -0
- package/types/log/file.d.ts +5 -0
- package/types/log/index.d.ts +34 -0
- package/types/log/level.d.ts +15 -0
- package/types/log/store.d.ts +12 -0
- package/types/mongodb/collection.d.ts +25 -0
- package/types/mongodb/config.d.ts +45 -0
- package/types/mongodb/doc.d.ts +11 -0
- package/types/mongodb/exception.d.ts +7 -0
- package/types/mongodb/index.d.ts +29 -0
- package/types/mongodb/manager/base.d.ts +188 -0
- package/types/mongodb/manager/index.d.ts +38 -0
- package/types/mongodb/manager/tx-strict.d.ts +41 -0
- package/types/mongodb/manager/tx.d.ts +21 -0
- package/types/mongodb/migration.d.ts +12 -0
- package/types/mvc/access-log.d.ts +7 -0
- package/types/mvc/config.d.ts +30 -0
- package/types/mvc/exchange.d.ts +72 -0
- package/types/mvc/handler/index.d.ts +3 -0
- package/types/mvc/handler/json.d.ts +23 -0
- package/types/mvc/handler/restful.d.ts +11 -0
- package/types/mvc/handler/upload.d.ts +40 -0
- package/types/mvc/index.d.ts +49 -0
- package/types/mvc/interceptor.d.ts +11 -0
- package/types/mvc/query.d.ts +13 -0
- package/types/mvc/render/file.d.ts +10 -0
- package/types/mvc/render/html/html.d.ts +98 -0
- package/types/mvc/render/html/index.d.ts +11 -0
- package/types/mvc/render/html/style.d.ts +1201 -0
- package/types/mvc/render/index.d.ts +4 -0
- package/types/mvc/render/json.d.ts +17 -0
- package/types/mvc/render/text.d.ts +10 -0
- package/types/mvc/router.d.ts +11 -0
- package/types/mysql/config.d.ts +86 -0
- package/types/mysql/exception.d.ts +7 -0
- package/types/mysql/index.d.ts +16 -0
- package/types/mysql/manager/base.d.ts +158 -0
- package/types/mysql/manager/index.d.ts +36 -0
- package/types/mysql/manager/ops/count.d.ts +13 -0
- package/types/mysql/manager/ops/criteria.d.ts +120 -0
- package/types/mysql/manager/ops/delete.d.ts +46 -0
- package/types/mysql/manager/ops/exist.d.ts +6 -0
- package/types/mysql/manager/ops/find.d.ts +66 -0
- package/types/mysql/manager/ops/index.d.ts +10 -0
- package/types/mysql/manager/ops/insert.d.ts +18 -0
- package/types/mysql/manager/ops/modify.d.ts +3 -0
- package/types/mysql/manager/ops/paginate.d.ts +36 -0
- package/types/mysql/manager/ops/query.d.ts +3 -0
- package/types/mysql/manager/ops/update.d.ts +70 -0
- package/types/mysql/manager/tx-strict.d.ts +34 -0
- package/types/mysql/manager/tx.d.ts +15 -0
- package/types/mysql/manager/utils.d.ts +17 -0
- package/types/mysql/migration.d.ts +8 -0
- package/types/mysql/table-info.d.ts +36 -0
- package/types/task/daily.d.ts +15 -0
- package/types/task/fixed-delay.d.ts +8 -0
- package/types/task/fixed-rate.d.ts +8 -0
- package/types/task/index.d.ts +4 -0
- package/types/task/task.d.ts +33 -0
- package/types/validation/exception.d.ts +43 -0
- package/types/validation/index.d.ts +32 -0
- package/types/validation/validator/array.d.ts +5 -0
- package/types/validation/validator/enum.d.ts +8 -0
- package/types/validation/validator/index.d.ts +11 -0
- package/types/validation/validator/length.d.ts +10 -0
- package/types/validation/validator/max-length.d.ts +8 -0
- package/types/validation/validator/max.d.ts +7 -0
- package/types/validation/validator/min-length.d.ts +6 -0
- package/types/validation/validator/min.d.ts +7 -0
- package/types/validation/validator/not-blank.d.ts +7 -0
- package/types/validation/validator/not-null.d.ts +6 -0
- package/types/validation/validator/plain-obj.d.ts +7 -0
- package/types/validation/validator/regexp.d.ts +8 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongoTxSession = void 0;
|
|
4
|
+
const exception_1 = require("../exception");
|
|
5
|
+
const base_1 = require("./base");
|
|
6
|
+
/**
|
|
7
|
+
* 事务会话
|
|
8
|
+
*/
|
|
9
|
+
class MongoTxSession extends base_1.BaseMongoManager {
|
|
10
|
+
/**
|
|
11
|
+
* 中止标识
|
|
12
|
+
*/
|
|
13
|
+
#aborted = false;
|
|
14
|
+
constructor(config, db, session) {
|
|
15
|
+
super(config, db, session);
|
|
16
|
+
}
|
|
17
|
+
timingQuery(opts) {
|
|
18
|
+
if (this.#aborted) {
|
|
19
|
+
throw new exception_1.MongoDBException('Session has been aborted!');
|
|
20
|
+
}
|
|
21
|
+
return super.timingQuery(opts);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 中止,被中止后的会话不能再进行任何操作
|
|
25
|
+
*/
|
|
26
|
+
abort() {
|
|
27
|
+
this.#aborted = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.MongoTxSession = MongoTxSession;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrate = void 0;
|
|
4
|
+
const exception_1 = require("./exception");
|
|
5
|
+
const log_1 = require("../log");
|
|
6
|
+
/**
|
|
7
|
+
* 迁移
|
|
8
|
+
* @param db
|
|
9
|
+
* @param versionList
|
|
10
|
+
*/
|
|
11
|
+
async function migrate(db, versionList) {
|
|
12
|
+
let currentVersion = await getCurrentVersion(db);
|
|
13
|
+
// 逐个执行,迁移执行的逻辑是无法提供事务支持的
|
|
14
|
+
// 版本管理代码是自定义的,无法做到强制绑定 session
|
|
15
|
+
// 一旦出错,只能手动处理数据库,然后再重新执行程序,和 mysql 一样
|
|
16
|
+
for (let idx = 0; idx < versionList.length; idx++) {
|
|
17
|
+
if (idx <= currentVersion) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const migrationVersion = versionList[idx];
|
|
21
|
+
(0, log_1.getLogger)().info(`MongoDB migrating, version: ${idx}`);
|
|
22
|
+
await migrationVersion(db);
|
|
23
|
+
await updateVersion(db, idx);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.migrate = migrate;
|
|
27
|
+
const VERSION_COLLECTION_NAME = 'db_version';
|
|
28
|
+
const VERSION_RECORD_ID = 'db_version';
|
|
29
|
+
async function getCurrentVersion(db) {
|
|
30
|
+
const res = await db
|
|
31
|
+
.collection(VERSION_COLLECTION_NAME)
|
|
32
|
+
.findOne({ _id: VERSION_RECORD_ID });
|
|
33
|
+
return res ? res.version : -1;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 更新版本号
|
|
37
|
+
* @param db
|
|
38
|
+
* @param version
|
|
39
|
+
*/
|
|
40
|
+
async function updateVersion(db, version) {
|
|
41
|
+
const collection = db.collection(VERSION_COLLECTION_NAME);
|
|
42
|
+
const res = await collection.findOne({ _id: VERSION_RECORD_ID });
|
|
43
|
+
if (res) {
|
|
44
|
+
const rs = await collection.updateOne({ _id: VERSION_RECORD_ID }, { $set: { version } });
|
|
45
|
+
if (rs.modifiedCount !== 1) {
|
|
46
|
+
throw new exception_1.MongoDBException('Failed to update version.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
await collection.insertOne({ _id: VERSION_RECORD_ID, version: version });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.accessLogInterceptor = void 0;
|
|
4
|
+
const log_1 = require("../log");
|
|
5
|
+
const date_1 = require("../log/date");
|
|
6
|
+
/**
|
|
7
|
+
* 访问日志拦截器,记录请求信息
|
|
8
|
+
* @param exchange
|
|
9
|
+
* @param next
|
|
10
|
+
*/
|
|
11
|
+
const accessLogInterceptor = async (exchange, next) => {
|
|
12
|
+
const start = new Date().getTime();
|
|
13
|
+
const userAgent = exchange.request.headers['user-agent'];
|
|
14
|
+
const ip = exchange.request.socket.remoteAddress;
|
|
15
|
+
const { url, method } = exchange.request;
|
|
16
|
+
exchange.response.once('close', () => {
|
|
17
|
+
const status = exchange.response.statusCode;
|
|
18
|
+
const rt = new Date().getTime() - start;
|
|
19
|
+
(0, log_1.getLogger)().info(`[access-log]${JSON.stringify({
|
|
20
|
+
method,
|
|
21
|
+
url,
|
|
22
|
+
ip,
|
|
23
|
+
userAgent,
|
|
24
|
+
start: (0, date_1.formatDateTime)(new Date(start)),
|
|
25
|
+
rt,
|
|
26
|
+
status
|
|
27
|
+
})}`);
|
|
28
|
+
});
|
|
29
|
+
await next();
|
|
30
|
+
};
|
|
31
|
+
exports.accessLogInterceptor = accessLogInterceptor;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const validation_1 = require("../validation");
|
|
6
|
+
exports.config = (0, config_1.registerConfig)({
|
|
7
|
+
port: 8080,
|
|
8
|
+
timeout: 30000,
|
|
9
|
+
accessLog: false,
|
|
10
|
+
corsAllowHeaders: '*',
|
|
11
|
+
corsAllowMethods: '*',
|
|
12
|
+
corsAllowOrigin: '*'
|
|
13
|
+
}, 'SERVER', {
|
|
14
|
+
port: [(0, validation_1.notNull)(), (0, validation_1.min)(80), (0, validation_1.max)(65535)],
|
|
15
|
+
timeout: [(0, validation_1.notNull)(), (0, validation_1.min)(1000), (0, validation_1.max)(60000)],
|
|
16
|
+
accessLog: [(0, validation_1.notNull)()],
|
|
17
|
+
corsAllowOrigin: [(0, validation_1.notBlank)()],
|
|
18
|
+
corsAllowHeaders: [(0, validation_1.notBlank)()],
|
|
19
|
+
corsAllowMethods: [(0, validation_1.notBlank)()]
|
|
20
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServerExchange = void 0;
|
|
4
|
+
const query_1 = require("./query");
|
|
5
|
+
const render_1 = require("./render");
|
|
6
|
+
const text_1 = require("./render/text");
|
|
7
|
+
/**
|
|
8
|
+
* 服务的数据交换对象.
|
|
9
|
+
*/
|
|
10
|
+
class ServerExchange {
|
|
11
|
+
request;
|
|
12
|
+
response;
|
|
13
|
+
#bufferPromise;
|
|
14
|
+
constructor(request, response) {
|
|
15
|
+
this.request = request;
|
|
16
|
+
this.response = response;
|
|
17
|
+
}
|
|
18
|
+
bodyBuffer() {
|
|
19
|
+
if (this.#bufferPromise) {
|
|
20
|
+
return this.#bufferPromise;
|
|
21
|
+
}
|
|
22
|
+
this.#bufferPromise = new Promise((resolve, reject) => {
|
|
23
|
+
if (this.request.readableEnded) {
|
|
24
|
+
throw new Error('Request has ended!');
|
|
25
|
+
}
|
|
26
|
+
let body = [];
|
|
27
|
+
this.request
|
|
28
|
+
.resume()
|
|
29
|
+
.on('error', reject)
|
|
30
|
+
.on('data', chunk => body.push(chunk))
|
|
31
|
+
.on('end', () => resolve(Buffer.concat(body)));
|
|
32
|
+
});
|
|
33
|
+
return this.#bufferPromise;
|
|
34
|
+
}
|
|
35
|
+
async bodyText() {
|
|
36
|
+
const buffer = await this.bodyBuffer();
|
|
37
|
+
return buffer.toString('utf-8');
|
|
38
|
+
}
|
|
39
|
+
async bodyJson() {
|
|
40
|
+
const buffer = await this.bodyBuffer();
|
|
41
|
+
const bodyText = buffer.toString('utf-8');
|
|
42
|
+
if (!bodyText || !bodyText.trim()) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
return JSON.parse(bodyText);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 响应纯文本
|
|
49
|
+
* @param text 文本内容
|
|
50
|
+
* @param status 状态码,可选,默认 200
|
|
51
|
+
*/
|
|
52
|
+
respondText(text, status) {
|
|
53
|
+
(0, text_1.renderText)(this.response, text, status);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 响应 json
|
|
57
|
+
* @param json 任意可被 json 序列化的对象
|
|
58
|
+
* @param status 状态码,可选,默认 200
|
|
59
|
+
*/
|
|
60
|
+
respondJson(json, status) {
|
|
61
|
+
(0, render_1.renderJson)(this.response, json, status);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 响应错误信息提示,提供一个统一的格式封装,json 格式
|
|
65
|
+
* @param message 消息
|
|
66
|
+
* @param status 状态码,默认 400 ,表示业务错误
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
respondErrMsg(message, status) {
|
|
70
|
+
(0, render_1.renderError)(this.response, message, status);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 响应文件
|
|
74
|
+
* @param filePath 文件路径,绝对路径
|
|
75
|
+
* @param download 是否下载模式
|
|
76
|
+
* @returns
|
|
77
|
+
*/
|
|
78
|
+
respondFile(filePath, download) {
|
|
79
|
+
return (0, render_1.renderFile)(this.request, this.response, filePath, download);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 响应 html
|
|
83
|
+
* @param html html 内容,一个特定结构的对象或者是字符串
|
|
84
|
+
* @param status 状态码,可选,默认 200
|
|
85
|
+
*/
|
|
86
|
+
respondHtml(html, status) {
|
|
87
|
+
(0, render_1.renderHtml)(this.response, html, status);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 响应
|
|
91
|
+
* @param opts
|
|
92
|
+
*/
|
|
93
|
+
respond(opts) {
|
|
94
|
+
this.response.statusCode = opts.statusCode;
|
|
95
|
+
if (opts.headers) {
|
|
96
|
+
for (const key in opts.headers) {
|
|
97
|
+
this.response.setHeader(key, opts.headers[key]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (opts.body) {
|
|
101
|
+
this.response.write(opts.body);
|
|
102
|
+
}
|
|
103
|
+
this.response.end();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 解析 queryString
|
|
107
|
+
* @returns
|
|
108
|
+
*/
|
|
109
|
+
parseQueryString() {
|
|
110
|
+
return new query_1.QueryString(this.request.url || '');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.ServerExchange = ServerExchange;
|
|
@@ -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("./json"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./upload"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./restful"), exports);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createJsonHandler = void 0;
|
|
4
|
+
const i18n_1 = require("../../i18n");
|
|
5
|
+
const validation_1 = require("../../validation");
|
|
6
|
+
/**
|
|
7
|
+
* 创建 json 处理器..
|
|
8
|
+
* @param <REQ> 表示请求的 json 数据格式类型
|
|
9
|
+
* @param <RES> 表示响应的类型,可选,如果不需要响应 json 数据,则方法可以不返回任何值
|
|
10
|
+
* @param opts
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
function createJsonHandler(opts) {
|
|
14
|
+
return async function (exchange) {
|
|
15
|
+
if (!exchange.request.method || exchange.request.method.toUpperCase() !== 'POST') {
|
|
16
|
+
exchange.respondErrMsg('Method Not Allowed', 405);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const body = await exchange.bodyJson();
|
|
20
|
+
if (opts.validation) {
|
|
21
|
+
// 切换语言
|
|
22
|
+
(0, i18n_1.getI18n)().switchByRequest(exchange.request.headers);
|
|
23
|
+
(0, validation_1.validate)(body, opts.validation);
|
|
24
|
+
}
|
|
25
|
+
const res = await opts.handle(body, exchange);
|
|
26
|
+
if (!res) {
|
|
27
|
+
exchange.respond({ statusCode: 200 });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
exchange.respondJson(res);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
exports.createJsonHandler = createJsonHandler;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.restful = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 构建 restful 风格路由
|
|
6
|
+
*/
|
|
7
|
+
function restful(opts) {
|
|
8
|
+
return async (exchange) => {
|
|
9
|
+
const method = (exchange.request.method || '').toLowerCase();
|
|
10
|
+
let handler;
|
|
11
|
+
switch (method) {
|
|
12
|
+
case 'get':
|
|
13
|
+
handler = opts.get;
|
|
14
|
+
break;
|
|
15
|
+
case 'post':
|
|
16
|
+
handler = opts.post;
|
|
17
|
+
break;
|
|
18
|
+
case 'put':
|
|
19
|
+
handler = opts.put;
|
|
20
|
+
break;
|
|
21
|
+
case 'patch':
|
|
22
|
+
handler = opts.patch;
|
|
23
|
+
break;
|
|
24
|
+
case 'delete':
|
|
25
|
+
handler = opts.delete;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (!handler) {
|
|
29
|
+
exchange.respondErrMsg(`${method} ${exchange.request.url} not found`, 404);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await handler(exchange);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
exports.restful = restful;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createUploadHandler = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 创建上传处理器. 上传要求一次只能传输一个文件,并且请求正文就是文件二进制内容。
|
|
6
|
+
* 必须是 post 请求,额外的参数只能通过 queryString 或 header 来传递。
|
|
7
|
+
* 和 postman 中的 binary 模式是一致的,这样上传文件性能会好一些,但是局限性也很大。
|
|
8
|
+
*
|
|
9
|
+
* @param opts
|
|
10
|
+
* @param <RES> 响应json数据类型
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
function createUploadHandler(opts) {
|
|
14
|
+
return async function (exchange) {
|
|
15
|
+
if (!exchange.request.method || exchange.request.method.toUpperCase() !== 'POST') {
|
|
16
|
+
exchange.respondErrMsg('Method Not Allowed', 405);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const body = await exchange.bodyBuffer();
|
|
20
|
+
const { url, headers } = exchange.request;
|
|
21
|
+
const res = await opts.handle(body, {
|
|
22
|
+
url: url || '',
|
|
23
|
+
headers,
|
|
24
|
+
query: exchange.parseQueryString()
|
|
25
|
+
});
|
|
26
|
+
if (!res) {
|
|
27
|
+
exchange.respond({ statusCode: 200 });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
exchange.respondJson(res);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
exports.createUploadHandler = createUploadHandler;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stopWebServer = exports.startWebServer = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const http_1 = require("http");
|
|
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
|
+
/**
|
|
15
|
+
* 处理请求,完成拦截器和路由的流程.
|
|
16
|
+
*
|
|
17
|
+
* @param interceptors 拦截器
|
|
18
|
+
* @param routers 路由
|
|
19
|
+
* @param req
|
|
20
|
+
* @param res
|
|
21
|
+
* @param staticSettings
|
|
22
|
+
*/
|
|
23
|
+
async function handleRequest(interceptors, routers, req, res, staticSettings) {
|
|
24
|
+
req.socket.remoteAddress;
|
|
25
|
+
const { method } = req;
|
|
26
|
+
// cros 支持
|
|
27
|
+
res.setHeader('Access-Control-Allow-Origin', config_1.config.corsAllowOrigin);
|
|
28
|
+
res.setHeader('Access-Control-Allow-Headers', config_1.config.corsAllowHeaders);
|
|
29
|
+
res.setHeader('Access-Control-Allow-Methods', config_1.config.corsAllowMethods);
|
|
30
|
+
if (method === 'OPTIONS') {
|
|
31
|
+
res.statusCode = 200;
|
|
32
|
+
res.end();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const exchange = new exchange_1.ServerExchange(req, res);
|
|
36
|
+
// 顺序执行拦截器
|
|
37
|
+
await handleInterceptor(interceptors, 0, exchange, req, res, routers, staticSettings);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 处理拦截器.
|
|
41
|
+
* @param interceptors 拦截器
|
|
42
|
+
* @param idx 当前要执行的拦截器下标
|
|
43
|
+
* @param exchange 传输对象
|
|
44
|
+
* @param req
|
|
45
|
+
* @param res
|
|
46
|
+
* @param routers 路由
|
|
47
|
+
* @param staticSettings
|
|
48
|
+
*/
|
|
49
|
+
async function handleInterceptor(interceptors, idx, exchange, req, res, routers, staticSettings) {
|
|
50
|
+
const interceptor = interceptors[idx];
|
|
51
|
+
// 到最后一个了,那么执行路由处理
|
|
52
|
+
if (!interceptor) {
|
|
53
|
+
await handleRouter(exchange, routers, staticSettings);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await interceptor(exchange, () => handleInterceptor(interceptors, idx + 1, exchange, req, res, routers, staticSettings));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 处理路由.
|
|
60
|
+
* @param exchange
|
|
61
|
+
* @param routers
|
|
62
|
+
* @param staticSettings
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
async function handleRouter(exchange, routers, staticSettings) {
|
|
66
|
+
const url = exchange.request.url;
|
|
67
|
+
if (url === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// 判定路由
|
|
71
|
+
const idx = url.indexOf('?');
|
|
72
|
+
let path = idx === -1 ? url : url.substring(0, idx);
|
|
73
|
+
const router = routers[path];
|
|
74
|
+
if (!router) {
|
|
75
|
+
// 路由找不不到,尝试静态文件
|
|
76
|
+
if (staticSettings.length) {
|
|
77
|
+
await handleStatic(exchange, routers, path, staticSettings);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
respond404(exchange, routers, path);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// 执行路由
|
|
85
|
+
await router(exchange);
|
|
86
|
+
// 在路由顺利处理的情况下,如果 res 没有 end ,就表示响应没有完成
|
|
87
|
+
// 也就是说路由没有做响应处理,或处理没有完成就结束了,给予错误提示
|
|
88
|
+
if (!exchange.response.writableEnded) {
|
|
89
|
+
throw new Error(`RouterHandler unresponsive, url: ${url}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 处理静态文件
|
|
94
|
+
* @param exchange
|
|
95
|
+
* @param routers
|
|
96
|
+
* @param path
|
|
97
|
+
* @param staticDir
|
|
98
|
+
* @returns
|
|
99
|
+
*/
|
|
100
|
+
async function handleStatic(exchange, routers, path, staticSettings) {
|
|
101
|
+
// 匹配
|
|
102
|
+
let matchedSetting;
|
|
103
|
+
for (const setting of staticSettings) {
|
|
104
|
+
if (setting.path === '/') {
|
|
105
|
+
matchedSetting = setting;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (path.startsWith(setting.path)) {
|
|
109
|
+
matchedSetting = setting;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!matchedSetting) {
|
|
114
|
+
respond404(exchange, routers, path);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let finalPath = matchedSetting.path === '/' ? path : path.substring(matchedSetting.path.length);
|
|
118
|
+
if (finalPath.startsWith('/')) {
|
|
119
|
+
finalPath = finalPath.substring(1);
|
|
120
|
+
}
|
|
121
|
+
const fullPath = (0, path_1.resolve)(matchedSetting.dir, finalPath);
|
|
122
|
+
if (!(0, fs_1.existsSync)(fullPath)) {
|
|
123
|
+
respond404(exchange, routers, path);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const stat = (0, fs_1.statSync)(fullPath);
|
|
127
|
+
// 目录,寻找 index.html
|
|
128
|
+
if (stat.isDirectory()) {
|
|
129
|
+
const indexPath = (0, path_1.resolve)(fullPath, 'index.html');
|
|
130
|
+
if (!(0, fs_1.existsSync)(indexPath)) {
|
|
131
|
+
respond404(exchange, routers, path);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const indexStat = (0, fs_1.statSync)(indexPath);
|
|
135
|
+
if (!indexStat.isFile()) {
|
|
136
|
+
respond404(exchange, routers, path);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Cache-Control
|
|
140
|
+
if (matchedSetting.cacheAge >= 0) {
|
|
141
|
+
exchange.response.setHeader('Cache-Control', matchedSetting.cacheAge === 0 ? 'no-store' : `max-age=${matchedSetting.cacheAge}`);
|
|
142
|
+
}
|
|
143
|
+
await exchange.respondFile(indexPath, false);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// 文件直接渲染
|
|
147
|
+
if (stat.isFile()) {
|
|
148
|
+
// Cache-Control
|
|
149
|
+
if (matchedSetting.cacheAge >= 0) {
|
|
150
|
+
exchange.response.setHeader('Cache-Control', matchedSetting.cacheAge === 0 ? 'no-store' : `max-age=${matchedSetting.cacheAge}`);
|
|
151
|
+
}
|
|
152
|
+
await exchange.respondFile(fullPath, false);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// 其它类型,404
|
|
156
|
+
respond404(exchange, routers, path);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 404响应
|
|
160
|
+
*
|
|
161
|
+
* @param exchange
|
|
162
|
+
* @param routers
|
|
163
|
+
* @param path
|
|
164
|
+
*/
|
|
165
|
+
async function respond404(exchange, routers, path) {
|
|
166
|
+
const handler = routers['*'];
|
|
167
|
+
if (handler) {
|
|
168
|
+
await handler(exchange);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
exchange.respondErrMsg(`${path} not found`, 404);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 获取 ipv4 地址列表
|
|
176
|
+
* @returns
|
|
177
|
+
*/
|
|
178
|
+
function getIpv4List() {
|
|
179
|
+
const ifs = (0, os_1.networkInterfaces)();
|
|
180
|
+
const res = [];
|
|
181
|
+
for (const name in ifs) {
|
|
182
|
+
const list = ifs[name];
|
|
183
|
+
if (!list) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
res.push(...list.filter(info => info.family === 'IPv4').map(info => info.address));
|
|
187
|
+
}
|
|
188
|
+
return res;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 服务实例.
|
|
192
|
+
*/
|
|
193
|
+
let SERVER;
|
|
194
|
+
/**
|
|
195
|
+
* 启动 web服务
|
|
196
|
+
* @param opts
|
|
197
|
+
*/
|
|
198
|
+
async function startWebServer(opts) {
|
|
199
|
+
if (SERVER) {
|
|
200
|
+
throw new Error('The server has already been started!');
|
|
201
|
+
}
|
|
202
|
+
// 检查静态文件配置,做一些预处理的操作,在处理静态资源的请求时不必再做这些处理
|
|
203
|
+
const staticSettings = [];
|
|
204
|
+
if (opts.static) {
|
|
205
|
+
// 重复记录表 ,作用是为了路径去重判定,可以提示哪些路径是重复的
|
|
206
|
+
const duplicateMap = new Map();
|
|
207
|
+
for (const entry of Object.entries(opts.static)) {
|
|
208
|
+
const [path, setting] = entry;
|
|
209
|
+
const dir = (0, path_1.isAbsolute)(setting.dir) ? setting.dir : (0, path_1.resolve)(process.cwd(), setting.dir);
|
|
210
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
211
|
+
throw new Error(`Static file configuration error,path ${dir} does not exist,config dir:${setting.dir}`);
|
|
212
|
+
}
|
|
213
|
+
const stat = (0, fs_1.statSync)(dir);
|
|
214
|
+
if (!stat.isDirectory()) {
|
|
215
|
+
throw new Error(`Static file configuration error,path ${dir} is not a directory,config dir:${setting.dir}`);
|
|
216
|
+
}
|
|
217
|
+
let finalPath = path.startsWith('/') ? path : '/' + path;
|
|
218
|
+
// 保持以 / 结尾,为了匹配方便
|
|
219
|
+
if (!finalPath.endsWith('/')) {
|
|
220
|
+
finalPath += '/';
|
|
221
|
+
}
|
|
222
|
+
if (duplicateMap.has(finalPath)) {
|
|
223
|
+
throw new Error(`Static path duplicated: ${duplicateMap.get(finalPath)} and ${path}`);
|
|
224
|
+
}
|
|
225
|
+
duplicateMap.set(finalPath, path);
|
|
226
|
+
staticSettings.push({ path: finalPath, dir, cacheAge: setting.cacheAge || 0 });
|
|
227
|
+
}
|
|
228
|
+
// 优先级排序
|
|
229
|
+
staticSettings.sort((o1, o2) => {
|
|
230
|
+
let priority1 = o1.path === '/' ? -1 : o1.path.split('/').length;
|
|
231
|
+
let priority2 = o2.path === '/' ? -1 : o2.path.split('/').length;
|
|
232
|
+
// 如果 o1 优先级高,就应该排前面,返回小于0的值,反之亦然\
|
|
233
|
+
// 前面的优先级值是值越大优先级越高,反过来减
|
|
234
|
+
return priority2 - priority1;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// 如果启用请求日志,增加拦截器
|
|
238
|
+
let interceptors = [];
|
|
239
|
+
if (config_1.config.accessLog) {
|
|
240
|
+
interceptors.push(access_log_1.accessLogInterceptor);
|
|
241
|
+
}
|
|
242
|
+
if (opts.interceptors) {
|
|
243
|
+
interceptors.push(...opts.interceptors);
|
|
244
|
+
}
|
|
245
|
+
SERVER = (0, http_1.createServer)((req, res) => {
|
|
246
|
+
res.on('error', error => {
|
|
247
|
+
// 如果响应流发生错误,只能把信息记录下来
|
|
248
|
+
(0, log_1.getLogger)().error(`Response Error:${req.url}`, error);
|
|
249
|
+
});
|
|
250
|
+
handleRequest(interceptors, opts.routers, req, res, staticSettings).catch(error => {
|
|
251
|
+
(0, log_1.getLogger)().error(`Handle request failed:${req.url}`, error);
|
|
252
|
+
if (!res.writableEnded) {
|
|
253
|
+
// 响应 500
|
|
254
|
+
(0, render_1.renderError)(res, error.message ? error.message : 'Internal Server Error', 500);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
SERVER.setTimeout(config_1.config.timeout);
|
|
259
|
+
SERVER.on('timeout', (socket) => {
|
|
260
|
+
socket.end('HTTP/1.1 408 Timeout\ncontent-type: application/json; charset=utf-8\n\n{"message":"Request timeout"}');
|
|
261
|
+
});
|
|
262
|
+
if (opts.preHandler) {
|
|
263
|
+
await opts.preHandler(SERVER);
|
|
264
|
+
}
|
|
265
|
+
const server = SERVER;
|
|
266
|
+
await new Promise((resolve, reject) => {
|
|
267
|
+
server.on('error', e => {
|
|
268
|
+
if (e.code === 'EADDRINUSE') {
|
|
269
|
+
reject(`端口号 ${config_1.config.port} 已经被占用`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
reject(e);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
server.listen(config_1.config.port, resolve);
|
|
276
|
+
});
|
|
277
|
+
console.log('App running at: ');
|
|
278
|
+
getIpv4List().forEach(ip => {
|
|
279
|
+
console.log(`http://${ip}:${config_1.config.port}`);
|
|
280
|
+
});
|
|
281
|
+
process.on('beforeExit', () => {
|
|
282
|
+
if (server.listening) {
|
|
283
|
+
server.close();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
exports.startWebServer = startWebServer;
|
|
288
|
+
/**
|
|
289
|
+
* 停止 web 服务
|
|
290
|
+
* @returns
|
|
291
|
+
*/
|
|
292
|
+
async function stopWebServer() {
|
|
293
|
+
if (!SERVER) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (SERVER.listening) {
|
|
297
|
+
const server = SERVER;
|
|
298
|
+
await new Promise((res, rej) => {
|
|
299
|
+
server.close(err => {
|
|
300
|
+
if (err) {
|
|
301
|
+
rej(err);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
res();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
SERVER = undefined;
|
|
310
|
+
}
|
|
311
|
+
exports.stopWebServer = stopWebServer;
|
|
312
|
+
tslib_1.__exportStar(require("./exchange"), exports);
|
|
313
|
+
tslib_1.__exportStar(require("./handler"), exports);
|
|
314
|
+
tslib_1.__exportStar(require("./render"), exports);
|
|
315
|
+
tslib_1.__exportStar(require("./router"), exports);
|
|
316
|
+
tslib_1.__exportStar(require("./interceptor"), exports);
|