bdy 1.22.33-dev → 1.22.34-stage

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.22.33-dev",
4
+ "version": "1.22.34-stage",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "scripts": {
@@ -9,7 +9,9 @@ const http2_1 = __importDefault(require("http2"));
9
9
  const uuid_1 = require("uuid");
10
10
  const texts_1 = require("../texts");
11
11
  const CONNECTION_TIMEOUT = 5000;
12
- const STREAM_LIMIT = 10;
12
+ const HTTP2_MAX_POOL_SIZE = 4;
13
+ const HTTP2_MAX_CONCURRENT_STREAMS = 100;
14
+ const HTTP2_MAX_LIFETIME_REQUESTS = 1000;
13
15
  class TunnelAgent {
14
16
  static httpAgent1;
15
17
  static httpsAgent1;
@@ -40,34 +42,68 @@ class TunnelAgent {
40
42
  TunnelAgent.deleteClient(authority, client, id);
41
43
  reject(new Error(texts_1.ERR_CONNECTION_TIMEOUT));
42
44
  }, CONNECTION_TIMEOUT);
45
+ let pingTs;
43
46
  const client = http2_1.default.connect(authority, {
44
47
  rejectUnauthorized: verify,
45
48
  maxSessionMemory: 100,
46
49
  servername,
47
50
  });
51
+ client.setTimeout(60000);
48
52
  client.once('close', () => {
53
+ clearInterval(pingTs);
54
+ TunnelAgent.deleteClient(authority, client, id);
55
+ });
56
+ client.once('goaway', () => {
57
+ clearInterval(pingTs);
49
58
  TunnelAgent.deleteClient(authority, client, id);
50
59
  });
51
60
  client.once('timeout', () => {
61
+ clearInterval(pingTs);
52
62
  TunnelAgent.deleteClient(authority, client, id);
53
63
  });
54
64
  client.once('error', () => {
55
65
  clearTimeout(ts);
66
+ clearInterval(pingTs);
56
67
  TunnelAgent.deleteClient(authority, client, id);
57
68
  reject(new Error(texts_1.ERR_CONNECTION_ERROR));
58
69
  });
59
70
  client.once('connect', () => {
60
71
  clearTimeout(ts);
72
+ pingTs = setInterval(() => {
73
+ if (client.closed || client.destroyed)
74
+ return;
75
+ let acked = false;
76
+ const ackTs = setTimeout(() => {
77
+ if (!acked) {
78
+ clearInterval(pingTs);
79
+ TunnelAgent.deleteClient(authority, client, id);
80
+ }
81
+ }, 5000);
82
+ try {
83
+ client.ping(() => {
84
+ acked = true;
85
+ clearTimeout(ackTs);
86
+ });
87
+ }
88
+ catch {
89
+ clearTimeout(ackTs);
90
+ clearInterval(pingTs);
91
+ TunnelAgent.deleteClient(authority, client, id);
92
+ }
93
+ }, 10000);
61
94
  resolve(client);
62
95
  });
63
96
  });
64
97
  }
