topbit 3.1.0 → 3.1.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/README.cn.md CHANGED
@@ -1921,7 +1921,7 @@ let hxy = new Http2Proxy({
1921
1921
  weight: 10,
1922
1922
  path : '/',
1923
1923
  reconnDelay: 200, // 重连延迟
1924
- max: 2,
1924
+ maxConnect: 2,
1925
1925
  headers: {
1926
1926
  'x-test-key': `${Date.now()}`
1927
1927
  },
package/README.md CHANGED
@@ -1882,7 +1882,7 @@ let hxy = new Http2Proxy({
1882
1882
  weight: 10,
1883
1883
  path : '/',
1884
1884
  reconnDelay: 200, // Reconnection delay
1885
- max: 2,
1885
+ maxConnect: 2,
1886
1886
  headers: {
1887
1887
  'x-test-key': `${Date.now()}`
1888
1888
  },
package/demo/group-api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const titbit = require('../lib/titbit.js')
3
+ const titbit = require('../src/topbit.js')
4
4
 
5
5
  const app = new titbit({
6
6
  debug: true,
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const Titbit = require('../lib/titbit.js')
3
+ const Titbit = require('../src/topbit.js')
4
4
 
5
5
  let app = new Titbit({
6
6
  debug: true,
package/demo/monitor.js CHANGED
@@ -12,11 +12,14 @@ let {args} = npargv({
12
12
  '--loadstdio': {
13
13
  name: 'loadstdio',
14
14
  default: false
15
+ },
16
+
17
+ '--http2': {
18
+ name: 'http2',
19
+ default: false
15
20
  }
16
21
  })
17
22
 
18
- console.log(args)
19
-
20
23
  const app = new Topbit({
21
24
  debug : true,
22
25
  allow : new Set(['127.0.0.1']),
@@ -24,7 +27,7 @@ const app = new Topbit({
24
27
  unitTime: 10,
25
28
  useLimit: true,
26
29
  maxConn: 2000,
27
- //http2: true,
30
+ http2: args.http2,
28
31
  loadMonitor: true,
29
32
  loadInfoType : args.loadtype,
30
33
  globalLog : false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "topbit",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "A Server-side web framework support http/1.1 and http/2",
5
5
  "main": "src/topbit.js",
6
6
  "directories": {
@@ -6,360 +6,194 @@ const crypto = require('node:crypto')
6
6
  class Http2Pool {
7
7
 
8
8
  constructor(options = {}) {
9
- // 存储session的Map
10
9
  if (!options || typeof options !== 'object') options = {}
11
- if (!options.connectOptions) options.connectOptions = {}
12
-
13
- this.pool = new Map()
14
10
 
15
- this.innerConnectDelay = 0
16
- this.failedCount = 0
17
- this.reconnecting = false
18
- // 配置项
19
- this.maxStreamId = !isNaN(options.maxStreamId) && options.maxStreamId > 1
20
- ? options.maxStreamId
21
- : 90000
22
-
23
- this.timeout = options.timeout || 30000
24
- this.connectTimeout = options.connectTimeout || 15000
25
-
26
- this.max = (options.max && !isNaN(options.max) && options.max > 0) ? options.max : 50
27
- this.poolMax = Math.floor(this.max * 1.5 + 0.5)
28
- this.maxConnect = (options.maxConnect && !isNaN(options.maxConnect) && options.maxConnect > 0)
29
- ? options.maxConnect
30
- : this.poolMax + 500
31
-
32
- this.url = options.url || ''
33
- this.debug = options.debug || false
34
- // 连接选项
11
+ // 配置初始化
12
+ this.maxConnect = options.maxConnect || 100 // 最大物理连接数
13
+ this.maxAliveStreams = options.maxAliveStreams || 100 // 单连接最大并发流
14
+ this.url = options.url
15
+ this.debug = !!options.debug
16
+ this.reconnDelay = options.reconnDelay || 500
17
+
18
+ // 核心数据结构
19
+ this.sessions = [] // 使用数组代替Map,利用索引做 Round-Robin
20
+ this.cursor = 0 // 轮询指针
21
+
35
22
  this.connectOptions = {
36
23
  rejectUnauthorized: false,
37
- requestCert: false,
38
- peerMaxConcurrentStreams: 100,
39
- timeout: this.timeout,
24
+ timeout: options.timeout || 30000,
40
25
  ...options.connectOptions
41
26
  }
27
+
28
+ this.waitQueue = [] // 等待可用连接的队列
29
+ }
42
30
 
43
- this.reconnDelay = 100
44
- if (options.reconnDelay !== undefined && !isNaN(options.reconnDelay)) {
45
- this.reconnDelay = options.reconnDelay
46
- }
47
-
48
- this.parent = null
49
-
50
- if (options.parent && typeof options.parent === 'object') {
51
- this.parent = options.parent
31
+ /**
32
+ * 初始化连接池 (预热)
33
+ */
34
+ createPool(initialSize = 5) {
35
+ for (let i = 0; i < initialSize; i++) {
36
+ this._createConnection()
52
37
  }
53
-
54
- this.maxAliveStreams = options.maxAliveStreams || 100
55
-
56
- this.quiet = false
57
- if (options.quiet)
58
- this.quiet = !!options.quiet
59
38
  }
60
39
 
61
40
  /**
62
- * 创建新的session连接
41
+ * 内部建立连接
63
42
  */
64
- async connect() {
65
- if (this.pool.size > this.maxConnect) {
66
- return {
67
- deny: true,
68
- error: `超出最大连接限制:${this.maxConnect}`
69
- }
70
- }
43
+ _createConnection() {
44
+ if (this.sessions.length >= this.maxConnect) return null
71
45
 
72
46
  const session = http2.connect(this.url, this.connectOptions)
73
-
74
- // 生成唯一session id
75
- const sessionId = crypto.randomBytes(16).toString('hex')
76
47
 
77
- // 初始化session相关计数器和状态
78
- const sessionState = {
79
- using: false,
80
- id: sessionId,
81
- session,
82
- streamCount: 0,
83
- url: this.url,
48
+ const wrapper = {
49
+ id: crypto.randomBytes(8).toString('hex'),
50
+ session: session,
84
51
  connected: false,
85
- error: null,
86
- aliveStreams: 0
52
+ aliveStreams: 0, // 当前并发数
53
+ weight: 1 // 预留权重字段
87
54
  }
88
55
 
89
- // 处理session事件
90
- this._handleSessionEvents(sessionState)
91
-
92
- // 等待连接建立
93
- try {
94
- let timeout_timer = null
95
- let resolved = false
96
- let rejected = false
97
-
98
- await new Promise((resolve, reject) => {
99
- session.once('connect', () => {
100
- if (timeout_timer) {
101
- clearTimeout(timeout_timer)
102
- timeout_timer = null
103
- }
104
-
105
- if (this.failedCount > 0) {
106
- this.failedCount--
107
- }
108
-
109
- this.innerConnectDelay = 0
110
-
111
- sessionState.connected = true
112
- this.parent && !this.parent.alive && (this.parent.alive = true)
113
-
114
- resolve()
115
- })
116
-
117
- session.once('error', err => {
118
- if (timeout_timer) {
119
- clearTimeout(timeout_timer)
120
- timeout_timer = null
121
- }
122
-
123
- if (this.pool.size < 1) {
124
- this.parent && (this.parent.alive = false)
125
- }
126
-
127
- this.failedCount++
128
-
129
- if (this.failedCount < 10) {
130
- this.innerConnectDelay = this.failedCount
131
- } if (this.failedCount < 60000) {
132
- this.innerConnectDelay = Math.floor(this.failedCount / 10)
133
- } else { this.innerConnectDelay = 6000 }
134
-
135
- !rejected && (rejected = true) && reject(err)
136
- })
137
-
138
- session.once('goaway', err => {
139
- if (timeout_timer) {
140
- clearTimeout(timeout_timer)
141
- timeout_timer = null
142
- }
143
-
144
- !rejected && (rejected = true) && reject(err||new Error('goaway'))
145
- })
146
-
147
- session.once('frameError', err => {
148
- if (timeout_timer) {
149
- clearTimeout(timeout_timer)
150
- timeout_timer = null
151
- }
152
- !rejected && (rejected = true) && reject(err)
153
- })
154
-
155
- if (!timeout_timer) {
156
- timeout_timer = setTimeout(() => {
157
- timeout_timer = null
158
- !session.destroyed && session.destroy()
159
- !rejected && (rejected = true) && reject(new Error('connect timeout'))
160
- }, this.connectTimeout)
161
- }
162
- })
163
- } catch (err) {
164
- sessionState.error = err
165
- sessionState.session = null
166
- this.debug && console.error(err)
167
- } finally {
168
- this.reconnecting = false
169
- }
170
-
171
- if (this.pool.size < this.poolMax && sessionState.connected) {
172
- this.pool.set(sessionId, sessionState)
173
- }
174
-
175
- return sessionState
176
- }
177
-
178
- createPool(max=0) {
179
- if (max <= 0) max = this.max
180
-
181
- for (let i = 0; i < max; i++) {
182
- this.connect()
183
- }
184
- }
185
-
186
- delayConnect() {
187
- if (this.reconnecting) return false
188
-
189
- let delay_time = this.reconnDelay + this.innerConnectDelay
190
-
191
- if (delay_time > 0) {
192
- if (!this.delayTimer) {
193
- this.reconnecting = true
194
- this.delayTimer = setTimeout(() => {
195
- this.delayTimer = null
196
- this.connect()
197
- }, delay_time)
198
- }
199
- } else {
200
- this.reconnecting = true
201
- this.connect()
202
- }
203
- }
204
-
205
- /**
206
- * 处理session的各种事件
207
- */
208
- _handleSessionEvents(sessionState) {
209
- const { session, id } = sessionState
210
-
211
- session.on('close', () => {
212
- // session关闭时从pool中移除
213
- this.pool.delete(id)
214
-
215
- if (this.pool.size < 1) {
216
- this.delayConnect()
56
+ session.once('connect', () => {
57
+ wrapper.connected = true
58
+ // 触发队列中的等待者
59
+ while (this.waitQueue.length > 0 && wrapper.aliveStreams < this.maxAliveStreams) {
60
+ const resolve = this.waitQueue.shift()
61
+ resolve(wrapper)
217
62
  }
218
63
  })
219
64
 
220
- session.on('error', err => {
221
- this.debug && console.error(err)
222
- !session.destroyed && session.destroy()
223
- this.pool.delete(id)
224
- })
65
+ // 错误处理与清理
66
+ const cleanup = () => {
67
+ if (wrapper._destroyed) return
68
+ wrapper._destroyed = true
69
+
70
+ // 从池中移除
71
+ const idx = this.sessions.indexOf(wrapper)
72
+ if (idx !== -1) {
73
+ this.sessions.splice(idx, 1)
74
+ // 修正指针,防止跳过
75
+ if (this.cursor >= idx && this.cursor > 0) this.cursor--
76
+ }
225
77
 
226
- session.on('frameError', err => {
227
- !session.destroyed && session.destroy()
228
- this.pool.delete(id)
229
- })
78
+ if (!session.destroyed) session.destroy()
79
+
80
+ // 自动补充连接 (维持最小连接数,可选)
81
+ if (this.sessions.length < 2) {
82
+ // 防止雪崩,延迟重连
83
+ setTimeout(() => this._createConnection(), this.reconnDelay)
84
+ }
85
+ }
230
86
 
231
- session.on('goaway', err => {
232
- this.debug && err && console.error('..........goaway........', err)
233
- !session.destroyed && session.close()
234
- this.pool.delete(id)
87
+ session.on('close', cleanup)
88
+ session.on('error', (err) => {
89
+ if(this.debug) console.error(`[H2Pool] Session Error ${this.url}:`, err.message)
90
+ cleanup()
235
91
  })
236
-
237
- session.setTimeout(this.timeout, () => {
238
- this.debug && console.error('session.....time.....out......')
239
- if (!session.destroyed) {
240
- session.destroy()
241
- }
242
-
243
- this.pool.delete(id)
92
+ session.on('goaway', cleanup)
93
+
94
+ // 超时保活策略:只有完全空闲才销毁,否则发送 ping
95
+ session.setTimeout(this.connectOptions.timeout, () => {
96
+ if (wrapper.aliveStreams > 0) {
97
+ // 还有流量,不销毁,尝试 ping 保持活跃
98
+ session.ping && session.ping(() => {})
99
+ } else {
100
+ session.close() // 优雅关闭
101
+ }
244
102
  })
245
- }
246
103
 
247
- isSessionHealthy(session) {
248
- return session
249
- && !session.destroyed
250
- && !session.closed
251
- && session.socket
252
- && !session.socket.destroyed
104
+ this.sessions.push(wrapper)
105
+ return wrapper
253
106
  }
254
107
 
255
108
  /**
256
- * 获取可用的session,如果没有则创建新的
109
+ * 获取最佳可用 Session (Round-Robin)
257
110
  */
258
111
  async getSession() {
259
- if (this.pool.size > 0) {
260
- let items = this.pool.entries()
261
- for (const [id, state] of items) {
262
- if (state.connected
263
- && state.streamCount < this.maxStreamId
264
- && this.isSessionHealthy(state.session))
265
- {
266
- if (state.aliveStreams < this.maxAliveStreams) {
267
- return state
268
- }
269
- } else {
270
- state.connected = false
271
- if (!state.session.destroyed) {
272
- state.session.close()
112
+ let tried = 0
113
+ const len = this.sessions.length
273
114
 
274
- if (state.aliveStreams < 1) {
275
- state.session.destroy()
276
- }
277
- /* else {
278
- let sess = state.session
279
- setTimeout(() => {
280
- !sess.destroyed && sess.destroy()
281
- sess = null
282
- }, this.timeout + 5000)
283
- } */
284
- }
115
+ // 1. 尝试轮询获取可用连接
116
+ while (tried < len) {
117
+ this.cursor = (this.cursor + 1) % len
118
+ const wrapper = this.sessions[this.cursor]
285
119
 
286
- this.pool.delete(state.id)
287
- }
120
+ if (wrapper && wrapper.connected && !wrapper.session.destroyed && wrapper.aliveStreams < this.maxAliveStreams) {
121
+ return wrapper
122
+ }
123
+ tried++
124
+ }
125
+
126
+ // 2. 如果没有可用连接,且未达上限,创建新连接
127
+ if (this.sessions.length < this.maxConnect) {
128
+ const newWrapper = this._createConnection()
129
+ if (newWrapper) {
130
+ // 等待连接建立 (设置一个短超时)
131
+ return new Promise((resolve, reject) => {
132
+ const timer = setTimeout(() => {
133
+ reject(new Error('Connection timeout'))
134
+ }, 5000)
135
+
136
+ const onConnect = () => {
137
+ clearTimeout(timer)
138
+ newWrapper.session.removeListener('connect', onConnect)
139
+ resolve(newWrapper)
140
+ }
141
+ newWrapper.session.once('connect', onConnect)
142
+ newWrapper.session.once('error', (err) => {
143
+ clearTimeout(timer)
144
+ reject(err)
145
+ })
146
+ })
288
147
  }
289
148
  }
290
149
 
291
- return this.connect()
292
- }
293
-
294
- /**
295
- * 创建新的请求stream
296
- */
297
- async request(headers, sessionState=null) {
298
- !sessionState && (sessionState = await this.getSession())
299
-
300
- sessionState.streamCount++
301
-
302
- if (!sessionState.connected) {
303
- if (this.quiet) return null
304
- throw new Error('There is no connected')
305
- }
306
-
307
- //创建请求stream
308
- return sessionState.session.request(headers)
150
+ // 3. 还是没有,进入队列等待 (削峰填谷)
151
+ return new Promise((resolve, reject) => {
152
+ // 3秒后还没拿到连接就报错
153
+ const timer = setTimeout(() => {
154
+ const idx = this.waitQueue.indexOf(resolve)
155
+ if (idx !== -1) this.waitQueue.splice(idx, 1)
156
+ reject(new Error('No available h2 session (Queued timeout)'))
157
+ }, 3000)
158
+
159
+ // 包装 resolve 以清理 timer
160
+ this.waitQueue.push((wrapper) => {
161
+ clearTimeout(timer)
162
+ resolve(wrapper)
163
+ })
164
+ })
309
165
  }
310
166
 
311
167
  /**
312
- * 关闭所有session
168
+ * 发起请求 (核心入口)
313
169
  */
314
- closeAll() {
315
- for (const [id, state] of this.pool.entries()) {
316
- if (!state.session.destroyed) {
317
- state.session.close()
318
- }
319
- }
320
-
321
- this.pool.clear()
322
- }
170
+ async request(headers) {
171
+ const wrapper = await this.getSession()
172
+ wrapper.aliveStreams++
323
173
 
324
- async aok() {
325
- if (this.pool.size <= 0) {
326
- await this.connect()
174
+ try {
175
+ const stream = wrapper.session.request(headers)
176
+
177
+ // 监听流关闭,减少计数
178
+ stream.once('close', () => {
179
+ wrapper.aliveStreams--
180
+ // 如果有等待队列,唤醒一个
181
+ if (this.waitQueue.length > 0) {
182
+ const resolve = this.waitQueue.shift()
183
+ resolve(wrapper)
184
+ }
185
+ })
186
+
187
+ return stream
188
+ } catch (e) {
189
+ wrapper.aliveStreams--
190
+ throw e
327
191
  }
328
-
329
- return this.ok()
330
192
  }
331
193
 
332
194
  ok() {
333
- let items = this.pool.entries()
334
-
335
- for (const [id, state] of items) {
336
- if (state.connected) return true
337
- }
338
-
339
- return false
340
- }
341
-
342
- /**
343
- * 获取当前pool状态
344
- */
345
- status() {
346
- const status = {
347
- total: this.pool.size,
348
- sessions: []
349
- }
350
-
351
- let items = this.pool.entries()
352
-
353
- for (const [id, state] of items) {
354
- status.sessions.push({
355
- id: state.id,
356
- streamCount: state.streamCount,
357
- connected: state.connected
358
- })
359
- }
360
-
361
- return status
195
+ return this.sessions.some(s => s.connected && !s.session.destroyed)
362
196
  }
363
197
  }
364
198
 
365
- module.exports = Http2Pool
199
+ module.exports = Http2Pool
@@ -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.max && typeof tmp.max === 'number' && tmp.max > 1)
192
- backend_obj.max = tmp.max
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
- max: 50,
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 >= 2 ? c.headers[':path'] : c.request.url)
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.headers[':path'] = rpath
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
- c.major === 2 ? c.headers : this.fmtHeaders(c.headers, c),
498
- session_client
499
- ).catch(err => {
500
- rejected = true
501
- rj(err)
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
- let data_count = 0
640
- stm.on('data', chunk => {
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
- if (data_count >= 111) {
645
- data_count = 0
646
- timeout_timer && clearTimeout(timeout_timer)
647
- timeout_timer = setTimeout(timeout_handler, pr.timeout + 5000)
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 (timeout_timer) {
653
- clearTimeout(timeout_timer)
654
- timeout_timer = null
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
  }
@@ -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 < 200; 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
@@ -1,4 +1,4 @@
1
- const titbit = require('../lib/titbit');
1
+ const titbit = require('../src/topbit');
2
2
 
3
3
  let app = new titbit();
4
4
 
@@ -1,4 +1,4 @@
1
- const titbit = require('../lib/titbit.js');
1
+ const titbit = require('../src/topbit.js');
2
2
 
3
3
  var app = new titbit();
4
4