@maiyunnet/kebab 9.1.4 → 9.2.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 +2237 -437
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/buffer.d.ts +5 -0
- package/lib/buffer.js +13 -0
- package/lib/cookie.d.ts +44 -0
- package/lib/cookie.js +216 -0
- package/lib/core.d.ts +3 -2
- package/lib/core.js +15 -12
- package/lib/dns.d.ts +1 -1
- package/lib/dns.js +4 -6
- package/lib/net/request.d.ts +2 -1
- package/lib/net.d.ts +3 -38
- package/lib/net.js +6 -219
- package/lib/socket.d.ts +4 -3
- package/lib/turnstile.js +2 -2
- package/lib/undici/formdata.d.ts +83 -0
- package/lib/undici/formdata.js +166 -0
- package/lib/undici/request.d.ts +86 -0
- package/lib/undici/request.js +120 -0
- package/lib/undici/response.d.ts +39 -0
- package/lib/undici/response.js +111 -0
- package/lib/undici.d.ts +174 -0
- package/lib/undici.js +595 -0
- package/lib/ws.d.ts +6 -5
- package/lib/ws.js +9 -9
- package/package.json +3 -2
- package/sys/route.js +5 -2
- package/www/example/ctr/middle.js +3 -2
- package/www/example/ctr/test.d.ts +32 -0
- package/www/example/ctr/test.js +526 -4
- package/www/example/route.json +1 -0
package/lib/net.js
CHANGED
|
@@ -11,8 +11,8 @@ import * as hc from '@litert/http-client';
|
|
|
11
11
|
import * as kebab from '#kebab/index.js';
|
|
12
12
|
import * as lFs from '#kebab/lib/fs.js';
|
|
13
13
|
import * as lText from '#kebab/lib/text.js';
|
|
14
|
-
import * as
|
|
15
|
-
import * as
|
|
14
|
+
import * as lCore from '#kebab/lib/core.js';
|
|
15
|
+
import * as lCookie from '#kebab/lib/cookie.js';
|
|
16
16
|
// --- 自己 ---
|
|
17
17
|
import * as lFd from './net/formdata.js';
|
|
18
18
|
import * as lRequest from './net/request.js';
|
|
@@ -309,7 +309,7 @@ export async function request(u, data, opt = {}) {
|
|
|
309
309
|
}
|
|
310
310
|
// --- cookie 托管 ---
|
|
311
311
|
if (opt.cookie) {
|
|
312
|
-
headers['cookie'] = buildCookieQuery(opt.cookie, uri);
|
|
312
|
+
headers['cookie'] = lCookie.buildCookieQuery(opt.cookie, uri);
|
|
313
313
|
}
|
|
314
314
|
// --- 发起请求 ---
|
|
315
315
|
let req;
|
|
@@ -346,7 +346,7 @@ export async function request(u, data, opt = {}) {
|
|
|
346
346
|
'method': method,
|
|
347
347
|
'data': data,
|
|
348
348
|
'headers': headers,
|
|
349
|
-
'timeout': timeout *
|
|
349
|
+
'timeout': timeout * 1_000,
|
|
350
350
|
'localAddress': local,
|
|
351
351
|
'ca': ca,
|
|
352
352
|
'connectionOptions': {
|
|
@@ -367,7 +367,7 @@ export async function request(u, data, opt = {}) {
|
|
|
367
367
|
// --- 是否追踪 cookie ---
|
|
368
368
|
if (opt.cookie) {
|
|
369
369
|
// --- 提取 cookie ---
|
|
370
|
-
await buildCookieObject(opt.cookie, (req.headers['set-cookie'] ?? []), uri);
|
|
370
|
+
await lCookie.buildCookieObject(opt.cookie, (req.headers['set-cookie'] ?? []), uri);
|
|
371
371
|
}
|
|
372
372
|
// --- 直接下载到文件 ---
|
|
373
373
|
/** --- 已下载文件的 size --- */
|
|
@@ -437,220 +437,6 @@ export async function request(u, data, opt = {}) {
|
|
|
437
437
|
'reuse': reuse
|
|
438
438
|
});
|
|
439
439
|
}
|
|
440
|
-
/**
|
|
441
|
-
* --- 对 cookie 对象进行操作 ---
|
|
442
|
-
* @param cookie 要操作的对象
|
|
443
|
-
* @param name 名
|
|
444
|
-
* @param value 值
|
|
445
|
-
* @param domain 应用网址,如 .xxx.com
|
|
446
|
-
* @param opt 选项 ttl, path, ssl, httponly
|
|
447
|
-
*/
|
|
448
|
-
export function setCookie(cookie, name, value, domain, opt = {}) {
|
|
449
|
-
const tim = lTime.stamp();
|
|
450
|
-
const ttl = opt.ttl ?? 0;
|
|
451
|
-
domain = lText.parseHost(domain).hostname;
|
|
452
|
-
const domainN = domain.startsWith('.') ? domain.slice(1) : domain;
|
|
453
|
-
let exp = -1992199400;
|
|
454
|
-
if (ttl) {
|
|
455
|
-
exp = tim + ttl;
|
|
456
|
-
}
|
|
457
|
-
cookie[name + '-' + domainN] = {
|
|
458
|
-
'name': name,
|
|
459
|
-
'value': value,
|
|
460
|
-
'exp': exp,
|
|
461
|
-
'path': opt['path'] ?? '/',
|
|
462
|
-
'domain': domainN,
|
|
463
|
-
'secure': opt['ssl'] ? true : false,
|
|
464
|
-
'httponly': opt['httponly'] ? true : false
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* --- 根据 Set-Cookie 头部转换到 cookie 对象 ---
|
|
469
|
-
* @param cookie cookie 对象
|
|
470
|
-
* @param setCookies 头部的 set-cookie 数组
|
|
471
|
-
* @param uri 请求的 URI 对象
|
|
472
|
-
*/
|
|
473
|
-
async function buildCookieObject(cookie, setCookies, uri) {
|
|
474
|
-
const tim = lTime.stamp();
|
|
475
|
-
uri.path ??= '/';
|
|
476
|
-
for (const setCookie of setCookies) {
|
|
477
|
-
const cookieTmp = {};
|
|
478
|
-
const list = setCookie.split(';');
|
|
479
|
-
// --- 提取 set-cookie 中的定义信息 ---
|
|
480
|
-
for (let index = 0; index < list.length; ++index) {
|
|
481
|
-
const item = list[index];
|
|
482
|
-
const arr = item.split('=');
|
|
483
|
-
/** --- 提取 key 并修整 --- */
|
|
484
|
-
const key = arr[0].trim();
|
|
485
|
-
if (key === '') {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
/** --- 提取 value --- */
|
|
489
|
-
let val = '';
|
|
490
|
-
if (arr.length > 1) {
|
|
491
|
-
val = item.slice(item.indexOf('=') + 1).trim();
|
|
492
|
-
}
|
|
493
|
-
if (index === 0) {
|
|
494
|
-
// --- 用户定义的信息 ---
|
|
495
|
-
cookieTmp['name'] = key;
|
|
496
|
-
try {
|
|
497
|
-
cookieTmp['value'] = decodeURIComponent(val);
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
cookieTmp['value'] = val;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
else {
|
|
504
|
-
// --- cookie 配置信息,可转小写方便读取 ---
|
|
505
|
-
cookieTmp[key.toLowerCase()] = val;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
// --- 获取定义的 domain ---
|
|
509
|
-
let domain = '', domainN = '';
|
|
510
|
-
if (cookieTmp['domain']) {
|
|
511
|
-
cookieTmp['domain'] = lText.parseHost(cookieTmp['domain']).hostname;
|
|
512
|
-
if (!(cookieTmp['domain'].startsWith('.'))) {
|
|
513
|
-
domain = '.' + cookieTmp['domain'];
|
|
514
|
-
domainN = cookieTmp['domain'];
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
domain = cookieTmp['domain'];
|
|
518
|
-
domainN = cookieTmp['domain'].slice(1);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
domain = '.' + (uri.hostname ?? '');
|
|
523
|
-
domainN = uri.hostname ?? '';
|
|
524
|
-
}
|
|
525
|
-
// --- 判断有没有设置 domain 的权限 ---
|
|
526
|
-
// --- uri.hostname vs domain(domainN) ---
|
|
527
|
-
// --- ok.xxx.com vs .ok.xxx.com: true ---
|
|
528
|
-
// --- ok.xxx.com vs .xxx.com: true ---
|
|
529
|
-
// --- z.ok.xxx.com vs .xxx.com: true ---
|
|
530
|
-
// --- ok.xxx.com vs .zz.ok.xxx.com: false ---
|
|
531
|
-
if (uri.hostname !== domainN) {
|
|
532
|
-
// --- 设置的域名和当前 host 不相等,如果是 IP、无 . 域名,则直接失败 ---
|
|
533
|
-
if (!lText.isDomain(uri.hostname ?? '')) {
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
const parseDomain = await lText.parseDomain(domainN);
|
|
537
|
-
if (parseDomain.tld === domainN.toLowerCase()) {
|
|
538
|
-
// --- 不能给 tld 设置 cookie ---
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
// --- 判断访问路径 (uri['host']) 是不是设置域名 (domain) 的孩子,domain 必须是 uriHost 的同级或者父辈 ---
|
|
542
|
-
if (!((uri.hostname ?? '').endsWith(domain))) {
|
|
543
|
-
// --- false 代表进入了,代表失败 ---
|
|
544
|
-
// --- ok.xxx.com, .xxx.com: true ---
|
|
545
|
-
// --- ok.xxx.com, .ppp.com: false ---
|
|
546
|
-
// --- ok.xxx.com, .p.ok.xxx.com: false ---
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
const cookieKey = cookieTmp['name'] + '-' + domainN;
|
|
551
|
-
let exp = -1992199400;
|
|
552
|
-
if (cookieTmp['max-age'] !== undefined) {
|
|
553
|
-
const maxAge = Number(cookieTmp['max-age']);
|
|
554
|
-
if (!(isNaN(maxAge))) {
|
|
555
|
-
if (maxAge <= 0) {
|
|
556
|
-
delete cookie[cookieKey];
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
exp = tim + maxAge;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
if ((exp === -1992199400) && cookieTmp['expires']) {
|
|
563
|
-
const expires = lTime.stamp(cookieTmp['expires']);
|
|
564
|
-
if (!(isNaN(expires))) {
|
|
565
|
-
if (expires <= tim) {
|
|
566
|
-
delete cookie[cookieKey];
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
exp = expires;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
// --- path ---
|
|
573
|
-
let path = cookieTmp['path'] ?? '';
|
|
574
|
-
if (path === '') {
|
|
575
|
-
const srp = (uri.pathname ?? '').lastIndexOf('/');
|
|
576
|
-
path = (uri.pathname ?? '').slice(0, srp + 1);
|
|
577
|
-
}
|
|
578
|
-
else if (!path.startsWith('/')) {
|
|
579
|
-
path = '/' + path;
|
|
580
|
-
}
|
|
581
|
-
cookie[cookieKey] = {
|
|
582
|
-
'name': cookieTmp['name'],
|
|
583
|
-
'value': cookieTmp['value'],
|
|
584
|
-
'exp': exp,
|
|
585
|
-
'path': path,
|
|
586
|
-
'domain': domainN,
|
|
587
|
-
'secure': cookieTmp['secure'] !== undefined,
|
|
588
|
-
'httponly': cookieTmp['httponly'] !== undefined
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* --- 对象转换为 Cookie 拼接字符串(会自动筛掉不能发送的 cookie) ---
|
|
594
|
-
* @param cookie cookie 对象
|
|
595
|
-
* @param uri 请求的 URI 对象
|
|
596
|
-
*/
|
|
597
|
-
export function buildCookieQuery(cookie, uri) {
|
|
598
|
-
const tim = lTime.stamp();
|
|
599
|
-
let cookieStr = '';
|
|
600
|
-
for (const key in cookie) {
|
|
601
|
-
const item = cookie[key];
|
|
602
|
-
if ((item.exp < tim) && (item.exp !== -1992199400)) {
|
|
603
|
-
delete cookie[key];
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
uri.path ??= '/';
|
|
607
|
-
if (item.secure && (uri.protocol !== 'https:')) {
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
// --- 判断 path 是否匹配 ---
|
|
611
|
-
if (!uri.path.startsWith(item.path)) {
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
const domain = '.' + item.domain;
|
|
615
|
-
// --- 判断 $uri['host'] 必须是 $domain 的同级或子级 ---
|
|
616
|
-
// --- $uri['host'] vs $domain ---
|
|
617
|
-
// --- ok.xxx.com vs .ok.xxx.com: true ---
|
|
618
|
-
// --- ok.xxx.com vs .xxx.com: true ---
|
|
619
|
-
// --- z.ok.xxx.com vs .xxx.com: true ---
|
|
620
|
-
// --- ok.xxx.com vs .zz.ok.xxx.com: false ---
|
|
621
|
-
if ('.' + (uri.hostname ?? '') !== domain) {
|
|
622
|
-
// --- 域名不相等,那么判断当前域名 host 是不是 domain 的孩子 ---
|
|
623
|
-
if (!(uri.hostname.endsWith(domain))) {
|
|
624
|
-
// --- false 代表进入,被排除了,因为 cookie 的 domain 和当前 host 后半部分,代表不是 domain 的孩子 ---
|
|
625
|
-
// --- ok.xxx.com, .zz.ok.xxx.com: false ---
|
|
626
|
-
// --- pp.ok.xxx.com, .zz.ok.xxx.com: false ---
|
|
627
|
-
// --- q.b.ok.xx.com, .zz.ok.xxx.com: false ---
|
|
628
|
-
// --- z.ok.xxx.com, .xxx.com: true ---
|
|
629
|
-
// --- xx.xxx.com, .ok.xxx.com: false ---
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
cookieStr += item.name + '=' + encodeURIComponent(item.value) + '; ';
|
|
634
|
-
}
|
|
635
|
-
if (cookieStr !== '') {
|
|
636
|
-
return cookieStr.slice(0, -2);
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
return '';
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* --- 模拟重启浏览器后的状态 ---
|
|
644
|
-
* @param cookie cookie 对象
|
|
645
|
-
*/
|
|
646
|
-
export function resetCookieSession(cookie) {
|
|
647
|
-
for (const key in cookie) {
|
|
648
|
-
const item = cookie[key];
|
|
649
|
-
if (item.exp === -1992199400000) {
|
|
650
|
-
delete cookie[key];
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
440
|
/**
|
|
655
441
|
* --- 创建 FormData 对象 ---
|
|
656
442
|
*/
|
|
@@ -808,3 +594,4 @@ export async function rproxy(ctr, route, opt = {}) {
|
|
|
808
594
|
}
|
|
809
595
|
return false;
|
|
810
596
|
}
|
|
597
|
+
/* eslint-enable */
|
package/lib/socket.d.ts
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
* Last: 2025-9-25 16:49:48
|
|
5
5
|
*/
|
|
6
6
|
import net from 'net';
|
|
7
|
-
import * as
|
|
7
|
+
import * as lUndici from '#kebab/lib/undici.js';
|
|
8
8
|
import * as lWs from '#kebab/lib/ws.js';
|
|
9
|
+
import * as lCokie from '#kebab/lib/cookie.js';
|
|
9
10
|
export interface IRwebsocketOptions {
|
|
10
11
|
/** --- 秒数 --- */
|
|
11
12
|
'timeout'?: number;
|
|
12
13
|
'hosts'?: Record<string, string>;
|
|
13
14
|
'local'?: string;
|
|
14
|
-
'headers'?:
|
|
15
|
+
'headers'?: lUndici.THttpHeaders;
|
|
15
16
|
/** --- cookie 托管对象 --- */
|
|
16
|
-
'cookie'?: Record<string,
|
|
17
|
+
'cookie'?: Record<string, lCokie.ICookie>;
|
|
17
18
|
/** --- 小帧模式,默认 false --- */
|
|
18
19
|
'mode'?: lWs.EFrameReceiveMode;
|
|
19
20
|
/** --- 加密模式,默认 true --- */
|
package/lib/turnstile.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as tc from 'tencentcloud-sdk-nodejs';
|
|
7
7
|
// --- 库和定义 ---
|
|
8
|
-
import * as
|
|
8
|
+
import * as lUndici from '#kebab/lib/undici.js';
|
|
9
9
|
import * as lText from '#kebab/lib/text.js';
|
|
10
10
|
/**
|
|
11
11
|
* 0. CloudFlare:https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
|
|
@@ -22,7 +22,7 @@ export async function verify(ctr, opt) {
|
|
|
22
22
|
switch (opt.factory) {
|
|
23
23
|
case EFACTORY.CLOUDFLARE: {
|
|
24
24
|
// --- CloudFlare 验证 ---
|
|
25
|
-
const res = await
|
|
25
|
+
const res = await lUndici.postJson('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
|
26
26
|
'secret': config.turnstile['CF'].skey,
|
|
27
27
|
'response': opt.token,
|
|
28
28
|
'remoteip': opt.ip,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project: Kebab, User: JianSuoQiYue
|
|
3
|
+
* Date: 2020-04-07 23:45:03
|
|
4
|
+
* Last: 2020-04-07 23:45:07, 2022-09-10 01:35:25
|
|
5
|
+
*/
|
|
6
|
+
import * as stream from 'stream';
|
|
7
|
+
/** --- Item 对象 --- */
|
|
8
|
+
export type IItem = {
|
|
9
|
+
/** --- key 键 --- */
|
|
10
|
+
'key': string;
|
|
11
|
+
'type': 'string';
|
|
12
|
+
/** --- 字符串值 --- */
|
|
13
|
+
'value': string;
|
|
14
|
+
'path': '';
|
|
15
|
+
} | {
|
|
16
|
+
/** --- key 键 --- */
|
|
17
|
+
'key': string;
|
|
18
|
+
'type': 'file';
|
|
19
|
+
/** --- 文件名 --- */
|
|
20
|
+
'value': string;
|
|
21
|
+
/** --- 文件路径 --- */
|
|
22
|
+
'path': string;
|
|
23
|
+
} | {
|
|
24
|
+
/** --- key 键 --- */
|
|
25
|
+
'key': string;
|
|
26
|
+
'type': 'buffer';
|
|
27
|
+
/** --- 文件名 --- */
|
|
28
|
+
'value': string;
|
|
29
|
+
/** --- Buffer 数据 --- */
|
|
30
|
+
'path': Buffer;
|
|
31
|
+
};
|
|
32
|
+
export declare class FormData extends stream.Readable {
|
|
33
|
+
/** --- read 调用次数 --- */
|
|
34
|
+
private _num;
|
|
35
|
+
/** --- 要编译的数据 --- */
|
|
36
|
+
private readonly _data;
|
|
37
|
+
/** --- 分隔符 --- */
|
|
38
|
+
private readonly _boundary;
|
|
39
|
+
/** --- 正在读取文件吗 --- */
|
|
40
|
+
private _fileReading;
|
|
41
|
+
/** --- 是否已经结束 --- */
|
|
42
|
+
private _close;
|
|
43
|
+
/** --- 总字节长度 --- */
|
|
44
|
+
private _length;
|
|
45
|
+
/** --- 已发送字节长度 --- */
|
|
46
|
+
private _sent;
|
|
47
|
+
/**
|
|
48
|
+
* --- 添加字符串 ---
|
|
49
|
+
* @param key 键
|
|
50
|
+
* @param val 值
|
|
51
|
+
*/
|
|
52
|
+
putString(key: string, val: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* --- 添加文件 ---
|
|
55
|
+
* @param key 键
|
|
56
|
+
* @param path 路径
|
|
57
|
+
* @param fname 可选,文件名
|
|
58
|
+
*/
|
|
59
|
+
putFile(key: string, path: string, fname?: string): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* --- 添加 Buffer 数据 ---
|
|
62
|
+
* @param key 键
|
|
63
|
+
* @param buffer Buffer 数据
|
|
64
|
+
* @param fname 文件名
|
|
65
|
+
*/
|
|
66
|
+
putBuffer(key: string, buffer: Buffer, fname: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* --- 获取 boundary ---
|
|
69
|
+
*/
|
|
70
|
+
getBoundary(): string;
|
|
71
|
+
/**
|
|
72
|
+
* --- 获取总字节长度 ---
|
|
73
|
+
*/
|
|
74
|
+
getLength(): number;
|
|
75
|
+
/**
|
|
76
|
+
* --- 获取已发送的字节长度 ---
|
|
77
|
+
*/
|
|
78
|
+
getSent(): number;
|
|
79
|
+
/**
|
|
80
|
+
* --- 间隔读取(on data 或 pipe 触发)---
|
|
81
|
+
*/
|
|
82
|
+
_read(): void;
|
|
83
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project: Kebab, User: JianSuoQiYue
|
|
3
|
+
* Date: 2020-04-07 23:45:03
|
|
4
|
+
* Last: 2020-04-07 23:45:07, 2022-09-10 01:35:25
|
|
5
|
+
*/
|
|
6
|
+
import * as stream from 'stream';
|
|
7
|
+
import * as mime from '@litert/mime';
|
|
8
|
+
import * as core from '#kebab/lib/core.js';
|
|
9
|
+
import * as fs from '#kebab/lib/fs.js';
|
|
10
|
+
export class FormData extends stream.Readable {
|
|
11
|
+
/** --- read 调用次数 --- */
|
|
12
|
+
_num = 0;
|
|
13
|
+
/** --- 要编译的数据 --- */
|
|
14
|
+
_data = [];
|
|
15
|
+
/** --- 分隔符 --- */
|
|
16
|
+
_boundary = '----Kebab' + core.random(29, core.RANDOM_LUN);
|
|
17
|
+
/** --- 正在读取文件吗 --- */
|
|
18
|
+
_fileReading = false;
|
|
19
|
+
/** --- 是否已经结束 --- */
|
|
20
|
+
_close = false;
|
|
21
|
+
/** --- 总字节长度 --- */
|
|
22
|
+
_length = 4 + this._boundary.length;
|
|
23
|
+
/** --- 已发送字节长度 --- */
|
|
24
|
+
_sent = 0;
|
|
25
|
+
/**
|
|
26
|
+
* --- 添加字符串 ---
|
|
27
|
+
* @param key 键
|
|
28
|
+
* @param val 值
|
|
29
|
+
*/
|
|
30
|
+
putString(key, val) {
|
|
31
|
+
this._data.push({
|
|
32
|
+
'key': key,
|
|
33
|
+
'type': 'string',
|
|
34
|
+
'value': val,
|
|
35
|
+
'path': ''
|
|
36
|
+
});
|
|
37
|
+
this._length += this._boundary.length + 49 + Buffer.byteLength(key) + Buffer.byteLength(val);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* --- 添加文件 ---
|
|
41
|
+
* @param key 键
|
|
42
|
+
* @param path 路径
|
|
43
|
+
* @param fname 可选,文件名
|
|
44
|
+
*/
|
|
45
|
+
async putFile(key, path, fname) {
|
|
46
|
+
path = path.replace(/\\/g, '/');
|
|
47
|
+
const stat = await fs.stats(path);
|
|
48
|
+
if (!stat) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (!fname) {
|
|
52
|
+
const lio = path.lastIndexOf('/');
|
|
53
|
+
fname = lio === -1 ? path : path.slice(lio + 1);
|
|
54
|
+
}
|
|
55
|
+
this._data.push({
|
|
56
|
+
'key': key,
|
|
57
|
+
'type': 'file',
|
|
58
|
+
'value': fname,
|
|
59
|
+
'path': path
|
|
60
|
+
});
|
|
61
|
+
this._length += this._boundary.length +
|
|
62
|
+
76 + Buffer.byteLength(key) + Buffer.byteLength(fname) +
|
|
63
|
+
mime.getMime(fname).length + stat.size + 2;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* --- 添加 Buffer 数据 ---
|
|
68
|
+
* @param key 键
|
|
69
|
+
* @param buffer Buffer 数据
|
|
70
|
+
* @param fname 文件名
|
|
71
|
+
*/
|
|
72
|
+
putBuffer(key, buffer, fname) {
|
|
73
|
+
this._data.push({
|
|
74
|
+
'key': key,
|
|
75
|
+
'type': 'buffer',
|
|
76
|
+
'value': fname,
|
|
77
|
+
'path': buffer
|
|
78
|
+
});
|
|
79
|
+
this._length += this._boundary.length +
|
|
80
|
+
76 + Buffer.byteLength(key) + Buffer.byteLength(fname) +
|
|
81
|
+
mime.getMime(fname).length + buffer.byteLength + 2;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* --- 获取 boundary ---
|
|
85
|
+
*/
|
|
86
|
+
getBoundary() {
|
|
87
|
+
return this._boundary;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* --- 获取总字节长度 ---
|
|
91
|
+
*/
|
|
92
|
+
getLength() {
|
|
93
|
+
return this._length;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* --- 获取已发送的字节长度 ---
|
|
97
|
+
*/
|
|
98
|
+
getSent() {
|
|
99
|
+
return this._sent;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* --- 间隔读取(on data 或 pipe 触发)---
|
|
103
|
+
*/
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
105
|
+
_read() {
|
|
106
|
+
if (this._close) {
|
|
107
|
+
// --- 结束了 ---
|
|
108
|
+
this.push(null);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// --- 文件读取中 ---
|
|
112
|
+
if (this._fileReading) {
|
|
113
|
+
// --- 等待下面 fileReadable 的 on data 或 end 事件 ---
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// --- 获取当前 item ---
|
|
117
|
+
const item = this._data[this._num];
|
|
118
|
+
if (!item) {
|
|
119
|
+
this._close = true;
|
|
120
|
+
const push = `--${this._boundary}--`;
|
|
121
|
+
this._sent += Buffer.byteLength(push);
|
|
122
|
+
this.push(push);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (item.type === 'buffer') {
|
|
126
|
+
// --- Buffer 数据 ---
|
|
127
|
+
const push = `--${this._boundary}\r\nContent-Disposition: form-data; name="${item.key}"; filename="${item.value}"\r\nContent-Type: ${mime.getMime(item.value)}\r\n\r\n`;
|
|
128
|
+
this._sent += Buffer.byteLength(push);
|
|
129
|
+
this.push(push);
|
|
130
|
+
this._sent += item.path.byteLength;
|
|
131
|
+
this.push(item.path);
|
|
132
|
+
const pushEnd = '\r\n';
|
|
133
|
+
this._sent += Buffer.byteLength(pushEnd);
|
|
134
|
+
this.push(pushEnd);
|
|
135
|
+
}
|
|
136
|
+
else if (item.type === 'string') {
|
|
137
|
+
// --- 字段 ---
|
|
138
|
+
const push = `--${this._boundary}\r\nContent-Disposition: form-data; name="${item.key}"\r\n\r\n${item.value}\r\n`;
|
|
139
|
+
this._sent += Buffer.byteLength(push);
|
|
140
|
+
this.push(push);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// --- 文件 ---
|
|
144
|
+
const push = `--${this._boundary}\r\nContent-Disposition: form-data; name="${item.key}"; filename="${item.value}"\r\nContent-Type: ${mime.getMime(item.value)}\r\n\r\n`;
|
|
145
|
+
this._sent += Buffer.byteLength(push);
|
|
146
|
+
this.push(push);
|
|
147
|
+
// --- 创建流 ---
|
|
148
|
+
this._fileReading = true;
|
|
149
|
+
const fileReadable = fs.createReadStream(item.path);
|
|
150
|
+
fileReadable.on('data', (chunk) => {
|
|
151
|
+
if (!(chunk instanceof Buffer)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this._sent += chunk.byteLength;
|
|
155
|
+
this.push(chunk);
|
|
156
|
+
});
|
|
157
|
+
fileReadable.on('end', () => {
|
|
158
|
+
this._fileReading = false;
|
|
159
|
+
const push = '\r\n';
|
|
160
|
+
this._sent += Buffer.byteLength(push);
|
|
161
|
+
this.push(push);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
++this._num;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project: Kebab, User: JianSuoQiYue
|
|
3
|
+
* Date: 2020-4-9 20:02:39
|
|
4
|
+
* Last: 2020-4-9 20:47:58, 2022-09-10 01:35:34, 2025-9-23 12:41:58
|
|
5
|
+
*/
|
|
6
|
+
import * as stream from 'stream';
|
|
7
|
+
import * as lCookie from '#kebab/lib/cookie.js';
|
|
8
|
+
import * as lUndici from '#kebab/lib/undici.js';
|
|
9
|
+
import * as lResponse from './response.js';
|
|
10
|
+
export declare class Request {
|
|
11
|
+
/** --- get 或 post 的数据 --- */
|
|
12
|
+
private _data;
|
|
13
|
+
/** --- 访问的 URL --- */
|
|
14
|
+
private readonly _url;
|
|
15
|
+
/** --- 要传递的参数 --- */
|
|
16
|
+
private _opt;
|
|
17
|
+
constructor(url: string);
|
|
18
|
+
/**
|
|
19
|
+
* --- 设置 get 或 post 的数据 ---
|
|
20
|
+
* @param data 数据
|
|
21
|
+
*/
|
|
22
|
+
data(data: Record<string, any> | Buffer | string | stream.Readable): this;
|
|
23
|
+
/**
|
|
24
|
+
* --- 设置 get 或 post 请求 ---
|
|
25
|
+
* @param method
|
|
26
|
+
*/
|
|
27
|
+
method(method: 'GET' | 'POST'): this;
|
|
28
|
+
/**
|
|
29
|
+
* --- method get 方法别名 ---
|
|
30
|
+
*/
|
|
31
|
+
get(): this;
|
|
32
|
+
/**
|
|
33
|
+
* --- method post 方法别名 ---
|
|
34
|
+
*/
|
|
35
|
+
post(): this;
|
|
36
|
+
/**
|
|
37
|
+
* --- 设置提交模式,json 还是普通 form ---
|
|
38
|
+
* @param type
|
|
39
|
+
*/
|
|
40
|
+
type(type: 'form' | 'json'): this;
|
|
41
|
+
/**
|
|
42
|
+
* --- type json 方法别名 ---
|
|
43
|
+
*/
|
|
44
|
+
json(): this;
|
|
45
|
+
/**
|
|
46
|
+
* --- 设置请求有效期 ---
|
|
47
|
+
* @param timeout 秒
|
|
48
|
+
*/
|
|
49
|
+
timeout(timeout: number): this;
|
|
50
|
+
/**
|
|
51
|
+
* --- 设置是否跟随请求方的 location,留空为跟随,不设置为不跟随 ---
|
|
52
|
+
* @param follow
|
|
53
|
+
*/
|
|
54
|
+
follow(follow?: number): this;
|
|
55
|
+
/**
|
|
56
|
+
* --- 设置域名 -> ip的对应键值,就像电脑里的 hosts 一样 ---
|
|
57
|
+
* @param hosts
|
|
58
|
+
*/
|
|
59
|
+
hosts(hosts: Record<string, string> | string): this;
|
|
60
|
+
/**
|
|
61
|
+
* --- 设置后将直接保存到本地文件,不会返回,save 为本地实体路径 ---
|
|
62
|
+
* @param save
|
|
63
|
+
*/
|
|
64
|
+
save(save: string): this;
|
|
65
|
+
/**
|
|
66
|
+
* --- 设置使用的本地网卡 IP ---
|
|
67
|
+
* @param addr
|
|
68
|
+
*/
|
|
69
|
+
local(addr: string): this;
|
|
70
|
+
/**
|
|
71
|
+
* --- 批量设置提交的 headers ---
|
|
72
|
+
* @param headers
|
|
73
|
+
*/
|
|
74
|
+
headers(headers: lUndici.THttpHeaders): this;
|
|
75
|
+
/**
|
|
76
|
+
* --- 设置单条 header ---
|
|
77
|
+
* @param name
|
|
78
|
+
* @param val
|
|
79
|
+
*/
|
|
80
|
+
setHeader(name: string, val: string): this;
|
|
81
|
+
/**
|
|
82
|
+
* --- 发起请求 ---
|
|
83
|
+
* @param cookie
|
|
84
|
+
*/
|
|
85
|
+
request(cookie?: Record<string, lCookie.ICookie>): Promise<lResponse.Response>;
|
|
86
|
+
}
|