topbit 3.2.1 → 3.2.3

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 CHANGED
@@ -1933,16 +1933,6 @@ app.run(1234)
1933
1933
 
1934
1934
  ### 7. SNI (HTTPS 多域名支持)
1935
1935
 
1936
- **描述**:用于在同一 IP 地址和端口上支持多个 HTTPS 域名证书的中间件。
1937
-
1938
- 感谢提供源代码。根据代码逻辑,`SNI` 扩展通过 `init` 方法将 `SNICallback` 注入到应用的 `config.server` 配置中,从而利用 Node.js 原生 TLS 的能力实现多域名证书支持。
1939
-
1940
- 以下是补全后的 **SNI** 文档部分:
1941
-
1942
- ---
1943
-
1944
- ### 7. SNI (HTTPS 多域名支持)
1945
-
1946
1936
  **描述**:用于在同一 IP 地址和端口上支持多个 HTTPS 域名证书的中间件。它利用 TLS 协议的 Server Name Indication 特性,根据客户端请求的域名动态加载对应的 SSL 证书。
1947
1937
 
1948
1938
  **注意**:初始化时会同步读取证书文件,请确保路径正确。如果某个域名的证书读取失败,会在控制台输出错误信息,但不会阻塞其他域名的加载。
package/demo/http2.js CHANGED
@@ -19,7 +19,15 @@ let app = new Topbit({
19
19
  loadInfoFile: '--mem',
20
20
  cert: './cert/localhost-cert.pem',
21
21
  key: './cert/localhost-privkey.pem',
22
- http2: true
22
+ http2: true,
23
+ server: {
24
+ peerMaxConcurrentStreams: 200,
25
+ settings: {
26
+ maxConcurrentStreams: 201,
27
+ maxHeaderListSize: 16384,
28
+ maxHeaderSize: 16384
29
+ }
30
+ }
23
31
  })
24
32
 
25
33
  if (app.isWorker) {
@@ -42,8 +50,32 @@ if (app.isWorker) {
42
50
  })
43
51
  }
44
52
 
53
+ app.on('connection', sock => {
54
+ console.log(sock)
55
+ })
45
56
 
46
57
  app.sched('none')
47
58
  .autoWorker(3)
48
59
  .printServInfo()
49
60
  .daemon(1234, 2)
61
+
62
+ let settings = {
63
+ maxConcurrentStreams: 200,
64
+ maxHeaderListSize: 16384
65
+ }
66
+
67
+ app.on('session', sess => {
68
+ console.log(sess.localSettings, sess.remoteSettings)
69
+
70
+ sess.on('localSettings', s => {
71
+ console.log('local', s)
72
+ })
73
+
74
+ sess.on('remoteSettings', s => {
75
+ console.log('remote', s)
76
+ })
77
+ /* sess.settings(settings, (err, setting, dura) => {
78
+ console.log(setting, dura)
79
+ }) */
80
+
81
+ })
@@ -42,4 +42,4 @@ if (port_ind > 0 && port_ind < process.argv.length - 1) {
42
42
  port = 2022
43
43
  }
44
44
 
