topbit 3.1.0 → 3.1.2
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/README.cn.md +1 -1
- package/README.md +1 -1
- package/demo/group-api.js +1 -1
- package/demo/group-api2.js +1 -1
- package/demo/monitor.js +6 -3
- package/package.json +1 -1
- package/src/extends/Http2Pool.js +143 -309
- package/src/extends/http2proxy.js +39 -141
- package/src/extends/proxy.js +16 -17
- package/src/lib/npargv.js +224 -280
- package/test/test-route-sort.js +1 -1
- package/test/test-route2.js +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const http2 = require('node:http2')
|
|
4
|
-
const Http2Pool = require('./Http2Pool')
|
|
4
|
+
const Http2Pool = require('./Http2Pool.js')
|
|
5
5
|
|
|
6
6
|
let error_502_text = `<!DOCTYPE html><html>
|
|
7
7
|
<head>
|
|
@@ -52,6 +52,19 @@ function fmtpath(path) {
|
|
|
52
52
|
return `${path}*`
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// 主机名提取 (IPv6 兼容优化版)
|
|
56
|
+
function extractHostname(host) {
|
|
57
|
+
if (!host) return ''
|
|
58
|
+
if (host.charCodeAt(0) === 91) { // '[' IPv6
|
|
59
|
+
const end = host.indexOf(']')
|
|
60
|
+
return end > -1 ? host.substring(0, end + 1) : host
|
|
61
|
+
}
|
|
62
|
+
const idx = host.indexOf(':')
|
|
63
|
+
if (idx === -1) return host
|
|
64
|
+
if (host.indexOf(':', idx + 1) !== -1) return host // 裸 IPv6
|
|
65
|
+
return host.substring(0, idx)
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
let Http2Proxy = function (options = {}) {
|
|
56
69
|
|
|
57
70
|
if (!(this instanceof Http2Proxy)) return Http2Proxy(options)
|
|
@@ -188,8 +201,8 @@ Http2Proxy.prototype.checkAndSetConfig = function (backend_obj, tmp) {
|
|
|
188
201
|
|
|
189
202
|
}
|
|
190
203
|
|
|
191
|
-
if (tmp.
|
|
192
|
-
backend_obj.
|
|
204
|
+
if (tmp.maxConnect && typeof tmp.maxConnect === 'number' && tmp.maxConnect > 1)
|
|
205
|
+
backend_obj.maxConnect = tmp.maxConnect
|
|
193
206
|
|
|
194
207
|
if (tmp.debug !== undefined) backend_obj.debug = tmp.debug
|
|
195
208
|
|
|
@@ -244,8 +257,7 @@ Http2Proxy.prototype.setHostProxy = function (cfg) {
|
|
|
244
257
|
weight: 1,
|
|
245
258
|
weightCount: 0,
|
|
246
259
|
reconnDelay: 500,
|
|
247
|
-
|
|
248
|
-
maxConnect: tmp.maxConnect || 0,
|
|
260
|
+
maxConnect: tmp.maxConnect || 10,
|
|
249
261
|
debug: this.debug,
|
|
250
262
|
h2Pool: null,
|
|
251
263
|
timeout: this.timeout,
|
|
@@ -268,7 +280,6 @@ Http2Proxy.prototype.setHostProxy = function (cfg) {
|
|
|
268
280
|
|
|
269
281
|
backend_obj.h2Pool = new Http2Pool({
|
|
270
282
|
debug: backend_obj.debug,
|
|
271
|
-
max: backend_obj.max,
|
|
272
283
|
url: backend_obj.url,
|
|
273
284
|
connectOptions: backend_obj.connectOptions,
|
|
274
285
|
parent: backend_obj,
|
|
@@ -338,23 +349,6 @@ Http2Proxy.prototype.getBackend = function (c, host) {
|
|
|
338
349
|
}
|
|
339
350
|
}
|
|
340
351
|
|
|
341
|
-
if (!pr.alive) {
|
|
342
|
-
pr.h2Pool && pr.h2Pool.delayConnect()
|
|
343
|
-
|
|
344
|
-
for (let i = prlist.length - 1; i >= 0 ; i--) {
|
|
345
|
-
|
|
346
|
-
pr = prlist[i]
|
|
347
|
-
|
|
348
|
-
if (pr.alive) {
|
|
349
|
-
return pr
|
|
350
|
-
} else {
|
|
351
|
-
pr.h2Pool && pr.h2Pool.delayConnect()
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return null
|
|
356
|
-
}
|
|
357
|
-
|
|
358
352
|
return pr
|
|
359
353
|
}
|
|
360
354
|
|
|
@@ -401,22 +395,7 @@ Http2Proxy.prototype.mid = function () {
|
|
|
401
395
|
|
|
402
396
|
return async (c, next) => {
|
|
403
397
|
|
|
404
|
-
let host = c.host
|
|
405
|
-
|
|
406
|
-
let hind = c.host.length - 1
|
|
407
|
-
|
|
408
|
-
if (hind > 4) {
|
|
409
|
-
let eind = hind - 5
|
|
410
|
-
|
|
411
|
-
while (hind >= eind) {
|
|
412
|
-
if (c.host[hind] === ':') {
|
|
413
|
-
host = c.host.substring(0, hind)
|
|
414
|
-
break
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
hind--
|
|
418
|
-
}
|
|
419
|
-
}
|
|
398
|
+
let host = extractHostname(c.host)
|
|
420
399
|
|
|
421
400
|
if (!self.hostProxy[host] || !self.hostProxy[host][c.routepath]) {
|
|
422
401
|
if (self.full) {
|
|
@@ -427,25 +406,7 @@ Http2Proxy.prototype.mid = function () {
|
|
|
427
406
|
}
|
|
428
407
|
|
|
429
408
|
let pr = self.getBackend(c, host)
|
|
430
|
-
|
|
431
|
-
if (!pr) {
|
|
432
|
-
pr = self.getBackend(c, host)
|
|
433
|
-
|
|
434
|
-
if (!pr) {
|
|
435
|
-
await c.ext.delay(9)
|
|
436
|
-
pr = self.getBackend(c, host)
|
|
437
|
-
|
|
438
|
-
if (!pr) {
|
|
439
|
-
for (let i = 0; i < 200; i++) {
|
|
440
|
-
await c.ext.delay(6 + i)
|
|
441
|
-
pr = self.getBackend(c, host)
|
|
442
|
-
if (pr) break
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (!pr) return c.status(503).to(error_503_text)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
409
|
+
if (!pr) return c.status(503).to(error_503_text)
|
|
449
410
|
|
|
450
411
|
if (self.addIP && c.headers['x-real-ip']) {
|
|
451
412
|
c.headers['x-real-ip'] += `,${c.ip}`
|
|
@@ -454,7 +415,6 @@ Http2Proxy.prototype.mid = function () {
|
|
|
454
415
|
}
|
|
455
416
|
|
|
456
417
|
let hii = pr.h2Pool
|
|
457
|
-
let session_client
|
|
458
418
|
|
|
459
419
|
try {
|
|
460
420
|
if (pr.headers) {
|
|
@@ -462,73 +422,38 @@ Http2Proxy.prototype.mid = function () {
|
|
|
462
422
|
}
|
|
463
423
|
|
|
464
424
|
if (pr.rewrite) {
|
|
465
|
-
let rpath = pr.rewrite(c, c.major
|
|
425
|
+
let rpath = pr.rewrite(c, c.major > 1 ? c.headers[':path'] : c.req.url)
|
|
466
426
|
|
|
467
427
|
if (rpath) {
|
|
468
428
|
let path_typ = typeof rpath
|
|
469
429
|
if (path_typ === 'object' && rpath.redirect) {
|
|
470
430
|
return c.setHeader('location', rpath.redirect)
|
|
471
431
|
} else if (path_typ === 'string') {
|
|
472
|
-
c.
|
|
432
|
+
if (c.major > 1)
|
|
433
|
+
c.headers[':path'] = rpath
|
|
434
|
+
else c.req.url = rpath
|
|
473
435
|
}
|
|
474
436
|
}
|
|
475
437
|
}
|
|
476
438
|
|
|
477
|
-
session_client = await hii.getSession()
|
|
478
|
-
|
|
479
|
-
if (session_client.deny) {
|
|
480
|
-
for (let i = 0; i < 50; i++) {
|
|
481
|
-
await c.ext.delay(5 + i)
|
|
482
|
-
session_client = await hii.getSession()
|
|
483
|
-
if (!session_client.deny) break
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (session_client.deny)
|
|
487
|
-
return c.status(429).to('服务繁忙,请稍后再试')
|
|
488
|
-
}
|
|
489
|
-
|
|
490
439
|
await new Promise(async (rv, rj) => {
|
|
491
440
|
let resolved = false
|
|
492
441
|
let rejected = false
|
|
493
442
|
let request_stream = c.stream
|
|
494
443
|
let stm = null
|
|
495
444
|
|
|
496
|
-
stm = await hii.request(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
stm = null
|
|
503
|
-
})
|
|
445
|
+
stm = await hii.request(c.major > 1 ? c.headers : this.fmtHeaders(c.headers, c))
|
|
446
|
+
.catch(err => {
|
|
447
|
+
rejected = true
|
|
448
|
+
rj(err)
|
|
449
|
+
stm = null
|
|
450
|
+
})
|
|
504
451
|
|
|
505
452
|
if (!stm) {
|
|
506
453
|
rj(new Error('request failed'))
|
|
507
454
|
return false
|
|
508
455
|
}
|
|
509
456
|
|
|
510
|
-
let timeout_handler = () => {
|
|
511
|
-
timeout_timer = null
|
|
512
|
-
|
|
513
|
-
//强制的异常结束,这意味着session的其他stream也会出现问题。
|
|
514
|
-
try {
|
|
515
|
-
!stm.closed && stm.close(http2.constants.NGHTTP2_CANCEL)
|
|
516
|
-
stm.destroy()
|
|
517
|
-
if (session_client.session && !session_client.session.destroyed) {
|
|
518
|
-
session_client.session.destroy()
|
|
519
|
-
}
|
|
520
|
-
} catch(e) {}
|
|
521
|
-
|
|
522
|
-
if (!resolved && !rejected) {
|
|
523
|
-
rejected = true
|
|
524
|
-
rj(new Error('force destroy stream, request timeout'))
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
session_client.aliveStreams++
|
|
529
|
-
|
|
530
|
-
let timeout_timer = setTimeout(timeout_handler, pr.timeout + 5000)
|
|
531
|
-
|
|
532
457
|
c.stream.on('timeout', () => {
|
|
533
458
|
stm.close(http2.constants.NGHTTP2_CANCEL)
|
|
534
459
|
//stm.destroy()
|
|
@@ -541,11 +466,6 @@ Http2Proxy.prototype.mid = function () {
|
|
|
541
466
|
})
|
|
542
467
|
|
|
543
468
|
c.stream.on('error', err => {
|
|
544
|
-
if (timeout_timer) {
|
|
545
|
-
clearTimeout(timeout_timer)
|
|
546
|
-
timeout_timer = null
|
|
547
|
-
}
|
|
548
|
-
|
|
549
469
|
stm.close(http2.constants.NGHTTP2_INTERNAL_ERROR)
|
|
550
470
|
stm.destroy()
|
|
551
471
|
})
|
|
@@ -562,11 +482,6 @@ Http2Proxy.prototype.mid = function () {
|
|
|
562
482
|
})
|
|
563
483
|
|
|
564
484
|
stm.on('aborted', err => {
|
|
565
|
-
if (timeout_timer) {
|
|
566
|
-
clearTimeout(timeout_timer)
|
|
567
|
-
timeout_timer = null
|
|
568
|
-
}
|
|
569
|
-
|
|
570
485
|
!stm.destroyed && stm.destroy()
|
|
571
486
|
|
|
572
487
|
if (!resolved && !rejected) {
|
|
@@ -576,13 +491,6 @@ Http2Proxy.prototype.mid = function () {
|
|
|
576
491
|
})
|
|
577
492
|
|
|
578
493
|
stm.on('close', () => {
|
|
579
|
-
if (timeout_timer) {
|
|
580
|
-
clearTimeout(timeout_timer)
|
|
581
|
-
timeout_timer = null
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
stm.removeAllListeners()
|
|
585
|
-
|
|
586
494
|
if (stm.rstCode === http2.constants.NGHTTP2_NO_ERROR) {
|
|
587
495
|
if (!resolved && !rejected) {
|
|
588
496
|
resolved = true
|
|
@@ -594,13 +502,9 @@ Http2Proxy.prototype.mid = function () {
|
|
|
594
502
|
rj(new Error(`stream close, exit code ${stm.rstCode}`))
|
|
595
503
|
}
|
|
596
504
|
}
|
|
597
|
-
|
|
598
|
-
session_client.aliveStreams--
|
|
599
505
|
})
|
|
600
506
|
|
|
601
507
|
stm.on('response', (headers, flags) => {
|
|
602
|
-
timeout_timer && clearTimeout(timeout_timer)
|
|
603
|
-
timeout_timer = setTimeout(timeout_handler, pr.timeout + 5000)
|
|
604
508
|
if (c.res && c.res.writable) {
|
|
605
509
|
if (c.res.respond) {
|
|
606
510
|
c.res.respond(headers)
|
|
@@ -636,25 +540,21 @@ Http2Proxy.prototype.mid = function () {
|
|
|
636
540
|
stm.end()
|
|
637
541
|
})
|
|
638
542
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
data_count++
|
|
642
|
-
c.res && c.res.writable && c.res.write(chunk)
|
|
543
|
+
const onDrain = () => stm.resume()
|
|
544
|
+
if (c.res) c.res.on('drain', onDrain)
|
|
643
545
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
546
|
+
stm.on('data', chunk => {
|
|
547
|
+
if (c.res && c.res.writable) {
|
|
548
|
+
if (c.res.write(chunk) === false) {
|
|
549
|
+
stm.pause()
|
|
550
|
+
}
|
|
648
551
|
}
|
|
649
552
|
})
|
|
650
553
|
|
|
651
554
|
stm.on('end', () => {
|
|
652
|
-
if (
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
stm.close()
|
|
555
|
+
if (c.res) c.res.removeListener('drain', onDrain)
|
|
556
|
+
|
|
557
|
+
!stm.closed && stm.close()
|
|
658
558
|
|
|
659
559
|
if (!resolved && !rejected) {
|
|
660
560
|
resolved = true
|
|
@@ -666,8 +566,6 @@ Http2Proxy.prototype.mid = function () {
|
|
|
666
566
|
} catch (err) {
|
|
667
567
|
self.debug && console.error(err||'request null error')
|
|
668
568
|
c.status(503).to(error_503_text)
|
|
669
|
-
} finally {
|
|
670
|
-
session_client = null
|
|
671
569
|
}
|
|
672
570
|
|
|
673
571
|
}
|
package/src/extends/proxy.js
CHANGED
|
@@ -4,6 +4,20 @@ const urlparse = require('node:url');
|
|
|
4
4
|
const http = require('node:http');
|
|
5
5
|
const https = require('node:https');
|
|
6
6
|
|
|
7
|
+
// 主机名提取 (IPv6 兼容优化版)
|
|
8
|
+
function extractHostname(host) {
|
|
9
|
+
if (!host) return ''
|
|
10
|
+
if (host.charCodeAt(0) === 91) { // '[' IPv6
|
|
11
|
+
const end = host.indexOf(']')
|
|
12
|
+
return end > -1 ? host.substring(0, end + 1) : host
|
|
13
|
+
}
|
|
14
|
+
const idx = host.indexOf(':')
|
|
15
|
+
if (idx === -1) return host
|
|
16
|
+
if (host.indexOf(':', idx + 1) !== -1) return host // 裸 IPv6
|
|
17
|
+
return host.substring(0, idx)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
7
21
|
/**
|
|
8
22
|
* {
|
|
9
23
|
* host : {}
|
|
@@ -406,22 +420,7 @@ class Proxy {
|
|
|
406
420
|
|
|
407
421
|
return async (c, next) => {
|
|
408
422
|
|
|
409
|
-
let host = c.host
|
|
410
|
-
|
|
411
|
-
let hind = c.host.length - 1
|
|
412
|
-
|
|
413
|
-
if (hind > 4) {
|
|
414
|
-
let eind = hind - 5
|
|
415
|
-
|
|
416
|
-
while (hind >= eind) {
|
|
417
|
-
if (c.host[hind] === ':') {
|
|
418
|
-
host = c.host.substring(0, hind)
|
|
419
|
-
break
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
hind--
|
|
423
|
-
}
|
|
424
|
-
}
|
|
423
|
+
let host = extractHostname(c.host)
|
|
425
424
|
|
|
426
425
|
if (self.hostProxy[host]===undefined || self.hostProxy[host][c.routepath]===undefined) {
|
|
427
426
|
if (self.full) {
|
|
@@ -433,7 +432,7 @@ class Proxy {
|
|
|
433
432
|
let pr = self.getBackend(c, host)
|
|
434
433
|
|
|
435
434
|
if (pr === null) {
|
|
436
|
-
for (let i = 0; i <
|
|
435
|
+
for (let i = 0; i < 50; i++) {
|
|
437
436
|
await new Promise((rv, rj) => {setTimeout(rv, 10)})
|
|
438
437
|
pr = self.getBackend(c, host)
|
|
439
438
|
if (pr) break
|