65
98
  static deleteClient(authority, client, id) {
66
99
  if (this.http2Clients && this.http2Clients[authority]) {
67
- if (this.http2Clients[authority][id])
100
+ if (this.http2Clients[authority][id]) {
101
+ this.http2Clients[authority][id].closed = true;
68
102
  delete this.http2Clients[authority][id];
69
- if (!Object.keys(this.http2Clients[authority]).length)
103
+ }
104
+ if (!Object.keys(this.http2Clients[authority]).length) {
70
105
  delete this.http2Clients[authority];
106
+ }
71
107
  }
72
108
  try {
73
109
  client.removeAllListeners();
@@ -82,22 +118,49 @@ class TunnelAgent {
82
118
  this.http2Clients = {};
83
119
  if (!this.http2Clients[authority])
84
120
  this.http2Clients[authority] = {};
85
- const keys = Object.keys(this.http2Clients[authority]);
121
+ const pool = this.http2Clients[authority];
122
+ let best = null;
123
+ let bestLoad = Infinity;
124
+ const keys = Object.keys(pool);
86
125
  for (let i = 0; i < keys.length; i += 1) {
87
126
  const id = keys[i];
88
- const { promise, streamCount } = this.http2Clients[authority][id];
89
- if (streamCount < STREAM_LIMIT) {
90
- this.http2Clients[authority][id].streamCount += 1;
91
- return promise;
127
+ const c = pool[id];
128
+ if (c.hasCapacity() && c.concurrent < bestLoad) {
129
+ best = c;
130
+ bestLoad = c.concurrent;
131
+ }
132
+ }
133
+ if (best)
134
+ return best;
135
+ if (keys.length >= HTTP2_MAX_POOL_SIZE) {
136
+ for (let i = 0; i < keys.length; i += 1) {
137
+ const id = keys[i];
138
+ const c = pool[id];
139
+ if (c.concurrent < bestLoad) {
140
+ best = c;
141
+ bestLoad = c.concurrent;
142
+ }
92
143
  }
93
144
  }
145
+ if (best)
146
+ return best;
94
147
  const id = (0, uuid_1.v4)();
95
148
  const promise = this.createHttp2Client(authority, servername, verify, id);
96
- this.http2Clients[authority][id] = {
149
+ best = {
97
150
  promise,
98
- streamCount: 1,
151
+ closed: false,
152
+ requests: 0,
153
+ concurrent: 0,
154
+ hasCapacity: function () {
155
+ if (this.closed)
156
+ return false;
157
+ if (this.requests >= HTTP2_MAX_LIFETIME_REQUESTS)
158
+ return false;
159
+ return this.concurrent < HTTP2_MAX_CONCURRENT_STREAMS;
160
+ },
99
161
  };
100
- return promise;
162
+ this.http2Clients[authority][id] = best;
163
+ return best;
101
164
  }
102
165
  }
103
166
  exports.default = TunnelAgent;
@@ -243,10 +243,14 @@ class TunnelHttp extends events_1.default {
243
243
  const compressionMethod = compression_1.default.detect(this.compression || false, reqHeaders, resHeaders);
244
244
  this.outputHeaders(statusCode, this.getClearedHeaders(resHeaders, compressionMethod));
245
245
  if (this.logRequest) {
246
- (0, node_stream_1.pipeline)(this.proxyRes, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
246
+ (0, node_stream_1.pipeline)(this.proxyRes, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => {
247
+ this.clear();
248
+ });
247
249
  }
248
250
  else {
249
- (0, node_stream_1.pipeline)(this.proxyRes, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
251
+ (0, node_stream_1.pipeline)(this.proxyRes, compression_1.default.compress(compressionMethod), this.res, () => {
252
+ this.clear();
253
+ });
250
254
  }
251
255
  if (this.httpLog) {
252
256
  this.httpLog.newResponse(statusCode, resHeaders, this.logRequest);
@@ -286,10 +290,14 @@ class TunnelHttp extends events_1.default {
286
290
  const compressionMethod = compression_1.default.detect(this.compression || false, reqHeaders, resHeaders);
287
291
  this.outputHeaders(statusCode, this.getClearedHeaders(resHeaders, compressionMethod));
288
292
  if (this.logRequest) {
289
- (0, node_stream_1.pipeline)(this.proxyRes, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
293
+ (0, node_stream_1.pipeline)(this.proxyRes, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => {
294
+ this.clear();
295
+ });
290
296
  }
291
297
  else {
292
- (0, node_stream_1.pipeline)(this.proxyRes, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
298
+ (0, node_stream_1.pipeline)(this.proxyRes, compression_1.default.compress(compressionMethod), this.res, () => {
299
+ this.clear();
300
+ });
293
301
  }
294
302
  if (this.httpLog) {
295
303
  this.httpLog.newResponse(statusCode, resHeaders, this.logRequest);
@@ -314,10 +322,14 @@ class TunnelHttp extends events_1.default {
314
322
  const compressionMethod = compression_1.default.detect(this.compression || false, reqHeaders, resHeaders);
315
323
  this.outputHeaders(statusCode, this.getClearedHeaders(resHeaders, compressionMethod));
316
324
  if (this.logRequest) {
317
- (0, node_stream_1.pipeline)(stream, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
325
+ (0, node_stream_1.pipeline)(stream, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => {
326
+ this.clear();
327
+ });
318
328
  }
319
329
  else {
320
- (0, node_stream_1.pipeline)(stream, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
330
+ (0, node_stream_1.pipeline)(stream, compression_1.default.compress(compressionMethod), this.res, () => {
331
+ this.clear();
332
+ });
321
333
  }
322
334
  }
323
335
  if (this.httpLog) {
@@ -326,10 +338,32 @@ class TunnelHttp extends events_1.default {
326
338
  });
327
339
  return r;
328
340
  }
341
+ async getHttp2Client(authority) {
342
+ let c = agent_1.default.getHttp2Client(authority, this.host || '', this.verify || false);
343
+ let client;
344
+ try {
345
+ client = await c.promise;
346
+ }
347
+ catch {
348
+ // do nothing
349
+ }
350
+ if (c.closed || !client || client.closed || client.destroyed) {
351
+ // try again
352
+ c = agent_1.default.getHttp2Client(authority, this.host || '', this.verify || false);
353
+ client = await c.promise;
354
+ }
355
+ if (c.closed || !client || client.closed || client.destroyed) {
356
+ throw new Error(texts_1.ERR_CONNECTION_ERROR);
357
+ }
358
+ return {
359
+ client,
360
+ c,
361
+ };
362
+ }
329
363
  async createRequestHttps2() {
330
364
  const reqHeaders = this.req.headers;
331
365
  const authority = `${this.proto}://${(0, utils_1.getRealTargetHost)(this.host)}:${this.port}`;
332
- const client = await agent_1.default.getHttp2Client(authority, this.host || '', this.verify || false);
366
+ const { c, client } = await this.getHttp2Client(authority);
333
367
  const method = this.getMethod();
334
368
  const path = this.getPath();
335
369
  const headers = {
@@ -337,28 +371,53 @@ class TunnelHttp extends events_1.default {
337
371
  ...this.headers,
338
372
  ...this.getRequestHeaders(true),
339
373
  };
340
- const r = client.request(headers);
374
+ c.requests += 1;
375
+ c.concurrent += 1;
376
+ let concurrentDecremented = false;
377
+ const decrementConcurrent = () => {
378
+ if (concurrentDecremented)
379
+ return;
380
+ concurrentDecremented = true;
381
+ if (c.concurrent > 0)
382
+ c.concurrent -= 1;
383
+ };
384
+ let r;
385
+ try {
386
+ r = client.request(headers);
387
+ }
388
+ catch (err) {
389
+ decrementConcurrent();
390
+ throw err;
391
+ }
341
392
  if (this.httpLog) {
342
393
  this.logRequest = this.httpLog.newRequest(method, headers, path, tunnel_1.TUNNEL_HTTP_IDENTIFY.HTTP2, this.req);
343
394
  }
344
395
  r.on('error', () => {
345
396
  try {
397
+ decrementConcurrent();
346
398
  this.res.end();
347
399
  }
348
400
  catch {
349
401
  // do nothing
350
402
  }
351
403
  });
404
+ r.once('close', decrementConcurrent);
352
405
  r.on('response', (resHeaders) => {
353
406
  r.setTimeout(0);
354
407
  const status = resHeaders[':status'] || 200;
355
408
  const compressionMethod = compression_1.default.detect(this.compression || false, reqHeaders, resHeaders);
356
409
  this.outputHeaders(status, this.getClearedHeaders(resHeaders, compressionMethod));
357
410
  if (this.logRequest) {
358
- (0, node_stream_1.pipeline)(r, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
411
+ (0, node_stream_1.pipeline)(r, this.logRequest.responseBody, compression_1.default.compress(compressionMethod), this.res, () => {
412
+ decrementConcurrent();
413
+ this.clear();
414
+ });
359
415
  }
360
416
  else {
361
- (0, node_stream_1.pipeline)(r, compression_1.default.compress(compressionMethod), this.res, () => { this.clear(); });
417
+ (0, node_stream_1.pipeline)(r, compression_1.default.compress(compressionMethod), this.res, () => {
418
+ decrementConcurrent();
419
+ this.clear();
420
+ });
362
421
  }
363
422
  if (this.httpLog) {
364
423
  this.httpLog.newResponse(status, resHeaders, this.logRequest);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.22.33-dev",
4
+ "version": "1.22.34-stage",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "scripts": {