45
- app.run(port)
45
+ app.printServInfo().run(port)
@@ -14,7 +14,7 @@ let app = new Topbit({
14
14
  if (app.isWorker) {
15
15
  let h2proxy = new Http2Proxy({
16
16
  config: {
17
- 'x.com': [
17
+ 'v.com': [
18
18
  {
19
19
  url: 'http://localhost:3001',
20
20
  weight: 10,
@@ -45,4 +45,4 @@ if (app.isWorker) {
45
45
  h2proxy.init(app)
46
46
  }
47
47
 
48
- app.daemon(1234, 2)
48
+ app.printServInfo().daemon(1234, 2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "topbit",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
4
4
  "description": "A Server-side web framework support http/1.1 and http/2",
5
5
  "main": "src/topbit.js",
6
6
  "directories": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  *
5
- * 基于IP的限制,titbit已经能够处理。
5
+ * 基于IP的限制,topbit已经能够处理。
6
6
  *
7
7
  * 此限流针对的是因为http/2协议设计上特点导致单个连接请求密集。
8
8
  *
@@ -29,7 +29,7 @@ let error_503_text = `<!DOCTYPE html><html>
29
29
  <p>此服务暂时不可用。</p>
30
30
  </div>
31
31
  </body>
32
- </html>`
32
+ </html>`
33
33
 
34
34
  function fmtpath(path) {
35
35
  path = path.trim()
@@ -83,6 +83,8 @@ let Http2Proxy = function (options = {}) {
83
83
 
84
84
  this.maxBody = 50000000
85
85
 
86
+ this.realIPHeader = 'x-real-ip'
87
+
86
88
  //是否启用全代理模式。
87
89
  this.full = false
88
90
 
@@ -103,8 +105,18 @@ let Http2Proxy = function (options = {}) {
103
105
  family: 4
104
106
  }
105
107
 
108
+ this.balancer = (options.balancer
109
+ && options.balancer.select
110
+ && typeof options.balancer.select === 'function')
111
+ ? options.balancer
112
+ : null
113
+
106
114
  for (let k in options) {
107
115
  switch (k) {
116
+ case 'realIPHeader':
117
+ this.realIPHeader = options[k]
118
+ break
119
+
108
120
  case 'config':
109
121
  this.config = options[k]
110
122
  break
@@ -323,9 +335,12 @@ Http2Proxy.prototype.checkAlive = function (pr) {
323
335
 
324
336
  Http2Proxy.prototype.getBackend = function (c, host) {
325
337
  let prlist = this.hostProxy[host][c.routepath]
326
-
327
338
  let pxybalance = this.proxyBalance[host][c.routepath]
328
339
 
340
+ if (this.balancer) {
341
+ return this.balancer.select(c, prlist, pxybalance)
342
+ }
343
+
329
344
  let pr
330
345
 
331
346
  if (prlist.length === 1) {
@@ -399,7 +414,7 @@ Http2Proxy.prototype.mid = function () {
399
414
 
400
415
  if (!self.hostProxy[host] || !self.hostProxy[host][c.routepath]) {
401
416
  if (self.full) {
402
- return c.status(502).to(error_502_text)
417
+ return c.status(502).to(error_502_text)
403
418
  }
404
419
 
405
420
  return await next(c)
@@ -408,10 +423,10 @@ Http2Proxy.prototype.mid = function () {
408
423
  let pr = self.getBackend(c, host)
409
424
  if (!pr) return c.status(503).to(error_503_text)
410
425
 
411
- if (self.addIP && c.headers['x-real-ip']) {
412
- c.headers['x-real-ip'] += `,${c.ip}`
426
+ if (self.addIP && c.headers[self.realIPHeader]) {
427
+ c.headers[self.realIPHeader] += `,${c.ip}`
413
428
  } else {
414
- c.headers['x-real-ip'] = c.ip
429
+ c.headers[self.realIPHeader] = c.ip
415
430
  }
416
431
 
417
432
  let hii = pr.h2Pool
@@ -40,6 +40,8 @@ class Proxy {
40
40
 
41
41
  this.methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'TRACE']
42
42
 
43
+ this.realIPHeader = 'x-real-ip'
44
+
43
45
  this.hostProxy = {}
44
46
 
45
47
  this.proxyBalance = {}
@@ -104,8 +106,18 @@ class Proxy {
104
106
  options = {}
105
107
  }
106
108
 
109
+ this.balancer = (options.balancer
110
+ && options.balancer.select
111
+ && typeof options.balancer.select === 'function')
112
+ ? options.balancer
113
+ : null
114
+
107
115
  for (let k in options) {
108
116
  switch (k) {
117
+ case 'realIPHeader':
118
+ this.realIPHeader = options[k]
119
+ break
120
+
109
121
  case 'host':
110
122
  case 'config':
111
123
  this.config = options[k]
@@ -375,6 +387,10 @@ class Proxy {
375
387
  getBackend(c, host) {
376
388
  let prlist = this.hostProxy[host][c.routepath]
377
389
  let pb = this.proxyBalance[host][c.routepath]
390
+ if (this.balancer) {
391
+ return this.balancer.select(c, prlist, pxybalance)
392
+ }
393
+
378
394
  let pr
379
395
 
380
396
  if (prlist.length === 1) {
@@ -448,10 +464,10 @@ class Proxy {
448
464
  urlobj.headers = c.headers
449
465
  urlobj.method = c.method
450
466
 
451
- if (self.addIP && urlobj.headers['x-real-ip']) {
452
- urlobj.headers['x-real-ip'] += `,${c.ip}`
467
+ if (self.addIP && urlobj.headers[self.realIPHeader]) {
468
+ urlobj.headers[self.realIPHeader] += `,${c.ip}`
453
469
  } else {
454
- urlobj.headers['x-real-ip'] = c.ip
470
+ urlobj.headers[self.realIPHeader] = c.ip
455
471
  }
456
472
 
457
473
  let hci = urlobj.protocol == 'https:' ? https : http
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+
5
+ /**
6
+ * 确定性负载均衡器
7
+ * 支持基于用户 ID 的哈希分发
8
+ */
9
+ class ConsistentBalancer {
10
+ constructor(options = {}) {
11
+ // 定义如何从请求上下文 c 中提取唯一标识
12
+ // 默认尝试提取 header 中的 user-id,或者使用 IP
13
+ this.identityFn = options.identityFn || ((c) => {
14
+ return c.user ? c.user.id : c.ip
15
+ })
16
+
17
+ this.hashAlgorithm = options.hashAlgorithm || 'sha256'
18
+ }
19
+
20
+ /**
21
+ * 负载均衡选择算法
22
+ * @param {Object} c 请求上下文
23
+ * @param {Array} prlist 备选后端列表
24
+ * @param {Object} pxybalance 状态表
25
+ */
26
+ select(c, prlist, pxybalance) {
27
+ if (!prlist || prlist.length === 0) return null
28
+
29
+ if (prlist.length === 1) return prlist[0]
30
+
31
+ // 1. 提取标识符
32
+ const identity = this.identityFn(c)
33
+
34
+ if (!identity) {
35
+ // 如果没有标识符,退回到随机/轮询(或者直接用原逻辑)
36
+ return this.fallback(prlist, pxybalance)
37
+ }
38
+
39
+ // 2. 过滤健康的后端 (基于你原有的 checkAlive 逻辑)
40
+ // 注意:pr.h2Pool.ok() 是判断连接池是否正常的关键
41
+ const aliveBackends = prlist.filter(pr => pr.h2Pool && pr.h2Pool.ok())
42
+
43
+ const targets = aliveBackends.length > 0 ? aliveBackends : prlist
44
+
45
+ // 3. 确定性哈希计算 (Rendezvous Hashing)
46
+ let maxWeight = -1
47
+ let selected = targets[0]
48
+
49
+ for (let pr of targets) {
50
+ // 计算 Hash(identity + server_url)
51
+ let hash = crypto.createHash(this.hashAlgorithm)
52
+ .update(identity + pr.url)
53
+ .digest()
54
+ .readUInt32BE(0)
55
+
56
+ // 结合权重计算分值 (HRW Hashing 变体)
57
+ // 使用公式:Score = Hash * (Weight^(1/n)) 或者简单乘法
58
+ let score = hash * (pr.weight || 1)
59
+
60
+ if (score > maxWeight) {
61
+ maxWeight = score
62
+ selected = pr
63
+ }
64
+ }
65
+
66
+ return selected
67
+ }
68
+
69
+ // 兜底逻辑
70
+ fallback(prlist, pxybalance) {
71
+ if (pxybalance.stepIndex >= prlist.length) pxybalance.stepIndex = 0
72
+ return prlist[pxybalance.stepIndex++]
73
+ }
74
+ }
75
+
76
+ module.exports = ConsistentBalancer
package/src/topbit.js CHANGED
@@ -35,6 +35,7 @@ const TopbitExtends = require('./_loadExtends.js')
35
35
  const npargv = require('./lib/npargv.js')
36
36
  const zipdata = require('./lib/zipdata.js')
37
37
  const ErrorLog = require('./lib/errorlog.js')
38
+ const Balancer = require('./lib/balancer.js')
38
39
 
39
40
  let __instance__ = 0;
40
41
 
@@ -780,14 +781,20 @@ class Topbit {
780
781
  if (typeof callback === 'function') {
781
782
  this.httpServ.requestError = callback;
782
783
  }
783
- return;
784
+ return this;
784
785
  }
785
-
786
+
787
+ if (this.server && this.server.on) {
788
+ this.server.on(evt, callback);
789
+ return this;
790
+ }
791
+
786
792
  if (!this.eventTable[evt]) {
787
793
  this.eventTable[evt] = [ callback ];
788
794
  } else {
789
795
  this.eventTable[evt].push(callback);
790
796
  }
797
+ return this;
791
798
  }
792
799
 
793
800
  /**
@@ -1329,5 +1336,6 @@ Topbit.npargv = npargv;
1329
1336
  Topbit.zipdata = zipdata;
1330
1337
  Topbit.ErrorLog = ErrorLog;
1331
1338
  Topbit.extensions = TopbitExtends;
1339
+ Topbit.ProxyBalancer = Balancer;
1332
1340
 
1333
1341
  module.exports = Topbit;