@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/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "7.6.3";
8
+ export declare const VER = "7.7.0";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  export declare const LIB_PATH: string;
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '7.6.3';
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 fd from './net/formdata.js';
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: any[] | Record<string, kebab.Json>, opt?: IRequestOptions): Promise<lResponse.Response>;
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: any[] | Record<string, kebab.Json>, opt?: IRequestOptions): Promise<any | null>;
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'?: any;
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, any> | Buffer | string | stream.Readable, opt?: IRequestOptions): Promise<lResponse.Response>;
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(): fd.FormData;
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): any;
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'?: any;
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'?: any;
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 fd from './net/formdata.js';
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 ? init.mproxy.url + (init.mproxy.url.includes('?') ? '&' : '?') + lText.queryStringify({
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 fd.FormData) {
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 ? opt.mproxy.url + (opt.mproxy.url.includes('?') ? '&' : '?') + lText.queryStringify({
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
- req.headers['http-version'] = '2.0';
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 = domain.split(':')[0];
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'].split(':')[0];
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 ? true : false,
408
- 'httponly': cookieTmp['httponly'] !== undefined ? true : false
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 === 'https')) {
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 fd.FormData();
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
- const port = url.indexOf(':');
84
- if (port > -1) {
85
- rtn['hostname'] = url.slice(0, port).toLowerCase();
86
- rtn['port'] = url.slice(port + 1);
87
- rtn['host'] = rtn['hostname'] + (rtn['port'] ? ':' + rtn['port'] : '');
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 (!ca && (typeof hosts === 'string' ? hosts : hosts[uri.hostname])) {
76
- // --- 没有 ca,但是要设置 额外的 host ---
77
- headers['host'] = uri.hostname + (uri.port ? ':' + uri.port : '');
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': (typeof hosts === 'string' ? hosts : hosts[host]) || host,
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': (typeof hosts === 'string' ? hosts : hosts[host]) || host,
108
+ 'hostname': rhost || host,
101
109
  'port': port,
102
110
  'path': path,
103
111
  'headers': headers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "7.6.3",
3
+ "version": "7.7.0",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
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
- host = host.split(':')[0] + ':' + (req.socket.localPort ?? (https ? '443' : '80'));
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
- * --- 但返回 false 连接会被中断,什么都不返回则什么都不做 ---
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
- /** --- 请求发送开始时调用,返回 -1-流程中断,一般用于代理/反代,0-框架不会自动处理 post,1-自动处理(默认)(仅会在 middle 内触发) --- */
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
- /** --- 请求发送开始时调用,返回 -1-流程中断,一般用于代理/反代,0-框架不会自动处理 post,1-自动处理(默认)(仅会在 middle 内触发) --- */
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,
@@ -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 ws = await lWs.connect('ws' + (this._config.const.https ? 's' : '') + '://' + this._config.const.host + '/test', {
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' : '') + '://' + this._config.const.host + '/test" failed.</div><br>' + this._getEnd();
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' : '') + '://' + this._config.const.host + '/test".</div>'];
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 () => {