whistle 2.10.9 → 2.10.11

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.
Files changed (47) hide show
  1. package/assets/js/log.js +4 -0
  2. package/bin/api.js +3 -1
  3. package/bin/util.js +1 -1
  4. package/bin/whistle.js +5 -0
  5. package/biz/webui/cgi-bin/get-data.js +0 -1
  6. package/biz/webui/cgi-bin/init.js +0 -1
  7. package/biz/webui/cgi-bin/status.js +2 -1
  8. package/biz/webui/cgi-bin/util.js +1 -1
  9. package/biz/webui/cgi-bin/values/add.js +15 -13
  10. package/biz/webui/htdocs/index.html +1 -1
  11. package/biz/webui/htdocs/js/index.js +50 -51
  12. package/biz/webui/lib/index.js +39 -30
  13. package/index.js +10 -12
  14. package/lib/config.js +25 -79
  15. package/lib/handlers/file-proxy.js +1 -1
  16. package/lib/https/ca.js +18 -17
  17. package/lib/https/h2.js +54 -35
  18. package/lib/https/index.js +124 -107
  19. package/lib/inspectors/data.js +9 -3
  20. package/lib/inspectors/log.js +14 -33
  21. package/lib/inspectors/req.js +3 -6
  22. package/lib/inspectors/res.js +2 -5
  23. package/lib/inspectors/rules.js +11 -6
  24. package/lib/inspectors/weinre.js +1 -1
  25. package/lib/plugins/index.js +45 -54
  26. package/lib/plugins/load-plugin.js +31 -40
  27. package/lib/rules/dns.js +1 -1
  28. package/lib/rules/index.js +3 -1
  29. package/lib/rules/protocols.js +2 -4
  30. package/lib/rules/rules.js +3 -5
  31. package/lib/rules/util.js +2 -7
  32. package/lib/service/compose-data.js +2 -1
  33. package/lib/service/composer.js +12 -10
  34. package/lib/service/service.js +3 -5
  35. package/lib/socket-mgr.js +5 -2
  36. package/lib/tunnel.js +9 -5
  37. package/lib/util/common.js +190 -37
  38. package/lib/util/data-server.js +5 -1
  39. package/lib/util/drain.js +5 -6
  40. package/lib/util/file-mgr.js +1 -1
  41. package/lib/util/http-mgr.js +6 -2
  42. package/lib/util/index.js +48 -69
  43. package/lib/util/speed-transform.js +2 -2
  44. package/lib/util/transproto.js +4 -3
  45. package/lib/util/whistle-transform.js +2 -2
  46. package/lib/util/zlib.js +1 -1
  47. package/package.json +4 -4
@@ -35,6 +35,7 @@ var uploadUrlencodedParser = bodyParser.urlencoded(UPLOAD_PARSE_CONF);
35
35
  var uploadJsonParser = bodyParser.json(UPLOAD_PARSE_CONF);
36
36
  var GET_METHOD_RE = /^get$/i;
37
37
  var WEINRE_RE = /^\/weinre\/.*/;
38
+ var IMPORT_CGI = '/cgi-bin/import-remote';
38
39
  var ALLOW_PLUGIN_PATHS = ['/cgi-bin/rules/list2', '/cgi-bin/values/list2', '/cgi-bin/get-custom-certs-info'];
39
40
  var DONT_CHECK_PATHS = ['/cgi-bin/server-info', '/cgi-bin/plugins/is-enable', '/cgi-bin/plugins/get-plugins',
40
41
  '/preview.html', '/cgi-bin/rootca', '/cgi-bin/check-update', '/cgi-bin/log/set', '/cgi-bin/status'];
@@ -70,11 +71,14 @@ function isTempFile(query) {
70
71
  return common.getTempFile(query.filename);
71
72
  }
72
73
 
