lz-nframe 1.0.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/lib/NApp.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /*******************************************************************************
2
+ 文件: NApp.ts
3
+ 创建: 2022年02月15日
4
+ 作者: 老张
5
+ 描述:
6
+ 应用基类
7
+ *******************************************************************************/
8
+ import { EventEmitter } from 'events';
9
+ export default abstract class NApp extends EventEmitter {
10
+ protected appName: string;
11
+ constructor(appName: string);
12
+ abstract start(): void;
13
+ }
package/lib/NApp.js ADDED
@@ -0,0 +1,23 @@
1
+ /*******************************************************************************
2
+ 文件: NApp.ts
3
+ 创建: 2022年02月15日
4
+ 作者: 老张
5
+ 描述:
6
+ 应用基类
7
+ *******************************************************************************/
8
+ import { EventEmitter } from 'events';
9
+ import * as fs from 'fs';
10
+ export default class NApp extends EventEmitter {
11
+ constructor(appName) {
12
+ super();
13
+ this.appName = appName;
14
+ // 输出日志
15
+ console.log(`${appName} Start ${new Date()}`);
16
+ console.error(`${appName} Start ${new Date()}`);
17
+ // 捕获未处理异常
18
+ process.on('uncaughtException', (err) => {
19
+ console.error(err);
20
+ fs.writeFileSync('./runtime.log', err + '\n', { flag: 'a+' });
21
+ });
22
+ }
23
+ }
@@ -0,0 +1,50 @@
1
+ /*******************************************************************************
2
+ 文件: NConfigsMgr.ts
3
+ 创建: 2022年02月05日
4
+ 作者: 老张
5
+ 描述:
6
+ 配置文件加载、变动监视,文件修改自动重载
7
+ 配置来源分为:
8
+ 1.通过代码提交的【默认配置】
9
+ 2.通过本地文件存储的【本地配置】
10
+ 3.通过远程部署的文件进行的【动态配置】
11
+
12
+ 获取配置时的顺序:【动态配置】->【本地配置】->【默认配置】
13
+ *******************************************************************************/
14
+ import { NestedKeyOf, ValueAtPath } from './NType';
15
+ export default class NConfigsMgr<T extends object> {
16
+ get configs(): T;
17
+ private defaultConfigs;
18
+ private localConfigs;
19
+ private dynamicConfigs;
20
+ private _configs;
21
+ private localConfigsFile;
22
+ private dynamicConfigsFile;
23
+ private changeListener;
24
+ private fileWatcherHandler;
25
+ /**
26
+ * 设置配置文件
27
+ * @param defaultConfig 默认配置
28
+ * @param localConfigsFile 本地配置文件路径,可使用path.join和__dirname来合成
29
+ * @param dynamicConfigsFile 动态配置文件路径,可使用path.join和__dirname来合成
30
+ */
31
+ constructor(defaultConfigs: T, localConfigsFile: string, dynamicConfigsFile: string);
32
+ /** 监听某项配置的变化,支持子属性路径(如 'db.host') */
33
+ addConfigChangeListener<K extends NestedKeyOf<T> & string>(key: K, listener: (last: ValueAtPath<T, K>, current: ValueAtPath<T, K>) => void): void;
34
+ /**
35
+ * 使用默认配置生成一个配置json模板
36
+ * @param outPath 模板存储路径,可使用path.join和__dirname来合成
37
+ */
38
+ createTemplateJson(outPath: string): boolean;
39
+ /**
40
+ * 释放内部文件监听器,避免测试或短生命周期场景中残留句柄导致进程无法退出
41
+ */
42
+ close(): void;
43
+ /** 设置属性查询器,【动态配置】->【本地配置】->【默认配置】 */
44
+ private setupSetter;
45
+ private fileWatcher;
46
+ /** 加载本地配置文件、动态配置文件 */
47
+ private loadFile;
48
+ /** 根据点号路径从对象中获取值 */
49
+ private getValueByPath;
50
+ }
@@ -0,0 +1,162 @@
1
+ /*******************************************************************************
2
+ 文件: NConfigsMgr.ts
3
+ 创建: 2022年02月05日
4
+ 作者: 老张
5
+ 描述:
6
+ 配置文件加载、变动监视,文件修改自动重载
7
+ 配置来源分为:
8
+ 1.通过代码提交的【默认配置】
9
+ 2.通过本地文件存储的【本地配置】
10
+ 3.通过远程部署的文件进行的【动态配置】
11
+
12
+ 获取配置时的顺序:【动态配置】->【本地配置】->【默认配置】
13
+ *******************************************************************************/
14
+ import * as fs from 'fs';
15
+ import NUtils from 'lz-nutils';
16
+ export default class NConfigsMgr {
17
+ get configs() {
18
+ return this._configs;
19
+ }
20
+ /**
21
+ * 设置配置文件
22
+ * @param defaultConfig 默认配置
23
+ * @param localConfigsFile 本地配置文件路径,可使用path.join和__dirname来合成
24
+ * @param dynamicConfigsFile 动态配置文件路径,可使用path.join和__dirname来合成
25
+ */
26
+ constructor(defaultConfigs, localConfigsFile, dynamicConfigsFile) {
27
+ this.changeListener = {};
28
+ this.defaultConfigs = NUtils.copySimple(defaultConfigs);
29
+ this.setupSetter();
30
+ this.localConfigsFile = localConfigsFile;
31
+ this.dynamicConfigsFile = dynamicConfigsFile;
32
+ this.fileWatcherHandler = this.fileWatcher.bind(this);
33
+ fs.watchFile(this.localConfigsFile, this.fileWatcherHandler);
34
+ fs.watchFile(this.dynamicConfigsFile, this.fileWatcherHandler);
35
+ this.loadFile(true);
36
+ }
37
+ /** 监听某项配置的变化,支持子属性路径(如 'db.host') */
38
+ addConfigChangeListener(key, listener) {
39
+ if (this.changeListener[key] == null) {
40
+ this.changeListener[key] = [];
41
+ }
42
+ this.changeListener[key].push(listener);
43
+ }
44
+ /**
45
+ * 使用默认配置生成一个配置json模板
46
+ * @param outPath 模板存储路径,可使用path.join和__dirname来合成
47
+ */
48
+ createTemplateJson(outPath) {
49
+ try {
50
+ let content = JSON.stringify(this.defaultConfigs, null, 4);
51
+ fs.writeFileSync(outPath, content);
52
+ return true;
53
+ }
54
+ catch (error) {
55
+ return false;
56
+ }
57
+ }
58
+ /**
59
+ * 释放内部文件监听器,避免测试或短生命周期场景中残留句柄导致进程无法退出
60
+ */
61
+ close() {
62
+ fs.unwatchFile(this.localConfigsFile, this.fileWatcherHandler);
63
+ fs.unwatchFile(this.dynamicConfigsFile, this.fileWatcherHandler);
64
+ }
65
+ /** 设置属性查询器,【动态配置】->【本地配置】->【默认配置】 */
66
+ setupSetter() {
67
+ this._configs = {};
68
+ for (let key in this.defaultConfigs) {
69
+ if (!this.defaultConfigs.hasOwnProperty(key))
70
+ continue;
71
+ Object.defineProperty(this._configs, key, {
72
+ get: () => {
73
+ if (this.dynamicConfigs && this.dynamicConfigs[key] != null)
74
+ return this.dynamicConfigs[key];
75
+ if (this.localConfigs && this.localConfigs[key] != null)
76
+ return this.localConfigs[key];
77
+ return this.defaultConfigs[key];
78
+ },
79
+ configurable: false
80
+ });
81
+ }
82
+ }
83
+ fileWatcher(current, prev) {
84
+ // ctime是文件stats发生变化的时间,ctime为0可以认定文件不存在
85
+ // mtime是文件内容修改时间
86
+ // 文件创建
87
+ if (prev.ctime.getTime() === 0 && current.ctime.getTime() !== 0) {
88
+ this.loadFile();
89
+ }
90
+ // 文件删除,已加载的配置不变
91
+ else if (current.ctime.getTime() === 0) {
92
+ //
93
+ }
94
+ // 文件修改了
95
+ else if (current.mtime.getTime() !== prev.mtime.getTime()) {
96
+ this.loadFile();
97
+ }
98
+ }
99
+ /** 加载本地配置文件、动态配置文件 */
100
+ loadFile(init = false) {
101
+ let lastConfigs = {};
102
+ if (!init) {
103
+ for (let key in this.defaultConfigs) {
104
+ if (!this.defaultConfigs.hasOwnProperty(key))
105
+ continue;
106
+ lastConfigs[key] = this.configs[key];
107
+ }
108
+ }
109
+ // 本地配置
110
+ try {
111
+ let content = fs.readFileSync(this.localConfigsFile);
112
+ let json = JSON.parse(content.toString());
113
+ this.localConfigs = json;
114
+ }
115
+ catch (error) {
116
+ }
117
+ // 动态配置
118
+ try {
119
+ let content = fs.readFileSync(this.dynamicConfigsFile);
120
+ let json = JSON.parse(content.toString());
121
+ this.dynamicConfigs = json;
122
+ }
123
+ catch (error) {
124
+ }
125
+ // 变动检查
126
+ if (!init) {
127
+ let changedKeys = [];
128
+ // 检测所有注册了监听器的路径
129
+ for (const key of Object.keys(this.changeListener)) {
130
+ const lastValue = this.getValueByPath(lastConfigs, key);
131
+ const currentValue = this.getValueByPath(this.configs, key);
132
+ if (lastValue === currentValue)
133
+ continue;
134
+ if (JSON.stringify(lastValue) !== JSON.stringify(currentValue)) {
135
+ changedKeys.push({ key, last: lastValue, current: currentValue });
136
+ }
137
+ }
138
+ // 隔离调用
139
+ for (const entry of changedKeys) {
140
+ let listeners = this.changeListener[entry.key] || [];
141
+ for (let listener of listeners) {
142
+ try {
143
+ listener(entry.last, entry.current);
144
+ }
145
+ catch (error) {
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ /** 根据点号路径从对象中获取值 */
152
+ getValueByPath(obj, path) {
153
+ const keys = path.split('.');
154
+ let current = obj;
155
+ for (const key of keys) {
156
+ if (current == null)
157
+ return undefined;
158
+ current = current[key];
159
+ }
160
+ return current;
161
+ }
162
+ }
@@ -0,0 +1,71 @@
1
+ /*******************************************************************************
2
+ 文件: NHttpClient.ts
3
+ 创建: 2026年05月06日
4
+ 作者: 老张
5
+ 描述:
6
+ 网络请求客户端 (优化版:不再强制抛错,返回统一的结果包装)
7
+ - 支持自动处理中文 URL 编码
8
+ - 支持泛型与类型安全
9
+ *******************************************************************************/
10
+ import * as http from 'http';
11
+ /**
12
+ * 请求配置接口
13
+ */
14
+ interface RequestOptions {
15
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
16
+ headers?: Record<string, string | number>;
17
+ timeout?: number;
18
+ body?: any;
19
+ }
20
+ /**
21
+ * 统一响应包装器
22
+ * 成功时 error 为 null,失败时 data 为 null
23
+ */
24
+ export interface ApiResult<T> {
25
+ success: boolean;
26
+ data: T | null;
27
+ error: {
28
+ message: string;
29
+ code?: number;
30
+ details?: any;
31
+ } | null;
32
+ response?: {
33
+ statusCode?: number;
34
+ headers: http.IncomingHttpHeaders;
35
+ };
36
+ }
37
+ export default class NHttpClient {
38
+ /**
39
+ * 核心请求方法
40
+ * 内部捕获所有异常,确保始终 resolve 一个 ApiResult 对象
41
+ */
42
+ static request<T = any>(url: string, options?: RequestOptions): Promise<ApiResult<T>>;
43
+ /**
44
+ * 获取客户端真实 IP
45
+ * 按优先级依次检查各类代理 header,最终回退到 socket.remoteAddress
46
+ */
47
+ static getClientIp(req: http.IncomingMessage): string;
48
+ static get<T = any>(url: string, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
49
+ /**
50
+ * GET 请求(自动将 params 对象拼接到 URL 作为查询参数)
51
+ */
52
+ static getWithParams<T = any>(url: string, params?: Record<string, string | number | boolean>, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
53
+ static post<T = any>(url: string, body?: any, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
54
+ /**
55
+ * POST 请求(表单编码 body)
56
+ * 自动设置 Content-Type 为 application/x-www-form-urlencoded
57
+ */
58
+ static postForm<T = any>(url: string, body: Record<string, string | number | boolean>, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
59
+ static put<T = any>(url: string, body?: any, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
60
+ /**
61
+ * PUT 请求(表单编码 body)
62
+ * 自动设置 Content-Type 为 application/x-www-form-urlencoded
63
+ */
64
+ static putForm<T = any>(url: string, body: Record<string, string | number | boolean>, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
65
+ static delete<T = any>(url: string, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<ApiResult<T>>;
66
+ /**
67
+ * 将普通对象编码为 application/x-www-form-urlencoded 格式字符串
68
+ */
69
+ static encodeFormBody(data: Record<string, string | number | boolean>): string;
70
+ }
71
+ export {};
@@ -0,0 +1,196 @@
1
+ /*******************************************************************************
2
+ 文件: NHttpClient.ts
3
+ 创建: 2026年05月06日
4
+ 作者: 老张
5
+ 描述:
6
+ 网络请求客户端 (优化版:不再强制抛错,返回统一的结果包装)
7
+ - 支持自动处理中文 URL 编码
8
+ - 支持泛型与类型安全
9
+ *******************************************************************************/
10
+ import * as http from 'http';
11
+ import * as https from 'https';
12
+ import { URL } from 'url';
13
+ export default class NHttpClient {
14
+ /**
15
+ * 核心请求方法
16
+ * 内部捕获所有异常,确保始终 resolve 一个 ApiResult 对象
17
+ */
18
+ static request(url, options = {}) {
19
+ return new Promise((resolve) => {
20
+ try {
21
+ // 1. 处理中文 URL。直接 new URL(url) 会自动对路径部分进行合法性校验,
22
+ // 但为了保险,我们确保使用 URL 对象的 href 属性发送请求,它会自动处理编码。
23
+ const parsedUrl = new URL(url);
24
+ const protocol = parsedUrl.protocol === 'https:' ? https : http;
25
+ const requestOptions = {
26
+ hostname: parsedUrl.hostname,
27
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
28
+ // 使用 URL 对象的 pathname + search 保证了中文参数被正确编码为 % 格式
29
+ path: parsedUrl.pathname + parsedUrl.search,
30
+ method: (options.method || 'GET').toUpperCase(),
31
+ headers: options.headers || {},
32
+ timeout: options.timeout || 10000,
33
+ };
34
+ let postData = null;
35
+ if (options.body) {
36
+ if (typeof options.body === 'object') {
37
+ postData = JSON.stringify(options.body);
38
+ const headers = requestOptions.headers;
39
+ headers['Content-Type'] = headers['Content-Type'] || 'application/json';
40
+ headers['Content-Length'] = Buffer.byteLength(postData);
41
+ }
42
+ else {
43
+ postData = String(options.body);
44
+ }
45
+ }
46
+ const req = protocol.request(requestOptions, (res) => {
47
+ const chunks = [];
48
+ res.on('data', (chunk) => chunks.push(chunk));
49
+ res.on('end', () => {
50
+ const buffer = Buffer.concat(chunks);
51
+ const responseString = buffer.toString();
52
+ let data;
53
+ const contentType = res.headers['content-type'] || '';
54
+ if (contentType.includes('application/json')) {
55
+ try {
56
+ data = responseString ? JSON.parse(responseString) : null;
57
+ }
58
+ catch (e) {
59
+ data = responseString;
60
+ }
61
+ }
62
+ else {
63
+ data = responseString;
64
+ }
65
+ const statusCode = res.statusCode || 0;
66
+ const success = statusCode >= 200 && statusCode < 300;
67
+ resolve({
68
+ success,
69
+ data: success ? data : null,
70
+ error: success ? null : {
71
+ message: `HTTP Error: ${statusCode}`,
72
+ details: data
73
+ },
74
+ response: {
75
+ statusCode,
76
+ headers: res.headers
77
+ }
78
+ });
79
+ });
80
+ });
81
+ req.on('error', (err) => {
82
+ resolve({
83
+ success: false,
84
+ data: null,
85
+ error: { message: err.message },
86
+ });
87
+ });
88
+ req.on('timeout', () => {
89
+ req.destroy();
90
+ resolve({
91
+ success: false,
92
+ data: null,
93
+ error: { message: `Request timed out after ${requestOptions.timeout}ms` },
94
+ });
95
+ });
96
+ if (postData)
97
+ req.write(postData);
98
+ req.end();
99
+ }
100
+ catch (err) {
101
+ // 处理 URL 解析错误(如 URL 格式不合法)
102
+ resolve({
103
+ success: false,
104
+ data: null,
105
+ error: { message: `URL Parse Error: ${err.message}` },
106
+ });
107
+ }
108
+ });
109
+ }
110
+ /**
111
+ * 获取客户端真实 IP
112
+ * 按优先级依次检查各类代理 header,最终回退到 socket.remoteAddress
113
+ */
114
+ static getClientIp(req) {
115
+ var _a, _b;
116
+ const headers = req.headers;
117
+ // 安全地提取 header 值(处理 string | string[] | undefined)
118
+ const getHeader = (name) => {
119
+ const val = headers[name];
120
+ if (typeof val === 'string')
121
+ return val.trim();
122
+ if (Array.isArray(val) && val.length > 0)
123
+ return String(val[0]).trim();
124
+ return '';
125
+ };
126
+ // 依次检查各代理 header
127
+ const candidates = [
128
+ getHeader('x-real-ip'),
129
+ getHeader('cf-connecting-ip'), // Cloudflare
130
+ getHeader('true-client-ip'), // Akamai / Cloudflare Enterprise
131
+ getHeader('x-client-ip'),
132
+ getHeader('x-forwarded-for'), // 通用代理,可能含多个 IP
133
+ getHeader('proxy-client-ip'),
134
+ getHeader('wl-proxy-client-ip'),
135
+ ];
136
+ for (const ip of candidates) {
137
+ if (ip && ip.length > 0 && ip.toLowerCase() !== 'unknown') {
138
+ // x-forwarded-for 格式: "client, proxy1, proxy2",取第一个
139
+ const first = ip.split(',')[0].trim();
140
+ if (first)
141
+ return first;
142
+ }
143
+ }
144
+ // 回退到 socket 地址
145
+ return (_b = (_a = req.socket) === null || _a === void 0 ? void 0 : _a.remoteAddress) !== null && _b !== void 0 ? _b : 'unknown';
146
+ }
147
+ static get(url, options) {
148
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'GET' }));
149
+ }
150
+ /**
151
+ * GET 请求(自动将 params 对象拼接到 URL 作为查询参数)
152
+ */
153
+ static getWithParams(url, params, options) {
154
+ if (params && Object.keys(params).length > 0) {
155
+ const parsedUrl = new URL(url);
156
+ Object.entries(params).forEach(([key, value]) => {
157
+ parsedUrl.searchParams.append(key, String(value));
158
+ });
159
+ url = parsedUrl.toString();
160
+ }
161
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'GET' }));
162
+ }
163
+ static post(url, body, options) {
164
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'POST', body }));
165
+ }
166
+ /**
167
+ * POST 请求(表单编码 body)
168
+ * 自动设置 Content-Type 为 application/x-www-form-urlencoded
169
+ */
170
+ static postForm(url, body, options) {
171
+ const formBody = this.encodeFormBody(body);
172
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/x-www-form-urlencoded' }, options === null || options === void 0 ? void 0 : options.headers), body: formBody }));
173
+ }
174
+ static put(url, body, options) {
175
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'PUT', body }));
176
+ }
177
+ /**
178
+ * PUT 请求(表单编码 body)
179
+ * 自动设置 Content-Type 为 application/x-www-form-urlencoded
180
+ */
181
+ static putForm(url, body, options) {
182
+ const formBody = this.encodeFormBody(body);
183
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'PUT', headers: Object.assign({ 'Content-Type': 'application/x-www-form-urlencoded' }, options === null || options === void 0 ? void 0 : options.headers), body: formBody }));
184
+ }
185
+ static delete(url, options) {
186
+ return this.request(url, Object.assign(Object.assign({}, options), { method: 'DELETE' }));
187
+ }
188
+ /**
189
+ * 将普通对象编码为 application/x-www-form-urlencoded 格式字符串
190
+ */
191
+ static encodeFormBody(data) {
192
+ return Object.entries(data)
193
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
194
+ .join('&');
195
+ }
196
+ }
@@ -0,0 +1,75 @@
1
+ /*******************************************************************************
2
+ 文件: NSimpleHttpServer.ts
3
+ 创建: 2022年02月17日
4
+ 作者: 老张
5
+ 描述:
6
+ 基于NodeJS内置http模块实现http(s)服务器,不需要额外的依赖包
7
+
8
+ 文档:
9
+ http模块api参考:http://nodejs.cn/api/http.html
10
+
11
+ 参考:
12
+ https://www.cnblogs.com/chuanzi/p/10507919.html
13
+
14
+ 示例:
15
+ new NSimpleHttpServer()
16
+ .addPage('/', (req, res) => {
17
+ res.write('HelloWorld\n');
18
+ console.log(req.socket.remoteAddress);
19
+ res.write('远程地址:' + req.socket.remoteAddress + '\n');
20
+ res.write('本地地址:' + req.socket.localAddress + '\n');
21
+ }, {contentType: ContentType.TextPlain})
22
+ .start(3000);
23
+ *******************************************************************************/
24
+ import * as http from 'http';
25
+ export default class NSimpleHttpServer {
26
+ private server;
27
+ private pages;
28
+ /** 单次请求 body 上限(字节),超出则返回 413 */
29
+ private maxBodyBytes;
30
+ constructor(params?: {
31
+ maxBodyBytes?: number;
32
+ });
33
+ /** 启动服务 host如果为null那么req.socket.remoteAddress格式如::ffff:127.0.0.1 */
34
+ start(port: number, host?: string, onListening?: () => void): this;
35
+ close(): this;
36
+ /**
37
+ * 注册页面/路由
38
+ * @param url 路径,会做规范化(去掉尾斜杠,根路径'/'除外)
39
+ * @param requestDealer 请求处理函数;内部可主动调用 res.writeHead/res.end 自定义状态码
40
+ * @param options.contentType 响应头的 Content-Type;兼容旧 enum 整行写法与纯 MIME 写法
41
+ * @param options.allowCORS 开启简单 CORS,并自动应答 OPTIONS 预检
42
+ * @param options.method 仅允许指定 HTTP 方法(如 'POST' 或 ['GET','POST']),不传则放行所有方法
43
+ */
44
+ addPage(url: string, requestDealer: (req: http.IncomingMessage, res: http.ServerResponse, query: {
45
+ [key: string]: string;
46
+ }, body: string) => void, options?: {
47
+ contentType?: ContentType | string;
48
+ allowCORS?: boolean;
49
+ method?: string | string[];
50
+ }): this;
51
+ private onRequest;
52
+ /** 路径规范化:去掉尾部多余斜杠,但保留根路径'/' */
53
+ private normalizePath;
54
+ /**
55
+ * 解析 contentType 配置,同时兼容两种写法:
56
+ * 1) 旧 enum 整行写法(含 ':'):'Content-Type: text/html; charset=utf-8;'
57
+ * 2) 纯 MIME 写法:'application/json'
58
+ * 修正了原实现 split(':').length===2 才生效的限制(含多冒号会被丢弃)以及尾分号问题
59
+ */
60
+ private parseContentType;
61
+ }
62
+ export declare enum ContentType {
63
+ TextHtml = "Content-Type: text/html; charset=utf-8;",
64
+ TextPlain = "Content-Type: text/plain; charset=utf-8;",
65
+ ApplicationJson = "Content-Type: application/json; charset=utf-8;"
66
+ }
67
+ export interface SimplePage {
68
+ url: string;
69
+ contentType?: ContentType | string;
70
+ allowCORS?: boolean;
71
+ method?: string | string[];
72
+ requestDealer: (req: http.IncomingMessage, res: http.ServerResponse, query: {
73
+ [key: string]: string;
74
+ }, body: string) => void;
75
+ }