@maiyunnet/kebab 7.6.3 → 7.7.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/doc/kebab-rag.md +236 -157
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/fs.js +0 -5
- package/lib/net.d.ts +9 -9
- package/lib/net.js +19 -19
- package/lib/s3.d.ts +1 -0
- package/lib/text.d.ts +9 -0
- package/lib/text.js +27 -11
- package/lib/ws.js +13 -5
- package/package.json +1 -1
- package/sys/child.js +6 -1
- package/sys/ctr.d.ts +11 -2
- package/sys/ctr.js +8 -1
- package/sys/route.js +2 -2
- package/www/example/ctr/test.js +9 -4
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '7.
|
|
9
|
+
export const VER = '7.7.0';
|
|
10
10
|
// --- 服务端用的路径 ---
|
|
11
11
|
const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
12
12
|
/** --- /xxx/xxx --- */
|
package/lib/fs.js
CHANGED
|
@@ -435,11 +435,6 @@ export async function readToResponse(path, req, res, stat) {
|
|
|
435
435
|
// --- 判断缓存以及 MIME 和编码 ---
|
|
436
436
|
let charset = '';
|
|
437
437
|
const mimeData = mime.getData(path);
|
|
438
|
-
// --- 特殊路径 ---
|
|
439
|
-
if (path.endsWith('/+esm')) {
|
|
440
|
-
mimeData.mime = 'application/javascript';
|
|
441
|
-
mimeData.extension = 'js';
|
|
442
|
-
}
|
|
443
438
|
if (['htm', 'html', 'css', 'js', 'mjs', 'xml', 'jpg', 'jpeg', 'svg', 'gif', 'png', 'json'].includes(mimeData.extension)) {
|
|
444
439
|
charset = '; charset=utf-8';
|
|
445
440
|
// --- 这些文件可能需要缓存 ---
|
package/lib/net.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as http from 'http';
|
|
|
9
9
|
import * as http2 from 'http2';
|
|
10
10
|
import * as kebab from '#kebab/index.js';
|
|
11
11
|
import * as sCtr from '#kebab/sys/ctr.js';
|
|
12
|
-
import * as
|
|
12
|
+
import * as lFd from './net/formdata.js';
|
|
13
13
|
import * as lRequest from './net/request.js';
|
|
14
14
|
import * as lResponse from './net/response.js';
|
|
15
15
|
/** --- 获取 CA 证书 --- */
|
|
@@ -38,7 +38,7 @@ export declare function post(u: string, data: Record<string, kebab.Json> | Buffe
|
|
|
38
38
|
* @param data 数据
|
|
39
39
|
* @param opt 选项
|
|
40
40
|
*/
|
|
41
|
-
export declare function postJson(u: string, data:
|
|
41
|
+
export declare function postJson(u: string, data: kebab.Json[] | Record<string, kebab.Json>, opt?: IRequestOptions): Promise<lResponse.Response>;
|
|
42
42
|
/**
|
|
43
43
|
* --- 发起 JSON 请求并解析 JSON 响应 ---
|
|
44
44
|
* @param u 网址
|
|
@@ -46,7 +46,7 @@ export declare function postJson(u: string, data: any[] | Record<string, kebab.J
|
|
|
46
46
|
* @param opt 选项
|
|
47
47
|
* @returns JSON 数据,失败时返回 null
|
|
48
48
|
*/
|
|
49
|
-
export declare function postJsonResponseJson(u: string, data:
|
|
49
|
+
export declare function postJsonResponseJson(u: string, data: kebab.Json[] | Record<string, kebab.Json>, opt?: IRequestOptions): Promise<kebab.Json | null>;
|
|
50
50
|
/**
|
|
51
51
|
* --- 发起一个原生 fetch 请求,增加了一些框架选项,注意:会抛出错误 ---
|
|
52
52
|
* @param input 请求的 URL 或 Request 对象
|
|
@@ -57,14 +57,14 @@ export declare function fetch(input: string | URL | Request, init?: RequestInit
|
|
|
57
57
|
'mproxy'?: {
|
|
58
58
|
'url': string;
|
|
59
59
|
'auth': string;
|
|
60
|
-
'data'?:
|
|
60
|
+
'data'?: kebab.Json;
|
|
61
61
|
};
|
|
62
62
|
}): Promise<Response>;
|
|
63
63
|
/**
|
|
64
64
|
* --- 发起一个请求 ---
|
|
65
65
|
* @param opt 配置项
|
|
66
66
|
*/
|
|
67
|
-
export declare function request(u: string, data?: Record<string,
|
|
67
|
+
export declare function request(u: string, data?: Record<string, kebab.Json> | Buffer | string | stream.Readable, opt?: IRequestOptions): Promise<lResponse.Response>;
|
|
68
68
|
/**
|
|
69
69
|
* --- 对 cookie 对象进行操作 ---
|
|
70
70
|
* @param cookie 要操作的对象
|
|
@@ -93,7 +93,7 @@ export declare function resetCookieSession(cookie: Record<string, ICookie>): voi
|
|
|
93
93
|
/**
|
|
94
94
|
* --- 创建 FormData 对象 ---
|
|
95
95
|
*/
|
|
96
|
-
export declare function getFormData():
|
|
96
|
+
export declare function getFormData(): lFd.FormData;
|
|
97
97
|
/**
|
|
98
98
|
* --- 剔除不代理的 header,返回新的 header ---
|
|
99
99
|
* @param headers 剔除前的 header
|
|
@@ -113,7 +113,7 @@ export declare function mproxy(ctr: sCtr.Ctr, auth: string, opt?: IMproxyOptions
|
|
|
113
113
|
* --- 获取 mproxy 的附加数据 ---
|
|
114
114
|
* @param ctr 当前控制器
|
|
115
115
|
*/
|
|
116
|
-
export declare function mproxyData(ctr: sCtr.Ctr):
|
|
116
|
+
export declare function mproxyData(ctr: sCtr.Ctr): kebab.Json;
|
|
117
117
|
/**
|
|
118
118
|
* --- 反向代理,注意提前处理不要自动处理 post 数据,将本服务器的某个路由反代到其他网址 ---
|
|
119
119
|
* @param ctr 当前控制器
|
|
@@ -138,7 +138,7 @@ export interface IRequestOptions {
|
|
|
138
138
|
'mproxy'?: {
|
|
139
139
|
'url': string;
|
|
140
140
|
'auth': string;
|
|
141
|
-
'data'?:
|
|
141
|
+
'data'?: kebab.Json;
|
|
142
142
|
};
|
|
143
143
|
/** --- 连接是否保持长连接(即是否允许复用),默认为 true --- */
|
|
144
144
|
'keep'?: boolean;
|
|
@@ -176,7 +176,7 @@ export interface IRproxyOptions {
|
|
|
176
176
|
'mproxy'?: {
|
|
177
177
|
'url': string;
|
|
178
178
|
'auth': string;
|
|
179
|
-
'data'?:
|
|
179
|
+
'data'?: kebab.Json;
|
|
180
180
|
};
|
|
181
181
|
/** --- 默认为 default --- */
|
|
182
182
|
'reuse'?: string;
|
package/lib/net.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as lText from '#kebab/lib/text.js';
|
|
|
14
14
|
import * as lTime from '#kebab/lib/time.js';
|
|
15
15
|
import * as lCore from './core.js';
|
|
16
16
|
// --- 自己 ---
|
|
17
|
-
import * as
|
|
17
|
+
import * as lFd from './net/formdata.js';
|
|
18
18
|
import * as lRequest from './net/request.js';
|
|
19
19
|
import * as lResponse from './net/response.js';
|
|
20
20
|
/** --- ca 根证书内容 --- */
|
|
@@ -27,6 +27,14 @@ export async function getCa() {
|
|
|
27
27
|
ca = (await lFs.getContent(kebab.LIB_PATH + 'net/cacert.pem', 'utf8')) ?? '';
|
|
28
28
|
return ca;
|
|
29
29
|
}
|
|
30
|
+
/** --- 获取 mproxy 的 URL --- */
|
|
31
|
+
function getMproxyUrl(u, mproxy) {
|
|
32
|
+
return mproxy.url + (mproxy.url.includes('?') ? '&' : '?') + lText.queryStringify({
|
|
33
|
+
'url': u,
|
|
34
|
+
'auth': mproxy.auth,
|
|
35
|
+
'data': mproxy.data ? lText.stringifyJson(mproxy.data) : '{}',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
30
38
|
/** --- 复用的 hc 对象列表 --- */
|
|
31
39
|
const reuses = {
|
|
32
40
|
'default': hc.createHttpClient(),
|
|
@@ -105,11 +113,7 @@ export function fetch(input, init = {}) {
|
|
|
105
113
|
if (!hostname) {
|
|
106
114
|
throw new Error('Kebab TypeError: Hostname is empty');
|
|
107
115
|
}
|
|
108
|
-
return global.fetch(init.mproxy ?
|
|
109
|
-
'url': u,
|
|
110
|
-
'auth': init.mproxy.auth,
|
|
111
|
-
'data': init.mproxy.data ? lText.stringifyJson(init.mproxy.data) : '{}',
|
|
112
|
-
}) : u, init);
|
|
116
|
+
return global.fetch(init.mproxy ? getMproxyUrl(u, init.mproxy) : u, init);
|
|
113
117
|
}
|
|
114
118
|
/**
|
|
115
119
|
* --- 发起一个请求 ---
|
|
@@ -155,7 +159,7 @@ export async function request(u, data, opt = {}) {
|
|
|
155
159
|
headers['content-type'] = 'application/json; charset=utf-8';
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
|
-
else if (data instanceof
|
|
162
|
+
else if (data instanceof lFd.FormData) {
|
|
159
163
|
headers['content-type'] = 'multipart/form-data; boundary=' + data.getBoundary();
|
|
160
164
|
headers['content-length'] = data.getLength();
|
|
161
165
|
}
|
|
@@ -196,11 +200,7 @@ export async function request(u, data, opt = {}) {
|
|
|
196
200
|
reuses[reuse] = hc.createHttpClient();
|
|
197
201
|
}
|
|
198
202
|
req = await reuses[reuse].request({
|
|
199
|
-
'url': opt.mproxy ?
|
|
200
|
-
'url': u,
|
|
201
|
-
'auth': opt.mproxy.auth,
|
|
202
|
-
'data': opt.mproxy.data ? lText.stringifyJson(opt.mproxy.data) : '{}',
|
|
203
|
-
}) : u,
|
|
203
|
+
'url': opt.mproxy ? getMproxyUrl(u, opt.mproxy) : u,
|
|
204
204
|
'method': method,
|
|
205
205
|
'data': data,
|
|
206
206
|
'headers': headers,
|
|
@@ -250,7 +250,7 @@ export async function request(u, data, opt = {}) {
|
|
|
250
250
|
switch (req.protocol) {
|
|
251
251
|
case hc.EProtocol.HTTPS_2:
|
|
252
252
|
case hc.EProtocol.HTTP_2: {
|
|
253
|
-
|
|
253
|
+
res.headers['http-version'] = '2.0';
|
|
254
254
|
break;
|
|
255
255
|
}
|
|
256
256
|
default: {
|
|
@@ -292,7 +292,7 @@ export async function request(u, data, opt = {}) {
|
|
|
292
292
|
export function setCookie(cookie, name, value, domain, opt = {}) {
|
|
293
293
|
const tim = lTime.stamp();
|
|
294
294
|
const ttl = opt.ttl ?? 0;
|
|
295
|
-
domain =
|
|
295
|
+
domain = lText.parseHost(domain).hostname;
|
|
296
296
|
const domainN = domain.startsWith('.') ? domain.slice(1) : domain;
|
|
297
297
|
let exp = -1992199400;
|
|
298
298
|
if (ttl) {
|
|
@@ -339,7 +339,7 @@ async function buildCookieObject(cookie, setCookies, uri) {
|
|
|
339
339
|
// --- 获取定义的 domain ---
|
|
340
340
|
let domain = '', domainN = '';
|
|
341
341
|
if (cookieTmp['domain']) {
|
|
342
|
-
cookieTmp['domain'] = cookieTmp['domain'].
|
|
342
|
+
cookieTmp['domain'] = lText.parseHost(cookieTmp['domain']).hostname;
|
|
343
343
|
if (!(cookieTmp['domain'].startsWith('.'))) {
|
|
344
344
|
domain = '.' + cookieTmp['domain'];
|
|
345
345
|
domainN = cookieTmp['domain'];
|
|
@@ -404,8 +404,8 @@ async function buildCookieObject(cookie, setCookies, uri) {
|
|
|
404
404
|
'exp': exp,
|
|
405
405
|
'path': path,
|
|
406
406
|
'domain': domainN,
|
|
407
|
-
'secure': cookieTmp['secure'] !== undefined
|
|
408
|
-
'httponly': cookieTmp['httponly'] !== undefined
|
|
407
|
+
'secure': cookieTmp['secure'] !== undefined,
|
|
408
|
+
'httponly': cookieTmp['httponly'] !== undefined
|
|
409
409
|
};
|
|
410
410
|
}
|
|
411
411
|
}
|
|
@@ -424,7 +424,7 @@ export function buildCookieQuery(cookie, uri) {
|
|
|
424
424
|
continue;
|
|
425
425
|
}
|
|
426
426
|
uri.path ??= '/';
|
|
427
|
-
if (item.secure && (uri.protocol
|
|
427
|
+
if (item.secure && (uri.protocol !== 'https:')) {
|
|
428
428
|
continue;
|
|
429
429
|
}
|
|
430
430
|
// --- 判断 path 是否匹配 ---
|
|
@@ -475,7 +475,7 @@ export function resetCookieSession(cookie) {
|
|
|
475
475
|
* --- 创建 FormData 对象 ---
|
|
476
476
|
*/
|
|
477
477
|
export function getFormData() {
|
|
478
|
-
return new
|
|
478
|
+
return new lFd.FormData();
|
|
479
479
|
}
|
|
480
480
|
/** --- proxy 要剔除的基础头部 --- */
|
|
481
481
|
const proxyContinueHeaders = [
|
package/lib/s3.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export declare class S3 {
|
|
|
51
51
|
*/
|
|
52
52
|
putObject(key: string, content: string | Buffer | stream.Readable, length?: number | {
|
|
53
53
|
'length'?: number;
|
|
54
|
+
/** --- content-type,如 application/javascript --- */
|
|
54
55
|
'type'?: string;
|
|
55
56
|
'disposition'?: string;
|
|
56
57
|
'bucket'?: string;
|
package/lib/text.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ import * as kebab from '#kebab/index.js';
|
|
|
10
10
|
* @param spliter 分隔符
|
|
11
11
|
*/
|
|
12
12
|
export declare function sizeFormat(size: number, spliter?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* --- 解析主机名和端口号 ---
|
|
15
|
+
* @param host 如 example.com:8080、[::1]:8080
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseHost(host: string): {
|
|
18
|
+
'rawHostname': string;
|
|
19
|
+
'hostname': string;
|
|
20
|
+
'port': string | null;
|
|
21
|
+
};
|
|
13
22
|
/**
|
|
14
23
|
* --- 格式化一段 URL ---
|
|
15
24
|
* @param url
|
package/lib/text.js
CHANGED
|
@@ -19,6 +19,27 @@ export function sizeFormat(size, spliter = ' ') {
|
|
|
19
19
|
}
|
|
20
20
|
return (Math.round(size * 100) / 100).toString() + spliter + units[i];
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* --- 解析主机名和端口号 ---
|
|
24
|
+
* @param host 如 example.com:8080、[::1]:8080
|
|
25
|
+
*/
|
|
26
|
+
export function parseHost(host) {
|
|
27
|
+
const rtn = {
|
|
28
|
+
'rawHostname': host,
|
|
29
|
+
'hostname': host,
|
|
30
|
+
'port': null,
|
|
31
|
+
};
|
|
32
|
+
const match = /^(\[?([\w:.-]+?)\]?)(:([0-9]{1,}))?$/.exec(host);
|
|
33
|
+
if (!match) {
|
|
34
|
+
return rtn;
|
|
35
|
+
}
|
|
36
|
+
rtn.rawHostname = match[1];
|
|
37
|
+
rtn.hostname = match[2];
|
|
38
|
+
if (match[4]) {
|
|
39
|
+
rtn.port = match[4];
|
|
40
|
+
}
|
|
41
|
+
return rtn;
|
|
42
|
+
}
|
|
22
43
|
/**
|
|
23
44
|
* --- 格式化一段 URL ---
|
|
24
45
|
* @param url
|
|
@@ -80,16 +101,11 @@ export function parseUrl(url) {
|
|
|
80
101
|
url = url.slice(auth + 1);
|
|
81
102
|
}
|
|
82
103
|
if (url) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
rtn['hostname'] = url.toLowerCase();
|
|
91
|
-
rtn['host'] = rtn['hostname'];
|
|
92
|
-
}
|
|
104
|
+
// --- 只剩下 host 了 ---
|
|
105
|
+
const hostInfo = parseHost(url);
|
|
106
|
+
rtn['hostname'] = hostInfo.hostname.toLowerCase();
|
|
107
|
+
rtn['port'] = hostInfo.port;
|
|
108
|
+
rtn['host'] = url;
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
111
|
else {
|
|
@@ -200,7 +216,7 @@ export const REGEXP_IPV6 = /^(\w*?:){2,7}[\w.]*$/i;
|
|
|
200
216
|
* @param ip
|
|
201
217
|
*/
|
|
202
218
|
export function isIPv6(ip) {
|
|
203
|
-
return REGEXP_IPV6.test(ip
|
|
219
|
+
return REGEXP_IPV6.test(ip);
|
|
204
220
|
}
|
|
205
221
|
export const REGEXP_DOMAIN = /^.+?\.((?![0-9]).)+$/i;
|
|
206
222
|
/**
|
package/lib/ws.js
CHANGED
|
@@ -72,13 +72,21 @@ export class Socket {
|
|
|
72
72
|
const ca = puri ?
|
|
73
73
|
puri.protocol === 'wss:' ? await lNet.getCa() : null :
|
|
74
74
|
uri.protocol === 'wss:' ? await lNet.getCa() : null;
|
|
75
|
-
if (
|
|
76
|
-
// ---
|
|
77
|
-
headers['host'] = uri.hostname
|
|
75
|
+
if (typeof hosts === 'string' ? hosts : hosts[uri.hostname]) {
|
|
76
|
+
// --- 要设置额外的 host ---
|
|
77
|
+
headers['host'] = uri.hostname;
|
|
78
|
+
if (uri.port) {
|
|
79
|
+
if (lText.isIPv6(headers['host'])) {
|
|
80
|
+
headers['host'] = `[${headers['host']}]`;
|
|
81
|
+
}
|
|
82
|
+
headers['host'] += ':' + uri.port;
|
|
83
|
+
}
|
|
78
84
|
}
|
|
79
85
|
try {
|
|
80
86
|
// --- 重定义 IP ---
|
|
81
87
|
const host = puri?.hostname ?? uri.hostname ?? '';
|
|
88
|
+
/** --- 真正的连接远程 IP / HOST --- */
|
|
89
|
+
const rhost = typeof hosts === 'string' ? hosts : hosts[host];
|
|
82
90
|
const port = (puri ? puri.port : uri.port) ?? 443;
|
|
83
91
|
const path = puri ? puri.path + (puri.path?.includes('?') ? '&' : '?') + lText.queryStringify({
|
|
84
92
|
'url': u,
|
|
@@ -86,7 +94,7 @@ export class Socket {
|
|
|
86
94
|
}) : uri.path;
|
|
87
95
|
const cli = ca ?
|
|
88
96
|
liws.createSecureClient({
|
|
89
|
-
'hostname':
|
|
97
|
+
'hostname': rhost || host,
|
|
90
98
|
'port': port,
|
|
91
99
|
'path': path,
|
|
92
100
|
'servername': host,
|
|
@@ -97,7 +105,7 @@ export class Socket {
|
|
|
97
105
|
'ca': ca,
|
|
98
106
|
}) :
|
|
99
107
|
liws.createClient({
|
|
100
|
-
'hostname':
|
|
108
|
+
'hostname': rhost || host,
|
|
101
109
|
'port': port,
|
|
102
110
|
'path': path,
|
|
103
111
|
'headers': headers,
|
package/package.json
CHANGED
package/sys/child.js
CHANGED
|
@@ -185,7 +185,12 @@ async function requestHandler(req, res, https) {
|
|
|
185
185
|
res.end();
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
const hostInfo = lText.parseHost(host);
|
|
189
|
+
host = hostInfo.hostname;
|
|
190
|
+
if (lText.isIPv6(host)) {
|
|
191
|
+
host = `[${host}]`;
|
|
192
|
+
}
|
|
193
|
+
host = host + ':' + (req.socket.localPort ?? (https ? '443' : '80'));
|
|
189
194
|
const uri = lText.parseUrl(`http${https ? 's' : ''}://${host}${req.url ?? ''}`);
|
|
190
195
|
/** --- 当前的 vhost 配置文件 --- */
|
|
191
196
|
const vhost = await getVhostByHostname(uri.hostname ?? '');
|
package/sys/ctr.d.ts
CHANGED
|
@@ -90,19 +90,23 @@ export declare class Ctr {
|
|
|
90
90
|
setPrototype(name: string, val: string | string[] | http.IncomingHttpHeaders | Record<string, kebab.Json> | lSession.Session | lWs.Socket | null): void;
|
|
91
91
|
/**
|
|
92
92
|
* --- 实例化后会执行的方法,可重写此方法 ---
|
|
93
|
+
* @returns 返回 true 或 undefined 则继续执行 onReady,否则中止且对应的返回值将作为输出结果(WebSocket 下中止将断开连接)
|
|
93
94
|
*/
|
|
94
95
|
onLoad(): boolean | string | kebab.DbValue[] | Promise<boolean | string | kebab.DbValue[]>;
|
|
95
96
|
/**
|
|
96
97
|
* --- onLoad 执行后会执行的方法,可重写此方法 ---
|
|
98
|
+
* @returns 返回 true 或 undefined 则继续执行 action,否则中止且对应的返回值将作为输出结果(WebSocket 下中止将断开连接)
|
|
97
99
|
*/
|
|
98
100
|
onReady(): boolean | string | kebab.DbValue[] | Promise<boolean | string | kebab.DbValue[]>;
|
|
99
101
|
/**
|
|
100
102
|
* --- 整个结束前会执行本方法,可重写此方法对输出结果再处理一次(Websocket 模式无效) ---
|
|
101
103
|
* @param rtn 之前用户的输出结果
|
|
104
|
+
* @returns 处理后的输出结果,将作为最终发送给客户端的内容
|
|
102
105
|
*/
|
|
103
106
|
onUnload(rtn: boolean | string | kebab.DbValue[]): boolean | string | kebab.DbValue[] | Promise<boolean | string | kebab.DbValue[]>;
|
|
104
107
|
/**
|
|
105
108
|
* --- WebSocket 下在建立 Server 连接之前可对 WebSocket 的信息进行配置 ---
|
|
109
|
+
* @returns WebSocket 配置参数,包含自定义 header 和超时时间
|
|
106
110
|
*/
|
|
107
111
|
onUpgrade(): {
|
|
108
112
|
'headers'?: http.OutgoingHttpHeaders;
|
|
@@ -110,7 +114,9 @@ export declare class Ctr {
|
|
|
110
114
|
};
|
|
111
115
|
/**
|
|
112
116
|
* --- WebSocket 下当收到数据时会自动被调用的事件,即只文本和二进制数据,返回内容会被发送给 socket ---
|
|
113
|
-
*
|
|
117
|
+
* @param data 数据
|
|
118
|
+
* @param opcode 操作码
|
|
119
|
+
* @returns 返回内容会被发送给 socket;若返回 false 则连接会被中断;不返回则不发送任何内容
|
|
114
120
|
*/
|
|
115
121
|
onData(data: Buffer | string, opcode: lWs.EOpcode): kebab.Json;
|
|
116
122
|
/**
|
|
@@ -133,7 +139,10 @@ export declare class Ctr {
|
|
|
133
139
|
* --- WebSocket 下连接被终止后会自动被调用的事件,可重写此方法 ---
|
|
134
140
|
*/
|
|
135
141
|
onClose(): void | Promise<void>;
|
|
136
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* --- 请求发送开始时调用(仅会在 middle 内触发) ---
|
|
144
|
+
* @returns 1-自动处理 POST (默认),0-框架不自动处理 POST,-1-流程中断 (通常用于代理/反代场景)
|
|
145
|
+
*/
|
|
137
146
|
onReqStart(): number | Promise<number>;
|
|
138
147
|
/**
|
|
139
148
|
* --- 获取截止当前时间的总运行时间 ---
|
package/sys/ctr.js
CHANGED
|
@@ -140,12 +140,14 @@ export class Ctr {
|
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
142
|
* --- 实例化后会执行的方法,可重写此方法 ---
|
|
143
|
+
* @returns 返回 true 或 undefined 则继续执行 onReady,否则中止且对应的返回值将作为输出结果(WebSocket 下中止将断开连接)
|
|
143
144
|
*/
|
|
144
145
|
onLoad() {
|
|
145
146
|
return true;
|
|
146
147
|
}
|
|
147
148
|
/**
|
|
148
149
|
* --- onLoad 执行后会执行的方法,可重写此方法 ---
|
|
150
|
+
* @returns 返回 true 或 undefined 则继续执行 action,否则中止且对应的返回值将作为输出结果(WebSocket 下中止将断开连接)
|
|
149
151
|
*/
|
|
150
152
|
onReady() {
|
|
151
153
|
return true;
|
|
@@ -153,12 +155,14 @@ export class Ctr {
|
|
|
153
155
|
/**
|
|
154
156
|
* --- 整个结束前会执行本方法,可重写此方法对输出结果再处理一次(Websocket 模式无效) ---
|
|
155
157
|
* @param rtn 之前用户的输出结果
|
|
158
|
+
* @returns 处理后的输出结果,将作为最终发送给客户端的内容
|
|
156
159
|
*/
|
|
157
160
|
onUnload(rtn) {
|
|
158
161
|
return rtn;
|
|
159
162
|
}
|
|
160
163
|
/**
|
|
161
164
|
* --- WebSocket 下在建立 Server 连接之前可对 WebSocket 的信息进行配置 ---
|
|
165
|
+
* @returns WebSocket 配置参数,包含自定义 header 和超时时间
|
|
162
166
|
*/
|
|
163
167
|
onUpgrade() {
|
|
164
168
|
return {};
|
|
@@ -187,7 +191,10 @@ export class Ctr {
|
|
|
187
191
|
onClose() {
|
|
188
192
|
return;
|
|
189
193
|
}
|
|
190
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* --- 请求发送开始时调用(仅会在 middle 内触发) ---
|
|
196
|
+
* @returns 1-自动处理 POST (默认),0-框架不自动处理 POST,-1-流程中断 (通常用于代理/反代场景)
|
|
197
|
+
*/
|
|
191
198
|
onReqStart() {
|
|
192
199
|
return 1;
|
|
193
200
|
}
|
package/sys/route.js
CHANGED
|
@@ -118,10 +118,10 @@ export async function run(data) {
|
|
|
118
118
|
'mobile': data.req.headers['user-agent'] ? data.req.headers['user-agent'].toLowerCase().includes('mobile') : false,
|
|
119
119
|
'wechat': data.req.headers['user-agent'] ? data.req.headers['user-agent'].toLowerCase().includes('micromessenger') : false,
|
|
120
120
|
'miniprogram': data.req.headers['referer'] ? (data.req.headers['referer'].toLowerCase().startsWith('https://servicewechat.com/') ? 'wechat' : '') : '',
|
|
121
|
-
'https': data.uri.protocol === 'https' ? true : false,
|
|
121
|
+
'https': data.uri.protocol === 'https:' ? true : false,
|
|
122
122
|
'host': data.uri.host ?? '',
|
|
123
123
|
'hostname': data.uri.hostname ?? '',
|
|
124
|
-
'hostport': data.uri.port ? parseInt(data.uri.port) : (data.uri.protocol === 'https' ? 443 : 80),
|
|
124
|
+
'hostport': data.uri.port ? parseInt(data.uri.port) : (data.uri.protocol === 'https:' ? 443 : 80),
|
|
125
125
|
'uri': data.uri,
|
|
126
126
|
// --- 服务端用的路径 ---
|
|
127
127
|
'rootPath': data.rootPath,
|
package/www/example/ctr/test.js
CHANGED
|
@@ -206,6 +206,7 @@ export default class extends sCtr.Ctr {
|
|
|
206
206
|
`<br><a href="${this._config.const.urlBase}test/ws-server?ac=rproxy2">View ws rproxy2 (handler)</a>`,
|
|
207
207
|
`<br><a href="${this._config.const.urlBase}test/ws-client">View "test/ws-client"</a>`,
|
|
208
208
|
`<br><a href="${this._config.const.urlBase}test/ws-client?ac=mproxy">View ws mproxy</a>`,
|
|
209
|
+
`<br><a href="${this._config.const.urlBase}test/ws-client?ac=hosts">View ws hosts</a>`,
|
|
209
210
|
'<br><br><b>Ssh:</b>',
|
|
210
211
|
`<br><br><a href="${this._config.const.urlBase}test/ssh?type=shell">View "test/ssh?type=shell"</a>`,
|
|
211
212
|
`<br><a href="${this._config.const.urlBase}test/ssh?type=sftp">View "test/ssh?type=sftp"</a>`,
|
|
@@ -2877,6 +2878,8 @@ const rtn = cons.migration(rows, newTables);</pre>`);
|
|
|
2877
2878
|
async text() {
|
|
2878
2879
|
const echo = `<pre>json_encode(lText.parseUrl('HtTp://uSer:pAss@sUBDom.TopdOm23.CoM:29819/Adm@xw2Ksiz/dszas?Mdi=KdiMs1&a=JDd#hehHe'))</pre>
|
|
2879
2880
|
${lText.htmlescape(JSON.stringify(lText.parseUrl('HtTp://uSer:pAss@sUBDom.TopdOm23.CoM:29819/Adm@xw2Ksiz/dszas?Mdi=KdiMs1&a=JDd#hehHe')))}
|
|
2881
|
+
<pre>json_encode(lText.parseUrl('HtTp://uSer:pAss@[::1]:29819/Adm@xw2Ksiz/dszas?Mdi=KdiMs1&a=JDd#hehHe'))</pre>
|
|
2882
|
+
${lText.htmlescape(JSON.stringify(lText.parseUrl('HtTp://uSer:pAss@[::1]:29819/Adm@xw2Ksiz/dszas?Mdi=KdiMs1&a=JDd#hehHe')))}
|
|
2880
2883
|
<pre>json_encode(lText.parseUrl('HtTp://uSer@sUBDom.TopdOm23.CoM/Admx%20w2Ksiz/dszas'))</pre>
|
|
2881
2884
|
${lText.htmlescape(JSON.stringify(lText.parseUrl('HtTp://uSer@sUBDom.TopdOm23.CoM/Admx%20w2Ksiz/dszas')))}
|
|
2882
2885
|
<pre>json_encode(lText.parseUrl('C:\\Windows\\Mi@sc'))</pre>
|
|
@@ -3064,16 +3067,18 @@ setInterval(() => {
|
|
|
3064
3067
|
return echo + '<br>' + this._getEnd();
|
|
3065
3068
|
}
|
|
3066
3069
|
async wsClient() {
|
|
3067
|
-
const
|
|
3070
|
+
const host = (this._get['ac'] === 'hosts') ? 'www.maiyun.net:' + this._config.const.hostport : this._config.const.host;
|
|
3071
|
+
const ws = await lWs.connect('ws' + (this._config.const.https ? 's' : '') + '://' + host + '/test', {
|
|
3068
3072
|
'mproxy': this._get['ac'] === 'mproxy' ? {
|
|
3069
3073
|
'url': `ws${this._config.const.https ? 's' : ''}://${this._config.const.host}/mproxy`,
|
|
3070
3074
|
'auth': '123456'
|
|
3071
|
-
} : undefined
|
|
3075
|
+
} : undefined,
|
|
3076
|
+
'hosts': (this._get['ac'] === 'hosts') ? '127.0.0.1' : undefined
|
|
3072
3077
|
});
|
|
3073
3078
|
if (!ws) {
|
|
3074
|
-
return '<div>Connect "ws' + (this._config.const.https ? 's' : '') + '://' +
|
|
3079
|
+
return '<div>Connect "ws' + (this._config.const.https ? 's' : '') + '://' + host + '/test" failed.</div><br>' + this._getEnd();
|
|
3075
3080
|
}
|
|
3076
|
-
const echo = ['<div>Connected "ws' + (this._config.const.https ? 's' : '') + '://' +
|
|
3081
|
+
const echo = ['<div>Connected "ws' + (this._config.const.https ? 's' : '') + '://' + host + '/test".</div>'];
|
|
3077
3082
|
// --- 绑定事件 ---
|
|
3078
3083
|
await new Promise((resolve) => {
|
|
3079
3084
|
(async () => {
|