74
+ function sendForbidden(res) {
75
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
76
+ res.end('{"ec":0,"value":"","forbidden":true}');
77
+ }
78
+
73
79
  function sendToService(req, res) {
74
80
  if (!hasLogin() && req.path === '/cgi-bin/temp/get' && !isTempFile(req.query)) {
75
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
76
- res.end('{"ec":0,"value":"","forbidden":true}');
77
- return;
81
+ return sendForbidden(res);
78
82
  }
79
83
  loadService(function(err, options) {
80
84
  if (err) {
@@ -135,6 +139,9 @@ function requireLogin(req, res, msg) {
135
139
  }
136
140
  return res.status(404).end();
137
141
  }
142
+ if (req.path === IMPORT_CGI) {
143
+ return sendForbidden(res);
144
+ }
138
145
  res.setHeader('WWW-Authenticate', ' Basic realm=User Login');
139
146
  res.setHeader('Content-Type', 'text/html; charset=utf8');
140
147
  res.status(401).end(msg || 'Access denied, please <a href="javascript:;" onclick="location.reload()">try again</a>');
@@ -410,32 +417,7 @@ function cgiHandler(req, res) {
410
417
  handleResponse();
411
418
  });
412
419
  }
413
- app.get('/cgi-bin/import-remote', function(req, res) {
414
- var url = req.query.url;
415
- if (HTTP_RE.test(url)) {
416
- util.request({
417
- url: url,
418
- maxLength: MAX_LEN
419
- }, function(err, body, r) {
420
- if (err) {
421
- var msg = err.code === 'EEXCEED' ? 'The size of response body exceeds 6MB' : err.message;
422
- return res.json({ec: 2, em: msg});
423
- }
424
- var status = r.statusCode;
425
- if (status !== 200) {
426
- var em = status > 200 && status < 400 ? 'No data' : 'Request failed';
427
- return res.json({ec: 2, em: em + ' (statusCode: ' + status + ')'});
428
- }
429
- return res.json({ec: 0, body: body});
430
- });
431
- } else if (util.isString(url)) {
432
- req.url = '/cgi-bin/temp/get?filename=' + encodeURIComponent(url);
433
- req.query.filename = url;
434
- sendToService(req, res);
435
- } else {
436
- res.json({ec: 400, em: 'Bad url'});
437
- }
438
- });
420
+
439
421
  app.all('/service/*', sendToService);
440
422
  app.all('/cgi-bin/service/*', sendToService);
441
423
  app.all('/cgi-bin/sessions/*', sendToService);
@@ -448,7 +430,7 @@ function readPluginPage(req, res, plugin, html, config) {
448
430
  res.write(config);
449
431
  res.write(html);
450
432
  var index = req.path.indexOf('/', 1);
451
- if (index === -1) {
433
+ if (index === -1 || common.existsUpPath(req.path)) {
452
434
  res.end();
453
435
  } else {
454
436
  var filepath = req.path.substring(index + 1);
@@ -528,6 +510,33 @@ app.use(function(req, res, next) {
528
510
  }
529
511
  });
530
512
 
513
+ app.get(IMPORT_CGI, function(req, res) {
514
+ var url = req.query.url;
515
+ if (HTTP_RE.test(url)) {
516
+ util.request({
517
+ url: url,
518
+ maxLength: MAX_LEN
519
+ }, function(err, body, r) {
520
+ if (err) {
521
+ var msg = err.code === 'EEXCEED' ? 'The size of response body exceeds 6MB' : err.message;
522
+ return res.json({ec: 2, em: msg});
523
+ }
524
+ var status = r.statusCode;
525
+ if (status !== 200) {
526
+ var em = status > 200 && status < 400 ? 'No data' : 'Request failed';
527
+ return res.json({ec: 2, em: em + ' (statusCode: ' + status + ')'});
528
+ }
529
+ return res.json({ec: 0, body: body});
530
+ });
531
+ } else if (util.isString(url)) {
532
+ req.url = '/cgi-bin/temp/get?filename=' + encodeURIComponent(url);
533
+ req.query.filename = url;
534
+ sendToService(req, res);
535
+ } else {
536
+ res.json({ec: 400, em: 'Bad url'});
537
+ }
538
+ });
539
+
531
540
  app.post('/cgi-bin/composer', function(req, res) {
532
541
  req.headers[config.CLIENT_IP_HEADER] = util.getClientIp(req);
533
542
  req.headers[config.CLIENT_PORT_HEADER] = util.getClientPort(req);
package/index.js CHANGED
@@ -8,7 +8,6 @@ var os = require('os');
8
8
  var assert = require('assert');
9
9
  var common = require('./lib/util/common');
10
10
 
11
- var ver = process.version.substring(1).split('.');
12
11
  var PROD_RE = /(^|\|)prod(uction)?($|\|)/;
13
12
  var noop = function () {};
14
13
  var state = {};
@@ -16,21 +15,20 @@ var INTERVAL = 1000;
16
15
  var TIMEOUT = 10000;
17
16
  var MASTER_TIMEOUT = 12000;
18
17
 
19
- if (ver[0] >= 7 && ver[1] >= 7) {
20
- var connect = net.Socket.prototype.connect;
21
- if (typeof connect === 'function') {
22
- //fix: Node v7.7.0+引入的 `"listener" argument must be a function` 问题
23
- net.Socket.prototype.connect = function (options, cb) {
24
- if (options && typeof options === 'object' && typeof cb !== 'function') {
25
- return connect.call(this, options, null);
26
- }
27
- return connect.apply(this, arguments);
28
- };
29
- }
18
+ var connect = net.Socket.prototype.connect;
19
+ if (typeof connect === 'function') {
20
+ //fix: Node v7.7.0+引入的 `"listener" argument must be a function` 问题
21
+ net.Socket.prototype.connect = function (options, cb) {
22
+ if (options && typeof options === 'object' && typeof cb !== 'function') {
23
+ return connect.call(this, options, null);
24
+ }
25
+ return connect.apply(this, arguments);
26
+ };
30
27
  }
31
28
 
32
29
  var env = process.env || '';
33
30
  env.WHISTLE_ROOT = __dirname;
31
+ env.PFORK_MAX_HTTP_HEADER_SIZE = 1024 * 1024;
34
32
  if (typeof tls.checkServerIdentity == 'function') {
35
33
  var checkServerIdentity = tls.checkServerIdentity;
36
34
  tls.checkServerIdentity = function () {
package/lib/config.js CHANGED
@@ -1,12 +1,11 @@
1
1
  var path = require('path');
2
- var http = require('http');
3
2
  var fs = require('fs');
4
3
  var net = require('net');
5
4
  var dns = require('dns');
6
5
  var qs = require('querystring');
7
6
  var LRU = require('lru-cache');
8
- var httpAgent = http.Agent;
9
- var httpsAgent = require('https').Agent;
7
+ var HttpAgent = require('http').Agent;
8
+ var HttpsAgent = require('https').Agent;
10
9
  var parseUrl = require('./util/parse-url-safe');
11
10
  var fse = require('fs-extra2');
12
11
  var extend = require('extend');
@@ -25,14 +24,11 @@ var CMD_RE = /^[\w]{1,12}(?:\s+-g)?$/;
25
24
  var httpsAgents = new LRU({ max: 360 });
26
25
  var socksAgents = new LRU({ max: 100 });
27
26
  var version = process.version.substring(1).split('.');
28
- var disableAgent = version[0] > 10;
29
- var useVerbatim = version[0] >= 17;
30
- var hasIPv6First = version[0] >= 22 && version[1] >= 1;
27
+ var hasIPv6First = (version[0] === 22 && version[1] >= 1) || version[0] > 22;
31
28
  var hasDnsOrder = typeof dns.setDefaultResultOrder === 'function';
32
29
  var uid = Date.now().toString(16) + (process.pid * 10000 + Math.floor(Math.random() * 10000)).toString(16);
33
30
  var IPV4_RE = /^([\d.]+)(:\d{1,5})?$/;
34
31
  var IPV6_RE = /^\[([\w:]+)\](:\d{1,5})?$/;
35
- var noop = function () {};
36
32
  var DATA_KEY_RE = /^(clientip|clientid|tunneldata)([=.])([\w.-]+)$/i;
37
33
  var DNS_ORDERS = ['ipv4First', 'ipv4first', 'ipv6first', 'ipv6First', 'verbatim'];
38
34
  var LOCAL_UI_HOST_LIST = [
@@ -61,18 +57,12 @@ var variableProperties = [
61
57
  'disableInstaller'
62
58
  ];
63
59
  if (hasDnsOrder) {
64
- if (hasIPv6First) {
65
- config.verbatim = 2;
66
- } else {
67
- config.verbatim = 1;
68
- }
60
+ config.verbatim = hasIPv6First ? 2 : 1;
69
61
  }
70
62
 
71
63
  config.appName = common.upperFirst(config.name);
72
- config.defaultDnsOrder = useVerbatim ? 1 : 2;
73
64
  config.uid = uid;
74
65
  config.rejectUnauthorized = false;
75
- config.enableH2 = version[0] > 12; // 支持 HTTP2 要求的最低 Node 版本
76
66
  config.ASSESTS_PATH = path.join(__dirname, '../assets');
77
67
  config.WHISTLE_POLICY_HEADER = 'x-whistle-policy';
78
68
  config.CLIENT_IP_HEADER = common.CLIENT_IP_HEADER;
@@ -119,16 +109,18 @@ config.PLUGIN_HOOKS = {
119
109
 
120
110
  var KEEP_ALIVE_MSECS = 10000;
121
111
  var CONN_TIMEOUT = common.CONN_TIMEOUT;
122
- var DNS_ORDERS_OPTS = ['', 'verbatim', 'ipv4first', hasIPv6First ? 'ipv6first' : ''];
112
+ var DNS_ORDERS_OPTS = ['', 'ipv4first', 'ipv4first', hasIPv6First ? 'ipv6first' : '', '', 'verbatim'];
123
113
  var DOMAIN_STAR_RE = /([*~]+)(\\.)?/g;
124
114
 
125
115
  config.CONN_TIMEOUT = CONN_TIMEOUT;
126
116
 
127
117
  var getWhistlePath = common.getWhistlePath;
128
118
  var getHostPort = common.getHostPort;
119
+ var WHISTLE_PATH = getWhistlePath();
129
120
 
130
- var ALL_WHISTLES_PATH = path.join(getWhistlePath(), 'all_whistles');
121
+ var ALL_WHISTLES_PATH = path.join(WHISTLE_PATH, 'all_whistles');
131
122
 
123
+ config.WHISTLE_PATH = WHISTLE_PATH;
132
124
 
133
125
  function domainToRegExp(_, star, dot) {
134
126
  var len = star.length;
@@ -143,16 +135,16 @@ function domainToRegExp(_, star, dot) {
143
135
  }
144
136
 
145
137
  function getDataDir(dirname) {
146
- var dir = path.join(getWhistlePath(), dirname || '.' + config.name);
138
+ var dir = path.join(WHISTLE_PATH, dirname || '.' + config.name);
147
139
  fse.ensureDirSync(dir);
148
140
  return dir;
149
141
  }
150
142
 
151
143
  config.baseDir = getDataDir();
152
- config.CUSTOM_PLUGIN_PATH = path.join(getWhistlePath(), 'custom_plugins');
153
- config.CUSTOM_CERTS_DIR = path.resolve(getWhistlePath(), 'custom_certs');
154
- config.SAVED_SESSIONS_PATH = path.resolve(getWhistlePath(), 'saved_sessions');
155
- config.DEV_PLUGINS_PATH = path.resolve(getWhistlePath(), 'dev_plugins');
144
+ config.CUSTOM_PLUGIN_PATH = path.join(WHISTLE_PATH, 'custom_plugins');
145
+ config.CUSTOM_CERTS_DIR = path.resolve(WHISTLE_PATH, 'custom_certs');
146
+ config.SAVED_SESSIONS_PATH = path.resolve(WHISTLE_PATH, 'saved_sessions');
147
+ config.DEV_PLUGINS_PATH = path.resolve(WHISTLE_PATH, 'dev_plugins');
156
148
  config.rulesDir = path.join(config.baseDir, 'rules');
157
149
  config.valuesDir = path.join(config.baseDir, 'values');
158
150
  config.propertiesDir = path.join(config.baseDir, 'properties');
@@ -173,55 +165,6 @@ try {
173
165
 
174
166
  exports.getWhistlePath = getWhistlePath;
175
167
  exports.getDataDir = getDataDir;
176
- var async_id_symbol;
177
- if (version[0] < 16) {
178
- try {
179
- async_id_symbol = process.binding('async_wrap').async_id_symbol;
180
- } catch (e) {}
181
- }
182
- var emptyHandle = {
183
- asyncReset: noop,
184
- getAsyncId: noop
185
- };
186
-
187
- function createAgent(agentConfig, https) {
188
- var agent = new (https ? httpsAgent : httpAgent)(agentConfig);
189
- if (async_id_symbol) {
190
- var addRequest = agent.addRequest;
191
- agent.addRequest = function (req, options) {
192
- // fix: https://github.com/nodejs/node/issues/13539
193
- var freeSockets = this.freeSockets[this.getName(options)];
194
- if (freeSockets && freeSockets.length) {
195
- var socket = freeSockets[0];
196
- var handle = socket._handle;
197
- if (!handle) {
198
- socket._handle = emptyHandle;
199
- } else if (typeof handle.asyncReset !== 'function') {
200
- handle.asyncReset = noop;
201
- }
202
- var originalRef = socket.ref;
203
- socket.ref = function () {
204
- socket.ref = originalRef;
205
- if (socket._handle === emptyHandle) {
206
- delete socket._handle;
207
- } else if (socket._handle.asyncReset === noop) {
208
- delete socket._handle.asyncReset;
209
- }
210
- socket.ref();
211
- };
212
- }
213
- var onSocket = req.onSocket;
214
- req.onSocket = function (socket) {
215
- try {
216
- socket[async_id_symbol] = socket._handle.getAsyncId();
217
- } catch (e) {}
218
- onSocket.apply(this, arguments);
219
- };
220
- addRequest.apply(this, arguments);
221
- };
222
- }
223
- return agent;
224
- }
225
168
 
226
169
  function getHttpsAgent(options, reqOpts) {
227
170
  var key = getCacheKey(options);
@@ -305,13 +248,16 @@ function getAuths(_url) {
305
248
 
306
249
  exports.getAuths = getAuths;
307
250
 
251
+ function getDnsOrder(order) {
252
+ return order == 1 ? 2 : order;
253
+ }
254
+
308
255
  function setDefaultResultOrder(order) {
309
256
  if (!hasDnsOrder) {
310
257
  return;
311
258
  }
312
- order = DNS_ORDERS_OPTS.indexOf(order) === -1 ? DNS_ORDERS_OPTS[order] : order;
313
- if (!order) {
314
- return;
259
+ if (DNS_ORDERS_OPTS.indexOf(order) === -1) {
260
+ order = DNS_ORDERS_OPTS[order] || DNS_ORDERS_OPTS[1];
315
261
  }
316
262
  config.ipv4First = false;
317
263
  config.ipv6First = false;
@@ -321,10 +267,10 @@ function setDefaultResultOrder(order) {
321
267
  config.ipv4First = true;
322
268
  }
323
269
  try {
324
- if (dns.getDefaultResultOrder() !== order) {
270
+ if (!dns.getDefaultResultOrder || dns.getDefaultResultOrder() !== order) {
325
271
  dns.setDefaultResultOrder(order);
326
272
  }
327
- config.dnsOrder = DNS_ORDERS_OPTS.indexOf(order);
273
+ config.dnsOrder = getDnsOrder(DNS_ORDERS_OPTS.indexOf(order));
328
274
  return true;
329
275
  } catch (e) {}
330
276
  }
@@ -1096,10 +1042,10 @@ exports.extend = function (newConf) {
1096
1042
  keepAliveMsecs: KEEP_ALIVE_MSECS,
1097
1043
  maxFreeSockets: 0
1098
1044
  };
1099
- // node 11及以上版本缓存连接有问题,先禁掉
1100
- disableAgent = !newAgentConf && !useAgent && (disableAgent || config.debug);
1101
- config.httpAgent = disableAgent ? false : createAgent(agentConfig);
1102
- config.httpsAgent = disableAgent ? false : createAgent(agentConfig, true);
1045
+ // node 11及以上版本缓存连接有问题,默认禁掉
1046
+ var disableAgent = !newAgentConf && !useAgent;
1047
+ config.httpAgent = disableAgent ? false : new HttpAgent(agentConfig);
1048
+ config.httpsAgent = disableAgent ? false : new HttpsAgent(agentConfig);
1103
1049
  config.getSocksAgent = function (options) {
1104
1050
  var key = getCacheKey(options, true);
1105
1051
  var agent = socksAgents.get(key);
@@ -157,7 +157,7 @@ function getRawResByPath(protocol, path, req, size, callback, body) {
157
157
  }
158
158
  });
159
159
  reader.on('error', response);
160
- reader.on('end', response);
160
+ reader.once('end', response);
161
161
  } else {
162
162
  callback(reader, range, size);
163
163
  }
package/lib/https/ca.js CHANGED
@@ -43,9 +43,9 @@ var ILEGAL_CHAR_RE = /[^a-z\d-]/i;
43
43
  // https://cloud.tencent.com/developer/ask/sof/58155
44
44
  var RANDOM_SERIAL = '/' + Date.now() + '/' + process.pid + '/' + Math.floor(Math.random() * 100);
45
45
  var ONE_DAY = 1000 * 60 * 60 * 24;
46
- var MIN_DATE = ONE_DAY * 20;
47
- var CLEAR_CERTS_INTERVAL = ONE_DAY * 20;
48
- var MAX_INNTERFAL = 16;
46
+ var ONE_YEAR = ONE_DAY * 365;
47
+ var MIN_DATE = ONE_DAY * 7;
48
+ var MAX_DATE = ONE_DAY * 36;
49
49
  var PORT_RE = /:\d*$/;
50
50
  var pureCertFiles = {};
51
51
  var customRoot;
@@ -71,13 +71,16 @@ function resetAllCerts() {
71
71
  }
72
72
 
73
73
  // When delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer.
74
- var intervalCount = 0;
74
+ var startTime = Date.now();
75
+ var INTERVAL = ONE_DAY;
76
+ var MAX_AGE = MAX_DATE - INTERVAL * 2;
75
77
  var timer = setInterval(function () {
76
- if (++intervalCount >= MAX_INNTERFAL) {
77
- intervalCount = 0;
78
+ var now = Date.now();
79
+ if (now - startTime >= MAX_AGE) {
80
+ startTime = now;
78
81
  resetAllCerts();
79
82
  }
80
- }, CLEAR_CERTS_INTERVAL);
83
+ }, INTERVAL);
81
84
 
82
85
  if (timer && typeof timer.unref === 'function') {
83
86
  timer.unref();
@@ -553,18 +556,16 @@ function createCert(publicKey, serialNumber, isShortPeriod) {
553
556
  var cert = pki.createCertificate();
554
557
  cert.publicKey = publicKey;
555
558
  cert.serialNumber = serialNumber || '01';
556
- var curDate = new Date();
557
- var curYear = curDate.getFullYear();
559
+ var now = Date.now();
560
+ // https://www.ssls.com/blog/apples-new-ssl-lifetime-limitation-and-what-it-means-for-you/
561
+ // https://chromium.googlesource.com/chromium/src/+/refs/heads/master/net/cert/cert_verify_proc.cc#900
558
562
  if (isShortPeriod) {
559
- cert.validity.notBefore = new Date(curDate.getTime() - MIN_DATE);
563
+ cert.validity.notBefore = new Date(now - MIN_DATE);
564
+ cert.validity.notAfter = new Date(now + MAX_DATE);
560
565
  } else {
561
- cert.validity.notBefore = new Date();
562
- cert.validity.notBefore.setFullYear(curYear - 1);
566
+ cert.validity.notBefore = new Date(now - ONE_YEAR);
567
+ cert.validity.notAfter = new Date(now + ONE_YEAR * 10);
563
568
  }
564
- // https://www.ssls.com/blog/apples-new-ssl-lifetime-limitation-and-what-it-means-for-you/
565
- // https://chromium.googlesource.com/chromium/src/+/refs/heads/master/net/cert/cert_verify_proc.cc#900
566
- cert.validity.notAfter = new Date();
567
- cert.validity.notAfter.setFullYear(curYear + (isShortPeriod ? 1 : 10));
568
569
  return cert;
569
570
  }
570
571
 
@@ -690,7 +691,7 @@ exports.getHttp2Server = function (listener, callback) {
690
691
  };
691
692
 
692
693
  exports.getSNIServer = function (listener, callback, disableH2, requestCert) {
693
- var enableH2 = config.enableH2 && !disableH2;
694
+ var enableH2 = !disableH2;
694
695
  var name = (enableH2 ? 'h2Sni' : 'sni') + (requestCert ? 'WithCert' : '');
695
696
  var curPort = ports[name];
696
697
  if (curPort) {
package/lib/https/h2.js CHANGED
@@ -5,7 +5,7 @@ var sockx = require('sockx');
5
5
  var util = require('../util');
6
6
  var config = require('../config');
7
7
  var extend = require('extend');
8
- var http2 = config.enableH2 ? require('http2') : null;
8
+ var http2 = require('http2');
9
9
 
10
10
  var SUPPORTED_PROTOS = ['h2', 'http/1.1', 'http/1.0'];
11
11
  var H2_SETTINGS = { enablePush: false };
@@ -33,33 +33,43 @@ function onClose(stream, callback) {
33
33
  });
34
34
  }
35
35
 
36
+ function closeClose(client, name) {
37
+ try {
38
+ client.close();
39
+ } catch (e) {}
40
+ delete clients[name];
41
+ }
42
+
36
43
  setInterval(function () {
37
44
  var now = Date.now();
38
45
  Object.keys(clients).forEach(function (name) {
39
46
  var client = clients[name];
40
- if (now - client._updateTime > CACHE_TIMEOUT) {
41
- client.close();
42
- delete clients[name];
47
+ if (isClosed(client) || now - client._updateTime > CACHE_TIMEOUT) {
48
+ closeClose(client, name);
43
49
  }
44
50
  });
45
51
  }, INTERVAL);
46
52
 
47
- function getKey(options) {
53
+ function getOrigin(req) {
54
+ return (req.useHttpH2 ? 'http' : 'https') + '://' + req.headers.host;
55
+ }
56
+
57
+ function getKey(options, req) {
48
58
  var proxyOpts = options._proxyOptions;
49
59
  var proxyType = '';
50
60
  if (proxyOpts) {
51
- var auth =
52
- (proxyOpts.headers && proxyOpts.headers['proxy-authorization']) || '';
61
+ var headers = proxyOpts.headers || {};
53
62
  proxyType = [
54
63
  proxyOpts.proxyType,
55
64
  proxyOpts.proxyHost,
56
65
  proxyOpts.proxyPort,
57
- proxyOpts.headers.host,
66
+ headers.host,
58
67
  proxyOpts.proxyTunnelPath || '',
59
- auth
68
+ headers['proxy-authorization'] || ''
60
69
  ].join(':');
61
70
  }
62
71
  return [
72
+ getOrigin(req),
63
73
  options.servername,
64
74
  options.host,
65
75
  options.port || '',
@@ -222,35 +232,35 @@ function getSocket(options, callback, req) {
222
232
  }
223
233
 
224
234
  function getClient(req, socket, name, callback) {
225
- var origin = (req.useHttpH2 ? 'http' : 'https') + '://' + req.headers.host;
226
235
  var handleCallback = function(err, clt) {
227
236
  if (callback) {
228
237
  clearTimeout(timer);
229
238
  if (err || !clt) {
230
- delete clients[name];
239
+ closeClose(client, name);
231
240
  socket.destroy();
232
- client.close();
233
241
  }
234
242
  callback(clt);
235
243
  callback = timer = null;
236
244
  }
237
245
  };
238
246
  var timer = setTimeout(handleCallback, REQ_TIMEOUT);
239
- var client = http2.connect(
240
- origin, util.setRejectUnauthorized(req, {
241
- settings: H2_SETTINGS,
242
- maxSessionMemory: MSM,
243
- peerMaxConcurrentStreams: PMCS,
244
- streamResetBurst: RESET_BURST,
245
- streamResetRate: RESET_RATE,
246
- createConnection: function () {
247
- return socket;
248
- }
249
- }), function() {
250
- handleCallback(null, client);
251
- });
247
+ var client = http2.connect(getOrigin(req), util.setRejectUnauthorized(req, {
248
+ settings: H2_SETTINGS,
249
+ maxSessionMemory: MSM,
250
+ peerMaxConcurrentStreams: PMCS,
251
+ streamResetBurst: RESET_BURST,
252
+ streamResetRate: RESET_RATE,
253
+ createConnection: function () {
254
+ return socket;
255
+ }
256
+ }), function() {
257
+ handleCallback(null, client);
258
+ });
252
259
  clients[name] = client;
253
260
  client._updateTime = Date.now();
261
+ client.once('goaway', function() {
262
+ closeClose(client, name);
263
+ });
254
264
  onClose(client, handleCallback);
255
265
  onClose(socket, handleCallback);
256
266
  return client;
@@ -261,11 +271,6 @@ function requestH2(client, req, res, callback) {
261
271
  return;
262
272
  }
263
273
  var headers = util.formatH2Headers(req.headers);
264
- delete req.headers.connection;
265
- delete req.headers['keep-alive'];
266
- delete req.headers['http2-settings'];
267
- delete req.headers['proxy-connection'];
268
- delete req.headers['transfer-encoding'];
269
274
  var options = req.options;
270
275
  var responsed;
271
276
  headers[':path'] = options.path;
@@ -327,8 +332,10 @@ function requestH2(client, req, res, callback) {
327
332
  });
328
333
  req.pipe(h2Session);
329
334
  } catch (e) {
330
- !responsed && callback();
331
- responsed = true;
335
+ if (!responsed) {
336
+ callback();
337
+ responsed = true;
338
+ }
332
339
  }
333
340
  }
334
341
 
@@ -380,12 +387,16 @@ function checkTlsError(err) {
380
387
  return typeof code === 'string' && (code.indexOf('ERR_TLS_') === 0 || code.indexOf('ERR_SSL_') === 0);
381
388
  }
382
389
 
390
+ function isClosed(client) {
391
+ return client.destroyed || client.closed;
392
+ }
393
+
383
394
  exports.request = function (req, res, callback) {
384
395
  var options = req.useH2 && req.options;
385
396
  if (!options || options.isPlugin || req._isInternalProxy) {
386
397
  return callback();
387
398
  }
388
- var key = getKey(options);
399
+ var key = getKey(options, req);
389
400
  var reqId = req.disable.keepH2Session ? '' : req._h2ReqId;
390
401
  var name = (reqId || req.clientIp) + '\n' + key;
391
402
  var client = clients[name];
@@ -395,13 +406,21 @@ exports.request = function (req, res, callback) {
395
406
  name = name + '\n' + client.curIndex;
396
407
  client.curIndex = ++client.curIndex % CONCURRENT;
397
408
  client = clients[name];
409
+ if (client && isClosed(client)) {
410
+ delete clients[name];
411
+ client = null;
412
+ }
398
413
  } else {
399
414
  client.curIndex = 1;
400
415
  }
401
416
  }
402
417
  if (client) {
403
- client._updateTime = Date.now();
404
- return requestH2(client, req, res, callback);
418
+ if (!isClosed(client)) {
419
+ client._updateTime = Date.now();
420
+ return requestH2(client, req, res, callback);
421
+ }
422
+ delete clients[name];
423
+ client = null;
405
424
  }
406
425
  }
407
426
  var time = notH2.peek(key);