@maiyunnet/kebab 9.1.4 → 9.2.1
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/master.js +23 -13
- 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/undici.js
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import * as undici from 'undici';
|
|
2
|
+
import * as stream from 'stream';
|
|
3
|
+
import * as dns from 'dns';
|
|
4
|
+
import * as lFs from '#kebab/lib/fs.js';
|
|
5
|
+
import * as lText from '#kebab/lib/text.js';
|
|
6
|
+
import * as lCore from '#kebab/lib/core.js';
|
|
7
|
+
import * as lCookie from '#kebab/lib/cookie.js';
|
|
8
|
+
// --- 自己 ---
|
|
9
|
+
import * as lFd from './undici/formdata.js';
|
|
10
|
+
import * as lRequest from './undici/request.js';
|
|
11
|
+
import * as lResponse from './undici/response.js';
|
|
12
|
+
/** --- 获取代理 agent --- */
|
|
13
|
+
export function getProxyAgent(url) {
|
|
14
|
+
return new undici.ProxyAgent(url);
|
|
15
|
+
}
|
|
16
|
+
/** --- 获取 mproxy 的 URL --- */
|
|
17
|
+
function getMproxyUrl(u, mproxy) {
|
|
18
|
+
return mproxy.url + (mproxy.url.includes('?') ? '&' : '?') + lText.queryStringify({
|
|
19
|
+
'url': u,
|
|
20
|
+
'auth': mproxy.auth,
|
|
21
|
+
'data': mproxy.data ? lText.stringifyJson(mproxy.data) : '{}',
|
|
22
|
+
'hosts': mproxy.hosts ? lText.stringifyJson(mproxy.hosts) : 'null',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** --- 复用的 undici.agent 对象列表 --- */
|
|
26
|
+
const agents = new Map();
|
|
27
|
+
/** --- 获取或创建 undici.agent 对象 --- */
|
|
28
|
+
function getAgent(opt = {}) {
|
|
29
|
+
const k = opt.reuse ?? 'default';
|
|
30
|
+
if (typeof k !== 'string') {
|
|
31
|
+
return k;
|
|
32
|
+
}
|
|
33
|
+
if (!agents.has(k)) {
|
|
34
|
+
agents.set(k, new undici.Agent({
|
|
35
|
+
'connect': {
|
|
36
|
+
'localAddress': opt.local,
|
|
37
|
+
lookup: buildDnsLookup(opt.hosts),
|
|
38
|
+
},
|
|
39
|
+
'pipelining': opt.keep === false ? 0 : 1,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
return agents.get(k);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* --- 创建一个请求对象 ---
|
|
46
|
+
* @param u
|
|
47
|
+
*/
|
|
48
|
+
export function open(u) {
|
|
49
|
+
return new lRequest.Request(u);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* --- 发起一个 get 请求 ---
|
|
53
|
+
* @param u 请求的 URL
|
|
54
|
+
* @param opt 参数
|
|
55
|
+
*/
|
|
56
|
+
export async function get(u, opt = {}) {
|
|
57
|
+
return request(u, undefined, opt);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* --- 发起一个 post 请求 ---
|
|
61
|
+
* @param u 请求的 URL
|
|
62
|
+
* @param data 要发送的数据
|
|
63
|
+
* @param opt 参数
|
|
64
|
+
*/
|
|
65
|
+
export async function post(u, data, opt = {}) {
|
|
66
|
+
opt.method = 'POST';
|
|
67
|
+
return request(u, data, opt);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* --- 发起 JSON 请求 ---
|
|
71
|
+
* @param u 网址
|
|
72
|
+
* @param data 数据
|
|
73
|
+
* @param opt 选项
|
|
74
|
+
*/
|
|
75
|
+
export async function postJson(u, data, opt = {}) {
|
|
76
|
+
opt.method = 'POST';
|
|
77
|
+
opt.type = 'json';
|
|
78
|
+
return request(u, data, opt);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* --- 发起 JSON 请求并解析 JSON 响应 ---
|
|
82
|
+
* @param u 网址
|
|
83
|
+
* @param data 数据
|
|
84
|
+
* @param opt 选项
|
|
85
|
+
* @returns JSON 数据,失败时返回 null
|
|
86
|
+
*/
|
|
87
|
+
export async function postJsonResponseJson(u, data, opt = {}) {
|
|
88
|
+
opt.method = 'POST';
|
|
89
|
+
opt.type = 'json';
|
|
90
|
+
const res = await request(u, data, opt);
|
|
91
|
+
const rtn = await res.getContent();
|
|
92
|
+
if (!rtn) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const json = lText.parseJson(rtn.toString());
|
|
96
|
+
if (!json) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return json;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* --- 发起 GET 请求并解析 JSON 响应 ---
|
|
103
|
+
* @param u 网址
|
|
104
|
+
* @param opt 选项
|
|
105
|
+
* @returns JSON 数据,失败时返回 null
|
|
106
|
+
*/
|
|
107
|
+
export async function getResponseJson(u, opt = {}) {
|
|
108
|
+
const res = await request(u, undefined, opt);
|
|
109
|
+
const rtn = await res.getContent();
|
|
110
|
+
if (!rtn) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const json = lText.parseJson(rtn.toString());
|
|
114
|
+
if (!json) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return json;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* --- 发起一个完全兼容 fetch 的请求 ---
|
|
121
|
+
* @param input 请求的 URL 或 Request 对象
|
|
122
|
+
* @param init 增加 mproxy、hosts
|
|
123
|
+
*/
|
|
124
|
+
export async function fetch(input, init = {}) {
|
|
125
|
+
// --- 解析 URL ---
|
|
126
|
+
let u;
|
|
127
|
+
let method = 'GET';
|
|
128
|
+
let headers = {};
|
|
129
|
+
let body;
|
|
130
|
+
// --- 处理 input 参数 ---
|
|
131
|
+
if (input instanceof Request) {
|
|
132
|
+
u = input.url;
|
|
133
|
+
method = input.method.toUpperCase();
|
|
134
|
+
for (const [key, value] of input.headers) {
|
|
135
|
+
headers[key.toLowerCase()] = value;
|
|
136
|
+
}
|
|
137
|
+
if (input.body) {
|
|
138
|
+
body = Buffer.from(await input.arrayBuffer());
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
u = input instanceof URL ? input.toString() : input;
|
|
143
|
+
}
|
|
144
|
+
// --- 处理 init 参数 ---
|
|
145
|
+
if (init.method) {
|
|
146
|
+
method = init.method.toUpperCase();
|
|
147
|
+
}
|
|
148
|
+
// --- 处理 headers ---
|
|
149
|
+
if (init.headers) {
|
|
150
|
+
if (init.headers instanceof Headers) {
|
|
151
|
+
for (const [key, value] of init.headers) {
|
|
152
|
+
headers[key.toLowerCase()] = value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (Array.isArray(init.headers)) {
|
|
156
|
+
for (const [key, value] of init.headers) {
|
|
157
|
+
headers[key.toLowerCase()] = value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
for (const key in init.headers) {
|
|
162
|
+
headers[key.toLowerCase()] = init.headers[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// --- 处理 body ---
|
|
167
|
+
if (init.body !== undefined && init.body !== null) {
|
|
168
|
+
if (typeof init.body === 'string') {
|
|
169
|
+
body = init.body;
|
|
170
|
+
}
|
|
171
|
+
else if (init.body instanceof ArrayBuffer) {
|
|
172
|
+
body = Buffer.from(init.body);
|
|
173
|
+
}
|
|
174
|
+
else if (init.body instanceof URLSearchParams) {
|
|
175
|
+
body = init.body.toString();
|
|
176
|
+
headers['content-type'] ??= 'application/x-www-form-urlencoded';
|
|
177
|
+
}
|
|
178
|
+
else if (init.body instanceof Blob) {
|
|
179
|
+
body = Buffer.from(await init.body.arrayBuffer());
|
|
180
|
+
}
|
|
181
|
+
else if (init.body instanceof FormData) {
|
|
182
|
+
// --- 原生 FormData 转为框架 FormData ---
|
|
183
|
+
const fd = getFormData();
|
|
184
|
+
for (const [key, value] of init.body.entries()) {
|
|
185
|
+
if (typeof value === 'string') {
|
|
186
|
+
fd.putString(key, value);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
fd.putBuffer(key, Buffer.from(await value.arrayBuffer()), value.name);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
body = fd;
|
|
193
|
+
headers['content-type'] = 'multipart/form-data; boundary=' + fd.getBoundary();
|
|
194
|
+
headers['content-length'] = fd.getLength().toString();
|
|
195
|
+
}
|
|
196
|
+
else if (init.body instanceof ReadableStream) {
|
|
197
|
+
body = stream.Readable.fromWeb(init.body);
|
|
198
|
+
}
|
|
199
|
+
else if (ArrayBuffer.isView(init.body)) {
|
|
200
|
+
// --- ArrayBufferView (Uint8Array 等 TypedArray) ---
|
|
201
|
+
body = Buffer.from(init.body.buffer, init.body.byteOffset, init.body.byteLength);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// --- 构建请求选项 ---
|
|
205
|
+
const opt = {
|
|
206
|
+
'method': method,
|
|
207
|
+
'headers': headers,
|
|
208
|
+
'hosts': init.hosts,
|
|
209
|
+
'mproxy': init.mproxy,
|
|
210
|
+
'follow': init.redirect === 'follow' ? 10 : 0,
|
|
211
|
+
};
|
|
212
|
+
// --- 检查是否已中止(注意:请求发起后无法中止) ---
|
|
213
|
+
if (init.signal?.aborted) {
|
|
214
|
+
return new Response(null, {
|
|
215
|
+
'status': 0,
|
|
216
|
+
'statusText': 'Aborted',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// --- 发起请求 ---
|
|
220
|
+
const res = await request(u, body, opt);
|
|
221
|
+
// --- 转换为原生 Response ---
|
|
222
|
+
const resStream = res.getStream();
|
|
223
|
+
const resHeaders = new Headers();
|
|
224
|
+
if (res.headers) {
|
|
225
|
+
for (const key in res.headers) {
|
|
226
|
+
if (key.startsWith('http-')) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const value = res.headers[key];
|
|
230
|
+
if (value === undefined) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (Array.isArray(value)) {
|
|
234
|
+
for (const v of value) {
|
|
235
|
+
resHeaders.append(key, v);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
resHeaders.set(key, value.toString());
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const status = res.headers?.['http-code'] ?? (res.error ? 0 : 200);
|
|
244
|
+
if (res.error || !resStream) {
|
|
245
|
+
return new Response(null, {
|
|
246
|
+
'status': status,
|
|
247
|
+
'statusText': res.error?.message ?? 'Unknown Error',
|
|
248
|
+
'headers': resHeaders,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return new Response(stream.Readable.toWeb(resStream), {
|
|
252
|
+
'status': status,
|
|
253
|
+
'statusText': '',
|
|
254
|
+
'headers': resHeaders,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/** --- 构建自定义 DNS 查询函数 --- */
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
259
|
+
function buildDnsLookup(hosts) {
|
|
260
|
+
if (!hosts) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return (hostname, opts, cb) => {
|
|
264
|
+
const override = typeof hosts === 'string' ? hosts : hosts[hostname];
|
|
265
|
+
if (override) {
|
|
266
|
+
cb(null, [{ 'address': override, 'family': lText.isIPv4(override) ? 4 : 6 }]);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
dns.lookup(hostname, opts, cb);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* --- 发起一个请求 ---
|
|
275
|
+
* @param opt 配置项
|
|
276
|
+
*/
|
|
277
|
+
export async function request(u, data, opt = {}) {
|
|
278
|
+
const uri = lText.parseUrl(u);
|
|
279
|
+
/** --- 正向代理的地址 --- */
|
|
280
|
+
const puri = opt.mproxy ? lText.parseUrl(opt.mproxy.url) : null;
|
|
281
|
+
const method = opt.method ?? 'GET';
|
|
282
|
+
const type = opt.type ?? 'form';
|
|
283
|
+
const timeout = opt.timeout ?? 10;
|
|
284
|
+
/** --- 追踪 location 次数,0 为不追踪,默认为 0 --- */
|
|
285
|
+
const follow = opt.follow ?? 0;
|
|
286
|
+
const hosts = opt.hosts ?? {};
|
|
287
|
+
const headers = {};
|
|
288
|
+
if (opt.headers) {
|
|
289
|
+
for (const key in opt.headers) {
|
|
290
|
+
headers[key.toLowerCase()] = opt.headers[key];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
headers['user-agent'] ??= 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36';
|
|
294
|
+
// --- DATA ---
|
|
295
|
+
if (method === 'GET') {
|
|
296
|
+
if (data && !(data instanceof stream.Readable) && !Buffer.isBuffer(data)) {
|
|
297
|
+
u += (u.includes('?') ? '&' : '?') +
|
|
298
|
+
(typeof data === 'string' ? data : lText.queryStringify(data));
|
|
299
|
+
data = undefined;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// --- POST ---
|
|
304
|
+
if (data && !(data instanceof stream.Readable) && !Buffer.isBuffer(data) && typeof data !== 'string') {
|
|
305
|
+
if (type === 'form') {
|
|
306
|
+
data = lText.queryStringify(data);
|
|
307
|
+
headers['content-type'] = 'application/x-www-form-urlencoded';
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
data = lText.stringifyJson(data);
|
|
311
|
+
headers['content-type'] = 'application/json; charset=utf-8';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (data instanceof lFd.FormData) {
|
|
315
|
+
headers['content-type'] = 'multipart/form-data; boundary=' + data.getBoundary();
|
|
316
|
+
headers['content-length'] = data.getLength();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
headers['accept-encoding'] = 'gzip, deflate';
|
|
320
|
+
// --- cookie 托管 ---
|
|
321
|
+
if (opt.cookie) {
|
|
322
|
+
headers['cookie'] = lCookie.buildCookieQuery(opt.cookie, uri);
|
|
323
|
+
}
|
|
324
|
+
// --- 发起请求 ---
|
|
325
|
+
let req;
|
|
326
|
+
try {
|
|
327
|
+
/** --- 定义请求 host --- */
|
|
328
|
+
const hostname = (puri ? puri.hostname : uri.hostname) ?? '';
|
|
329
|
+
if (!hostname) {
|
|
330
|
+
const res = new lResponse.Response(null);
|
|
331
|
+
res.error = {
|
|
332
|
+
'name': 'Possible mProxy error',
|
|
333
|
+
'message': 'hostname not found',
|
|
334
|
+
};
|
|
335
|
+
if (opt.log === undefined || opt.log) {
|
|
336
|
+
lCore.log({}, `[NET][REQUEST] ${res.error.message}`, '-neterror');
|
|
337
|
+
}
|
|
338
|
+
return res;
|
|
339
|
+
}
|
|
340
|
+
if (typeof hosts === 'string' ?
|
|
341
|
+
!hosts :
|
|
342
|
+
(hosts[hostname] !== undefined && !hosts[hostname])) {
|
|
343
|
+
const res = new lResponse.Response(null);
|
|
344
|
+
res.error = {
|
|
345
|
+
'name': 'hosts error',
|
|
346
|
+
'message': 'hosts param error',
|
|
347
|
+
};
|
|
348
|
+
if (opt.log === undefined || opt.log) {
|
|
349
|
+
lCore.log({}, `[NET][REQUEST] ${res.error.message}`, '-neterror');
|
|
350
|
+
}
|
|
351
|
+
return res;
|
|
352
|
+
}
|
|
353
|
+
const agent = getAgent(opt);
|
|
354
|
+
req = await undici.request(opt.mproxy ? getMproxyUrl(u, opt.mproxy) : u, {
|
|
355
|
+
'method': method,
|
|
356
|
+
'body': data,
|
|
357
|
+
'headers': headers,
|
|
358
|
+
'headersTimeout': timeout * 1_000,
|
|
359
|
+
'bodyTimeout': timeout * 1_000,
|
|
360
|
+
'dispatcher': agent,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
const res = new lResponse.Response(null);
|
|
365
|
+
res.error = err;
|
|
366
|
+
if (opt.log === undefined || opt.log) {
|
|
367
|
+
lCore.log({}, `[NET][REQUEST] ${err.message}`, '-neterror');
|
|
368
|
+
}
|
|
369
|
+
return res;
|
|
370
|
+
}
|
|
371
|
+
// --- 处理返回值 ---
|
|
372
|
+
// --- 是否追踪 cookie ---
|
|
373
|
+
if (opt.cookie) {
|
|
374
|
+
// --- 提取 cookie ---
|
|
375
|
+
await lCookie.buildCookieObject(opt.cookie, (req.headers['set-cookie'] ?? []), uri);
|
|
376
|
+
}
|
|
377
|
+
// --- 创建 Response 对象 ---
|
|
378
|
+
const res = new lResponse.Response(req);
|
|
379
|
+
res.headers = req.headers;
|
|
380
|
+
res.headers['http-code'] = req.statusCode;
|
|
381
|
+
res.headers['http-url'] = u;
|
|
382
|
+
// --- 直接下载到文件 ---
|
|
383
|
+
/** --- 已下载文件的 size --- */
|
|
384
|
+
let total = 0;
|
|
385
|
+
if (opt.save && (!req.headers['location'] || follow === 0)) {
|
|
386
|
+
const save = opt.save;
|
|
387
|
+
const stream = res.getStream();
|
|
388
|
+
if (stream) {
|
|
389
|
+
await new Promise(resolve => {
|
|
390
|
+
const ws = lFs.createWriteStream(save);
|
|
391
|
+
stream.on('error', () => {
|
|
392
|
+
resolve();
|
|
393
|
+
}).pipe(ws);
|
|
394
|
+
ws.on('finish', () => {
|
|
395
|
+
lFs.stats(save).then((stat) => {
|
|
396
|
+
total = stat?.size ?? 0;
|
|
397
|
+
resolve();
|
|
398
|
+
}).catch(() => {
|
|
399
|
+
resolve();
|
|
400
|
+
});
|
|
401
|
+
}).on('error', () => {
|
|
402
|
+
resolve();
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (total) {
|
|
408
|
+
res.setContent(total.toString());
|
|
409
|
+
}
|
|
410
|
+
// --- 判断 follow 追踪 ---
|
|
411
|
+
if (follow === 0) {
|
|
412
|
+
return res;
|
|
413
|
+
}
|
|
414
|
+
if (!res.headers['location']) {
|
|
415
|
+
return res;
|
|
416
|
+
}
|
|
417
|
+
// --- 哦,要追踪 ---
|
|
418
|
+
headers['referer'] = u;
|
|
419
|
+
let nextMethod = method;
|
|
420
|
+
let nextData = data;
|
|
421
|
+
const status = res.headers['http-code'];
|
|
422
|
+
if (status === 303 || ((status === 301 || status === 302) && method === 'POST')) {
|
|
423
|
+
nextMethod = 'GET';
|
|
424
|
+
nextData = undefined;
|
|
425
|
+
}
|
|
426
|
+
return request(lText.urlResolve(u, req.headers['location']), nextData, {
|
|
427
|
+
...opt,
|
|
428
|
+
...{
|
|
429
|
+
'method': nextMethod,
|
|
430
|
+
'type': type,
|
|
431
|
+
'timeout': timeout,
|
|
432
|
+
'follow': follow - 1,
|
|
433
|
+
'hosts': hosts,
|
|
434
|
+
'headers': headers,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* --- 创建 FormData 对象 ---
|
|
440
|
+
*/
|
|
441
|
+
export function getFormData() {
|
|
442
|
+
return new lFd.FormData();
|
|
443
|
+
}
|
|
444
|
+
/** --- proxy 要剔除的基础头部 --- */
|
|
445
|
+
const proxyContinueHeaders = [
|
|
446
|
+
'host', 'connection', 'http-code', 'http-url',
|
|
447
|
+
'transfer-encoding'
|
|
448
|
+
];
|
|
449
|
+
/**
|
|
450
|
+
* --- 剔除不代理的 header,返回新的 header ---
|
|
451
|
+
* @param headers 剔除前的 header
|
|
452
|
+
* @param res 直接设置头部而不返回,可置空
|
|
453
|
+
* @param filter 返回 true 则留下
|
|
454
|
+
*/
|
|
455
|
+
export function filterHeaders(headers, res, filter) {
|
|
456
|
+
const heads = {};
|
|
457
|
+
for (const h in headers) {
|
|
458
|
+
if (proxyContinueHeaders.includes(h)) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (h.includes(':') || h.includes('(')) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const v = headers[h];
|
|
465
|
+
if (v === undefined) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (filter && !filter(h)) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (res) {
|
|
472
|
+
res.setHeader(h, v);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
heads[h] = v;
|
|
476
|
+
}
|
|
477
|
+
return heads;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* --- 正向 mproxy 代理,注意提前处理不要自动处理 post 数据,读取 get 的 url 为实际请求地址 ---
|
|
481
|
+
* --- get: url, auth ---
|
|
482
|
+
* @param ctr 当前控制器
|
|
483
|
+
* @param auth 校验字符串,读取 get 的 auth 和本参数做比对
|
|
484
|
+
* @param opt 参数
|
|
485
|
+
*/
|
|
486
|
+
export async function mproxy(ctr, auth, opt = {}) {
|
|
487
|
+
const req = ctr.getPrototype('_req');
|
|
488
|
+
const res = ctr.getPrototype('_res');
|
|
489
|
+
/** --- 客户端请求中的 get 的数据 --- */
|
|
490
|
+
const get = ctr.getPrototype('_get');
|
|
491
|
+
if (req.readableEnded) {
|
|
492
|
+
return -3;
|
|
493
|
+
}
|
|
494
|
+
if (get['auth'] !== auth) {
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
if (!get['url']) {
|
|
498
|
+
return -1;
|
|
499
|
+
}
|
|
500
|
+
opt.method = req.method ?? 'GET';
|
|
501
|
+
opt.headers ??= {};
|
|
502
|
+
/** --- 传来的 hosts --- */
|
|
503
|
+
const hosts = lText.parseJson(get['hosts']);
|
|
504
|
+
const headers = Object.assign(filterHeaders(req.headers, undefined, opt.filter), opt.headers);
|
|
505
|
+
// --- 发起请求 ---
|
|
506
|
+
const rres = await request(get['url'], req, {
|
|
507
|
+
...opt,
|
|
508
|
+
'hosts': lText.logicalOr(hosts, opt.hosts),
|
|
509
|
+
headers
|
|
510
|
+
});
|
|
511
|
+
const stream = rres.getRawStream();
|
|
512
|
+
if (!stream) {
|
|
513
|
+
return -3;
|
|
514
|
+
}
|
|
515
|
+
if (rres.error) {
|
|
516
|
+
return -2;
|
|
517
|
+
}
|
|
518
|
+
if (rres.headers) {
|
|
519
|
+
filterHeaders(rres.headers, res, opt.filter);
|
|
520
|
+
}
|
|
521
|
+
lCore.writeHead(res, rres.headers?.['http-code'] ?? 200);
|
|
522
|
+
await new Promise((resolve) => {
|
|
523
|
+
stream.pipe(res).on('finish', () => {
|
|
524
|
+
resolve();
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
return 1;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* --- 获取 mproxy 的附加数据 ---
|
|
531
|
+
* @param ctr 当前控制器
|
|
532
|
+
*/
|
|
533
|
+
export function mproxyData(ctr) {
|
|
534
|
+
const get = ctr.getPrototype('_get');
|
|
535
|
+
if (!get['data']) {
|
|
536
|
+
return {};
|
|
537
|
+
}
|
|
538
|
+
const data = lText.parseJson(get['data']);
|
|
539
|
+
if (!data) {
|
|
540
|
+
return {};
|
|
541
|
+
}
|
|
542
|
+
return data;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* --- 反向代理,注意提前处理不要自动处理 post 数据,将本服务器的某个路由反代到其他网址 ---
|
|
546
|
+
* @param ctr 当前控制器
|
|
547
|
+
* @param route 要反代的路由
|
|
548
|
+
* @param opt 参数
|
|
549
|
+
*/
|
|
550
|
+
export async function rproxy(ctr, route, opt = {}) {
|
|
551
|
+
const req = ctr.getPrototype('_req');
|
|
552
|
+
const res = ctr.getPrototype('_res');
|
|
553
|
+
const config = ctr.getPrototype('_config');
|
|
554
|
+
const path = config.const.path + (config.const.qs ? '?' + config.const.qs : '');
|
|
555
|
+
if (req.readableEnded) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
for (const key in route) {
|
|
559
|
+
if (!path.startsWith(key)) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
// --- 找到了,做转发 ---
|
|
563
|
+
// --- key 类似:test/net-rproxy/ ---
|
|
564
|
+
// --- 值类似:https://cdn.jsdelivr.net/npm/deskrt@2.0.10/ ---
|
|
565
|
+
/** --- 要拼接的地址 --- */
|
|
566
|
+
const lpath = path.slice(key.length);
|
|
567
|
+
opt.method = req.method ?? 'GET';
|
|
568
|
+
opt.headers ??= {};
|
|
569
|
+
const headers = Object.assign(filterHeaders(req.headers, undefined, opt.filter), opt.headers);
|
|
570
|
+
// --- 发起请求 ---
|
|
571
|
+
const rres = await request(route[key] + lpath, req, {
|
|
572
|
+
...opt,
|
|
573
|
+
headers,
|
|
574
|
+
});
|
|
575
|
+
const stream = rres.getRawStream();
|
|
576
|
+
if (!stream) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
if (rres.error) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
if (rres.headers) {
|
|
583
|
+
filterHeaders(rres.headers, res, opt.filter);
|
|
584
|
+
}
|
|
585
|
+
lCore.writeHead(res, rres.headers?.['http-code'] ?? 200);
|
|
586
|
+
await new Promise((resolve) => {
|
|
587
|
+
stream.pipe(res).on('finish', () => {
|
|
588
|
+
resolve();
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
/* eslint-enable */
|
package/lib/ws.d.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import * as http from 'http';
|
|
7
7
|
import * as net from 'net';
|
|
8
8
|
import * as kebab from '#kebab/index.js';
|
|
9
|
-
import * as
|
|
9
|
+
import * as lUndici from '#kebab/lib/undici.js';
|
|
10
|
+
import * as lCookie from '#kebab/lib/cookie.js';
|
|
10
11
|
import * as sCtr from '#kebab/sys/ctr.js';
|
|
11
12
|
/** --- 一般用 SIMPLE --- */
|
|
12
13
|
export declare enum EFrameReceiveMode {
|
|
@@ -30,9 +31,9 @@ export interface IConnectOptions {
|
|
|
30
31
|
/** --- 自定义 host 映射,如 {'www.maiyun.net': '127.0.0.1'},或全部映射到一个 host --- */
|
|
31
32
|
'hosts'?: Record<string, string> | string;
|
|
32
33
|
'local'?: string;
|
|
33
|
-
'headers'?:
|
|
34
|
+
'headers'?: lUndici.THttpHeaders;
|
|
34
35
|
/** --- cookie 托管对象 --- */
|
|
35
|
-
'cookie'?: Record<string,
|
|
36
|
+
'cookie'?: Record<string, lCookie.ICookie>;
|
|
36
37
|
/** --- 小帧模式,默认 false --- */
|
|
37
38
|
'mode'?: EFrameReceiveMode;
|
|
38
39
|
/** --- 加密模式,默认 true --- */
|
|
@@ -50,7 +51,7 @@ export interface IMproxyOptions {
|
|
|
50
51
|
/** --- 自定义 host 映射,如 {'www.maiyun.net': '127.0.0.1'},或全部映射到一个 host --- */
|
|
51
52
|
'hosts'?: Record<string, string> | string;
|
|
52
53
|
'local'?: string;
|
|
53
|
-
'headers'?:
|
|
54
|
+
'headers'?: lUndici.THttpHeaders;
|
|
54
55
|
/** --- 过滤 header,返回 true 则留下 --- */
|
|
55
56
|
filter?: (h: string) => boolean;
|
|
56
57
|
/** --- 小帧模式,默认 false --- */
|
|
@@ -65,7 +66,7 @@ export interface IRproxyOptions {
|
|
|
65
66
|
/** --- 自定义 host 映射,如 {'www.maiyun.net': '127.0.0.1'},或全部映射到一个 host --- */
|
|
66
67
|
'hosts'?: Record<string, string> | string;
|
|
67
68
|
'local'?: string;
|
|
68
|
-
'headers'?:
|
|
69
|
+
'headers'?: lUndici.THttpHeaders;
|
|
69
70
|
/** --- 过滤 header,返回 true 则留下 --- */
|
|
70
71
|
filter?: (h: string) => boolean;
|
|
71
72
|
/** --- 小帧模式,默认 false --- */
|
package/lib/ws.js
CHANGED
|
@@ -2,7 +2,8 @@ import * as net from 'net';
|
|
|
2
2
|
// --- 第三方 ---
|
|
3
3
|
import * as liws from '@litert/websocket';
|
|
4
4
|
import * as lText from '#kebab/lib/text.js';
|
|
5
|
-
import * as
|
|
5
|
+
import * as lUndici from '#kebab/lib/undici.js';
|
|
6
|
+
import * as lCookie from '#kebab/lib/cookie.js';
|
|
6
7
|
/** --- 一般用 SIMPLE --- */
|
|
7
8
|
export var EFrameReceiveMode;
|
|
8
9
|
(function (EFrameReceiveMode) {
|
|
@@ -66,12 +67,12 @@ export class Socket {
|
|
|
66
67
|
headers['user-agent'] ??= 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36';
|
|
67
68
|
// --- cookie 托管 ---
|
|
68
69
|
if (opt.cookie) {
|
|
69
|
-
headers['cookie'] =
|
|
70
|
+
headers['cookie'] = lCookie.buildCookieQuery(opt.cookie, uri);
|
|
70
71
|
}
|
|
71
72
|
// --- ssl ---
|
|
72
|
-
const
|
|
73
|
-
puri.protocol === 'wss:' ?
|
|
74
|
-
uri.protocol === 'wss:' ?
|
|
73
|
+
const isSsl = puri ?
|
|
74
|
+
(puri.protocol === 'wss:' ? true : false) :
|
|
75
|
+
(uri.protocol === 'wss:' ? true : false);
|
|
75
76
|
if (typeof hosts === 'string' ? hosts : hosts[uri.hostname]) {
|
|
76
77
|
// --- 要设置额外的 host ---
|
|
77
78
|
headers['host'] = uri.hostname;
|
|
@@ -92,7 +93,7 @@ export class Socket {
|
|
|
92
93
|
'url': u,
|
|
93
94
|
'auth': opt.mproxy?.auth ?? ''
|
|
94
95
|
}) : uri.path;
|
|
95
|
-
const cli =
|
|
96
|
+
const cli = isSsl ?
|
|
96
97
|
liws.createSecureClient({
|
|
97
98
|
'hostname': rhost || host,
|
|
98
99
|
'port': port,
|
|
@@ -102,7 +103,6 @@ export class Socket {
|
|
|
102
103
|
'connectTimeout': timeout * 1000,
|
|
103
104
|
'frameReceiveMode': mode,
|
|
104
105
|
'localAddress': local,
|
|
105
|
-
'ca': ca,
|
|
106
106
|
}) :
|
|
107
107
|
liws.createClient({
|
|
108
108
|
'hostname': rhost || host,
|
|
@@ -397,7 +397,7 @@ export async function mproxy(ctr, auth, opt = {}) {
|
|
|
397
397
|
return -1;
|
|
398
398
|
}
|
|
399
399
|
opt.headers ??= {};
|
|
400
|
-
const headers = Object.assign(
|
|
400
|
+
const headers = Object.assign(lUndici.filterHeaders(req.headers, undefined, opt.filter), opt.headers);
|
|
401
401
|
// --- 发起请求 ---
|
|
402
402
|
/** --- 远程端的双向 socket --- */
|
|
403
403
|
const rsocket = await connect(get['url'], {
|
|
@@ -421,7 +421,7 @@ export async function rproxy(ctr, url, opt = {}) {
|
|
|
421
421
|
/** --- 请求端产生的双向 socket --- */
|
|
422
422
|
const socket = ctr.getPrototype('_socket');
|
|
423
423
|
opt.headers ??= {};
|
|
424
|
-
const headers = Object.assign(
|
|
424
|
+
const headers = Object.assign(lUndici.filterHeaders(req.headers, undefined, opt.filter), opt.headers);
|
|
425
425
|
// --- 发起请求 ---
|
|
426
426
|
/** --- 远程端的双向 socket --- */
|
|
427
427
|
const rsocket = await connect(url, {
|