whistle 2.10.5 → 2.10.7

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 (56) hide show
  1. package/assets/menu.html +2 -2
  2. package/assets/modal.html +2 -2
  3. package/assets/tab.html +2 -2
  4. package/bin/api.js +545 -0
  5. package/bin/import.js +3 -3
  6. package/bin/plugin.js +4 -8
  7. package/bin/use.js +22 -8
  8. package/bin/util.js +12 -7
  9. package/bin/whistle.js +11 -1
  10. package/biz/webui/cgi-bin/abort.js +14 -5
  11. package/biz/webui/cgi-bin/frames.js +12 -0
  12. package/biz/webui/cgi-bin/get-session.js +7 -3
  13. package/biz/webui/cgi-bin/is-enabled-https.js +6 -0
  14. package/biz/webui/cgi-bin/plugins/disable-plugin.js +16 -7
  15. package/biz/webui/cgi-bin/plugins/list.js +17 -0
  16. package/biz/webui/cgi-bin/plugins/plugin.js +21 -0
  17. package/biz/webui/cgi-bin/plugins/status.js +21 -0
  18. package/biz/webui/cgi-bin/rules/is-multi-select.js +7 -0
  19. package/biz/webui/cgi-bin/rules/move-top.js +11 -0
  20. package/biz/webui/cgi-bin/rules/select.js +1 -1
  21. package/biz/webui/cgi-bin/rules/select2.js +16 -0
  22. package/biz/webui/cgi-bin/rules/status.js +6 -0
  23. package/biz/webui/cgi-bin/rules/unselect.js +3 -2
  24. package/biz/webui/cgi-bin/rules/unselect2.js +18 -0
  25. package/biz/webui/cgi-bin/rules/value.js +19 -0
  26. package/biz/webui/cgi-bin/sessions.js +6 -0
  27. package/biz/webui/cgi-bin/util.js +45 -20
  28. package/biz/webui/htdocs/index.html +1 -1
  29. package/biz/webui/htdocs/js/index.js +51 -51
  30. package/biz/webui/lib/index.js +1 -3
  31. package/lib/https/index.js +7 -5
  32. package/lib/init.js +1 -0
  33. package/lib/inspectors/data.js +39 -34
  34. package/lib/inspectors/req.js +9 -6
  35. package/lib/inspectors/res.js +10 -14
  36. package/lib/plugins/index.js +2 -2
  37. package/lib/plugins/load-plugin.js +19 -8
  38. package/lib/rules/util.js +20 -10
  39. package/lib/service/composer.js +7 -22
  40. package/lib/service/data-center.js +0 -12
  41. package/lib/service/generate-saz.js +2 -2
  42. package/lib/service/service.js +8 -7
  43. package/lib/service/util.js +6 -16
  44. package/lib/socket-mgr.js +23 -17
  45. package/lib/tunnel.js +7 -17
  46. package/lib/upgrade.js +4 -8
  47. package/lib/util/common.js +78 -11
  48. package/lib/util/data-server.js +273 -0
  49. package/lib/util/file-mgr.js +7 -4
  50. package/lib/util/http-mgr.js +17 -9
  51. package/lib/util/index.js +67 -54
  52. package/lib/util/parse-url.js +11 -9
  53. package/lib/util/patch.js +2 -6
  54. package/lib/util/speed-transform.js +17 -6
  55. package/lib/util/whistle-transform.js +18 -7
  56. package/package.json +10 -7
@@ -31,6 +31,58 @@ var framesCache = [];
31
31
  var framesMap = {};
32
32
  var proxy, binded;
33
33
  var clearCount = 0;
34
+ var MAX_COUNT = 120;
35
+
36
+ function formatApiOptions(options) {
37
+ var result = { count: MAX_COUNT };
38
+ if (options) {
39
+ var count = options.count;
40
+ if (count > 0 && count < MAX_COUNT) {
41
+ result.count = count;
42
+ }
43
+ var startTime = options.startTime > 0 ? options.startTime + '' : null;
44
+ var startId = util.isString(options.startId) ? options.startId : null;
45
+ if (startId && startTime && startId < startTime) {
46
+ startId = startTime;
47
+ }
48
+ result.startId = startId || startTime;
49
+ result.latest = options.latest;
50
+ }
51
+ return result;
52
+ }
53
+
54
+ function getItemId(item, name) {
55
+ return name ? item[name] : item;
56
+ }
57
+
58
+ function getIdIndex(arr, target, name) {
59
+ if (!target) {
60
+ return 0;
61
+ }
62
+ var left = 0;
63
+ var right = arr.length - 1;
64
+
65
+ while (left <= right) {
66
+ if (getItemId(arr[left], name) > target) {
67
+ return left;
68
+ }
69
+ var count = right - left;
70
+ if (!count) {
71
+ return -1;
72
+ }
73
+ if (count === 1) {
74
+ return getItemId(arr[right], name) > target ? right : -1;
75
+ }
76
+ var mid = left + Math.floor(count / 2);
77
+ var val = getItemId(arr[mid], name);
78
+ if (val > target) {
79
+ right = mid;
80
+ } else {
81
+ left = mid + 1;
82
+ }
83
+ }
84
+ return -1;
85
+ }
34
86
 
35
87
  function enable() {
36
88
  if (binded) {
@@ -595,6 +647,227 @@ module.exports = function init(_proxy) {
595
647
  return getFrames(options.curReqId, options.lastFrameId);
596
648
  };
597
649
 
650
+ proxy.getFramesForApi = function (options) {
651
+ var reqId = options.reqId;
652
+ var isServer = options.from === 'server';
653
+ var isClient = options.from === 'client';
654
+ var isAll = !isServer && !isClient;
655
+ var params = formatApiOptions(options);
656
+ var index = getIdIndex(framesCache, params.startId, 'frameId');
657
+ var result = [];
658
+ if (index === -1) {
659
+ return result;
660
+ }
661
+ var count = params.count;
662
+ var len = framesCache.length;
663
+ var addFrame = function(i) {
664
+ var frame = framesCache[i];
665
+ if ((isAll || isClient === !!frame.isClient) && frame.reqId === reqId) {
666
+ result.push(decodeData(frame));
667
+ if (--count <= 0) {
668
+ return true;
669
+ }
670
+ }
671
+ };
672
+ if (params.latest) {
673
+ for (var i = len - 1; i >= index; i--) {
674
+ if (addFrame(i)) {
675
+ break;
676
+ }
677
+ }
678
+ result.reverse();
679
+ } else {
680
+ for (; index < len; index++) {
681
+ if (addFrame(index)) {
682
+ break;
683
+ }
684
+ }
685
+ }
686
+ return result;
687
+ };
688
+
689
+ function checkUrl(session, subUrl) {
690
+ if (!subUrl) {
691
+ return true;
692
+ }
693
+ var url = session.isHttps ? 'tunnel://' + session.url : session.url.toLowerCase();
694
+ return url.indexOf(subUrl) !== -1;
695
+ }
696
+
697
+ var WS_RE = /^wss?:\/\//;
698
+ var FONT_RE = /font\//i;
699
+ var MEDIA_RE = /audio\/|video\/|application\/vnd.apple.mpegurl|application\/dash+xml/i;
700
+ var WASM_RE = /application\/wasm/i;
701
+ var TYPES = ['IMG', 'HTML', 'CSS', 'JS', 'JSON'];
702
+ var ALIAS_TYPES = {
703
+ WEBSOCKET: 'WS',
704
+ WSS: 'WS',
705
+ IMAGE: 'IMG',
706
+ JAVASCRIPT: 'JS',
707
+ STYLE: 'CSS',
708
+ MATCHED: 'RULES',
709
+ EDIT: 'COMPOSER'
710
+ };
711
+ var RAW_TYPES = ['FONT', 'MEDIA', 'WASM'];
712
+
713
+ function hasError(item) {
714
+ var statusCode = item.res && item.res.statusCode;
715
+ return item.reqError || item.resError || (item.customData && item.customData.error) ||
716
+ (statusCode && (!/^\d+$/.test(statusCode) || statusCode >= 400));
717
+ }
718
+
719
+ function checkType(session, filterType) {
720
+ if (!filterType) {
721
+ return true;
722
+ }
723
+ filterType = ALIAS_TYPES[filterType] || filterType;
724
+ if (filterType === 'CAPTUREERROR') {
725
+ return session.captureError;
726
+ }
727
+ if (filterType === 'WS') {
728
+ return WS_RE.test(session.url);
729
+ }
730
+ if (filterType === 'TUNNEL') {
731
+ return session.isHttps;
732
+ }
733
+ if (filterType === 'RULES') {
734
+ return session.rules && Object.keys(session.rules).length > 0;
735
+ }
736
+ var rawType = util.getRawType(session.res);
737
+ var type;
738
+ for (var i = 0, len = RAW_TYPES.length; i < len; i++) {
739
+ type = RAW_TYPES[i];
740
+ if (filterType === type) {
741
+ var p = type === 'FONT' ? FONT_RE : type === 'MEDIA' ? MEDIA_RE : WASM_RE;
742
+ return p.test(rawType);
743
+ }
744
+ }
745
+
746
+ var curType = util.getContentType(rawType);
747
+ for (i = 0, len = TYPES.length; i < len; i++) {
748
+ type = TYPES[i];
749
+ if (filterType === type) {
750
+ return curType === (i ? type : 'IMG');
751
+ }
752
+ }
753
+ if (filterType === 'ERROR') {
754
+ return hasError(session);
755
+ }
756
+ if (filterType === 'MOCK') {
757
+ var rules = session.rules || '';
758
+ return rules.rule && rules.rule.isLoc;
759
+ }
760
+ if (filterType === 'IMPORT') {
761
+ return session.importedData;
762
+ }
763
+ if (filterType === 'COMPOSER') {
764
+ return session.fc;
765
+ }
766
+ }
767
+
768
+ function formatHeaderFilter(filter) {
769
+ if (!filter) {
770
+ return;
771
+ }
772
+ var name = filter.name || filter.key;
773
+ if (typeof name !== 'string') {
774
+ return;
775
+ }
776
+ return {
777
+ name: name.trim(),
778
+ subValue: filter.subValue == null ? null : String(filter.subValue).toLowerCase()
779
+ };
780
+ }
781
+
782
+ function filterHeader(headers, filter) {
783
+ if (!filter) {
784
+ return true;
785
+ }
786
+ if (!headers) {
787
+ return false;
788
+ }
789
+ var value = headers[filter.name];
790
+ if (value == null) {
791
+ return false;
792
+ }
793
+ var subValue = filter.subValue;
794
+ var noSubValue = !subValue;
795
+ if (noSubValue || !value) {
796
+ return noSubValue;
797
+ }
798
+ value = Array.isArray(value) ? value.join('\n') : String(value);
799
+ return value.toLowerCase().indexOf(subValue) !== -1;
800
+ }
801
+
802
+ function checkMethod(session, method) {
803
+ if (!method) {
804
+ return true;
805
+ }
806
+ return method.indexOf(session.req.method) !== -1;
807
+ }
808
+
809
+ function checkStatus(session, status) {
810
+ return !status || status.indexOf(String(session.res.statusCode).toUpperCase()) !== -1;
811
+ }
812
+
813
+
814
+ proxy.getSessionsForApi = function (options) {
815
+ var params = formatApiOptions(options);
816
+ var index = getIdIndex(ids, params.startId);
817
+ var result = [];
818
+ if (index === -1) {
819
+ return result;
820
+ }
821
+ var count = params.count;
822
+ var len = ids.length;
823
+ var subUrl = util.getString(options.subUrl).toLowerCase();
824
+ var type = util.getString(options.type).toUpperCase();
825
+ var method = options.method;
826
+ var status = options.statusCode || options.status;
827
+ var reqHeader = formatHeaderFilter(options.reqHeader);
828
+ var resHeader = formatHeaderFilter(options.resHeader);
829
+ if (Array.isArray(method)) {
830
+ method = method.join();
831
+ } else {
832
+ method = util.getString(method);
833
+ }
834
+ if (Array.isArray(status)) {
835
+ status = status.join();
836
+ } else if (status) {
837
+ status = String(status);
838
+ }
839
+ method = method && method.toUpperCase().split(',');
840
+ status = status && status.toUpperCase().split(',');
841
+ var addSession = function(i) {
842
+ var session = reqData[ids[i]];
843
+ if (checkUrl(session, subUrl) && checkMethod(session, method)
844
+ && checkStatus(session, status) && filterHeader(session.req.headers, reqHeader)
845
+ && filterHeader(session.res.headers, resHeader) && checkType(session, type)) {
846
+ toBase64String(session.req);
847
+ toBase64String(session.res);
848
+ result.push(session);
849
+ if (--count <= 0) {
850
+ return true;
851
+ }
852
+ }
853
+ };
854
+ if (params.latest) {
855
+ for (var i = len - 1; i >= index; i--) {
856
+ if (addSession(i)) {
857
+ break;
858
+ }
859
+ }
860
+ result.reverse();
861
+ } else {
862
+ for (; index < len; index++) {
863
+ if (addSession(index)) {
864
+ break;
865
+ }
866
+ }
867
+ }
868
+ return result;
869
+ };
870
+
598
871
  function getNetworkTime(id) {
599
872
  var item = id && reqData[id];
600
873
  return item && {
@@ -36,21 +36,24 @@ function readSingleFile(path, callback) {
36
36
  return callback();
37
37
  }
38
38
  var stream = fs.createReadStream(convertSlash(path));
39
- var done, buf;
39
+ var done;
40
+ var buf = [];
41
+ var len = 0;
40
42
  var execCallback = function (err) {
41
43
  if (done) {
42
44
  return;
43
45
  }
44
46
  done = true;
45
47
  stream.close();
46
- callback(err ? null : buf);
48
+ callback(buf.length > 1 ? Buffer.concat(buf) : buf[0]);
47
49
  };
48
50
  stream.on('data', function (data) {
49
51
  if (done) {
50
52
  return;
51
53
  }
52
- buf = buf ? Buffer.concat([buf, data]) : data;
53
- if (buf.length > MAX_SIZE) {
54
+ len += data.length;
55
+ buf.push(data);
56
+ if (len > MAX_SIZE) {
54
57
  execCallback();
55
58
  }
56
59
  });
@@ -182,6 +182,10 @@ function gunzip(err, res, body, callback) {
182
182
  }
183
183
  }
184
184
 
185
+ function getBody(body, len) {
186
+ return body.length > 1 ? Buffer.concat(body, len) : (body[0] || '');
187
+ }
188
+
185
189
  function request(options, callback) {
186
190
  loadPlugin(options, function (err) {
187
191
  if (err) {
@@ -230,21 +234,23 @@ function request(options, callback) {
230
234
  return callback(null, res, res);
231
235
  }
232
236
  addTimeout();
233
- var body = '';
237
+ var body = [];
238
+ var len = 0;
234
239
  res.on('data', function (data) {
235
- body = body ? Buffer.concat([body, data]) : data;
240
+ len += data.length;
241
+ body.push(data);
236
242
  addTimeout();
237
- if (maxLength && body.length > maxLength) {
243
+ if (maxLength && len > maxLength) {
238
244
  var err;
239
245
  if (!options.ignoreExceedError) {
240
246
  err = new Error('The response body exceeded length limit');
241
247
  err.code = EXCEED;
242
248
  }
243
- handleCallback(err, res, body);
249
+ handleCallback(err, res, getBody(body, len));
244
250
  }
245
251
  });
246
252
  res.on('end', function() {
247
- handleCallback(null, res, body);
253
+ handleCallback(null, res, getBody(body, len));
248
254
  });
249
255
  };
250
256
  client = sendReq(options, function (err, res) {
@@ -293,7 +299,8 @@ function readFile(url, callback) {
293
299
 
294
300
  var stream = fs.createReadStream(filePath, OPTIONS);
295
301
  var done;
296
- var body = '';
302
+ var body = [];
303
+ var len = 0;
297
304
  var listener = function (err) {
298
305
  if (done) {
299
306
  return;
@@ -305,14 +312,15 @@ function readFile(url, callback) {
305
312
  done = true;
306
313
  data.mtime = time;
307
314
  stream.close();
308
- triggerChange(data, body);
315
+ triggerChange(data, body.join(''));
309
316
  };
310
317
  stream.on('data', function (text) {
311
318
  if (done) {
312
319
  return;
313
320
  }
314
- body += text;
315
- if (body.length > MAX_FILE_LEN) {
321
+ len += text.length;
322
+ body.push(text);
323
+ if (len > MAX_FILE_LEN) {
316
324
  listener();
317
325
  }
318
326
  });
package/lib/util/index.js CHANGED
@@ -126,7 +126,10 @@ exports.ADDITIONAL_HEAD = 'x-whistle-additional-headers';
126
126
  workerIndex = workerIndex >= 0 ? common.padLeft(config.workerIndex, 3) : '';
127
127
 
128
128
 
129
+ exports.toString = common.toString;
129
130
  exports.sendRes = common.sendRes;
131
+ exports.setDelayTimer = common.setDelayTimer;
132
+ exports.clearDelayTimer = common.clearDelayTimer;
130
133
  exports.trimStr = trimStr;
131
134
  exports.THEME_STYLE = THEME_STYLE;
132
135
  exports.encodeNonLatin1Char = encodeNonLatin1Char;
@@ -747,24 +750,38 @@ exports.disableCSP = disableCSP;
747
750
 
748
751
  var interfaces = os.networkInterfaces();
749
752
  var hostname = os.hostname();
753
+ var serverInfo;
750
754
  var simpleHostname = '';
751
755
  var cpus = os.cpus();
752
756
  var addressList = [];
753
757
  var usiTimer;
754
758
 
755
- function updateSystyemInfo() {
759
+ function updateServerInfo() {
760
+ serverInfo = {ipv4: [], ipv6: []};
761
+ Object.keys(interfaces).forEach(function(ifname) {
762
+ interfaces[ifname].forEach(function (iface) {
763
+ if (iface.internal) {
764
+ return;
765
+ }
766
+ serverInfo[iface.family == 'IPv4' || iface.family === 4 ? 'ipv4' : 'ipv6'].push(iface.address);
767
+ });
768
+ });
769
+ }
770
+
771
+ function updateSysInfo() {
756
772
  clearTimeout(usiTimer);
757
773
  interfaces = os.networkInterfaces();
774
+ updateServerInfo();
758
775
  hostname = os.hostname();
759
776
  addressList = [];
760
777
  common.walkInterfaces(function (info) {
761
778
  addressList.push(info.address.toLowerCase());
762
779
  });
763
- usiTimer = setTimeout(updateSystyemInfo, 36000);
780
+ usiTimer = setTimeout(updateSysInfo, 36000);
764
781
  }
765
782
 
766
- updateSystyemInfo();
767
- process.on('w2NetworkInterfacesChange', updateSystyemInfo);
783
+ updateSysInfo();
784
+ process.on('w2NetworkInterfacesChange', updateSysInfo);
768
785
 
769
786
  if (isString(hostname)) {
770
787
  simpleHostname = hostname.replace(/[^\w.-]+/g, '').substring(0, 20);
@@ -874,15 +891,13 @@ exports.removeClientId = function (headers) {
874
891
  delete headers[config.CLIENT_ID_HEADER];
875
892
  };
876
893
 
877
- function networkInterfaces() {
878
- return interfaces;
879
- }
880
-
881
894
  function getHostname() {
882
895
  return hostname;
883
896
  }
884
897
 
885
- exports.networkInterfaces = networkInterfaces;
898
+ exports.getServerInfo = function() {
899
+ return serverInfo;
900
+ };
886
901
  exports.hostname = getHostname;
887
902
 
888
903
  function getProxyTunnelPath(req, isHttps) {
@@ -1613,35 +1628,35 @@ function getPipeIconvStream(headers) {
1613
1628
  pipeStream.addTail(iconv.encodeStream(charset));
1614
1629
  } else {
1615
1630
  pipeStream.addHead(function (res, next) {
1616
- var buffer, iconvDecoder;
1617
-
1618
- res.on('data', function (chunk) {
1619
- buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk;
1620
- resolveCharset(buffer);
1621
- });
1622
- res.on('end', resolveCharset);
1623
-
1624
- function resolveCharset(chunk) {
1625
- if (!charset) {
1626
- if (chunk && buffer.length < 25600) {
1631
+ var buf = [];
1632
+ var len = 0;
1633
+ var resolveCharset = function(chunk) {
1634
+ if (chunk) {
1635
+ len += chunk.length;
1636
+ buf.push(chunk);
1637
+ if (len < 25600) {
1627
1638
  return;
1628
1639
  }
1629
- charset = !buffer || isUtf8(buffer) ? 'utf8' : 'GB18030';
1630
1640
  }
1631
- if (!iconvDecoder) {
1632
- iconvDecoder = iconv.decodeStream(charset);
1633
- next(iconvDecoder);
1641
+ res.removeListener('data', resolveCharset);
1642
+ res.removeListener('end', resolveCharset);
1643
+ buf = buf.length > 1 ? Buffer.concat(buf, len) : buf[0];
1644
+ if (!buf) {
1645
+ return next(res);
1634
1646
  }
1635
- if (buffer) {
1636
- iconvDecoder.write(buffer);
1637
- buffer = null;
1638
- }
1639
- !chunk && iconvDecoder.end();
1640
- }
1647
+ charset = isUtf8(buf) ? 'utf8' : 'GB18030';
1648
+ var decoder = iconv.decodeStream(charset);
1649
+ decoder.write(buf);
1650
+ next(res.pipe(decoder));
1651
+ buf = null;
1652
+ };
1653
+
1654
+ res.on('data', resolveCharset);
1655
+ res.on('end', resolveCharset);
1641
1656
  });
1642
1657
 
1643
1658
  pipeStream.addTail(function (src, next) {
1644
- next(src.pipe(iconv.encodeStream(charset)));
1659
+ next(charset ? src.pipe(iconv.encodeStream(charset)) : src);
1645
1660
  });
1646
1661
  }
1647
1662
 
@@ -2452,10 +2467,7 @@ exports.getPluginModal = function(conf) {
2452
2467
  };
2453
2468
 
2454
2469
  function getString(str) {
2455
- if (!isString(str)) {
2456
- return;
2457
- }
2458
- return str.trim();
2470
+ return isString(str) ? str.trim() : '';
2459
2471
  }
2460
2472
 
2461
2473
  exports.getString = getString;
@@ -2625,19 +2637,6 @@ exports.getStaticDir = function (conf) {
2625
2637
  return toRelPath(staticDir.replace(/^\/+/, ''));
2626
2638
  };
2627
2639
 
2628
- function toString(str) {
2629
- if (str != null) {
2630
- if (typeof str === 'string') {
2631
- return str;
2632
- }
2633
- try {
2634
- return JSON.stringify(str) || '';
2635
- } catch (e) {}
2636
- }
2637
- return '';
2638
- }
2639
- exports.toString = toString;
2640
-
2641
2640
  var index = 0;
2642
2641
 
2643
2642
  exports.getReqId = function (now) {
@@ -2965,7 +2964,7 @@ exports.setResCors = function (data, cors, req) {
2965
2964
  } else if (autoComp) {
2966
2965
  var method = req.headers['access-control-request-method'];
2967
2966
  if (method) {
2968
- setHeader(data, 'access-control-allow-method', method);
2967
+ setHeader(data, 'access-control-allow-methods', method);
2969
2968
  }
2970
2969
  }
2971
2970
 
@@ -3357,7 +3356,7 @@ exports.parseRange = function (req, size) {
3357
3356
  }
3358
3357
  var start = size;
3359
3358
  var end = -1;
3360
- range = range.split(',').forEach(function (item) {
3359
+ range.split(',').forEach(function (item) {
3361
3360
  item = item.split('-');
3362
3361
  var s = parseInt(item[0], 10);
3363
3362
  var e = parseInt(item[1], 10);
@@ -3612,7 +3611,7 @@ function readOneChunk(stream, callback, timeout) {
3612
3611
  }
3613
3612
  var timer;
3614
3613
  var handler = function (chunk) {
3615
- timer && clearTimeout(timer);
3614
+ clearTimeout(timer);
3616
3615
  stream.pause();
3617
3616
  stream.removeListener('data', handler);
3618
3617
  stream.removeListener('end', handler);
@@ -3686,10 +3685,9 @@ exports.getAuthBasic = function (auth) {
3686
3685
 
3687
3686
  exports.delay = function (time, callback) {
3688
3687
  if (time > 0) {
3689
- setTimeout(callback, time);
3690
- } else {
3691
- callback();
3688
+ return setTimeout(callback, time);
3692
3689
  }
3690
+ callback();
3693
3691
  };
3694
3692
 
3695
3693
  var F_HOST_RE = /\bhost\b/i;
@@ -4016,3 +4014,18 @@ exports.getFrameId = function() {
4016
4014
  }
4017
4015
  return Date.now() + '-00' + frameIndex;
4018
4016
  };
4017
+
4018
+ exports.joinBuffer = function(buf, chunk) {
4019
+ if (!buf) {
4020
+ return chunk;
4021
+ }
4022
+ if (!Array.isArray(buf)) {
4023
+ return [buf, chunk];
4024
+ }
4025
+ buf.push(chunk);
4026
+ return buf;
4027
+ };
4028
+
4029
+ exports.formatBuffer = function(buf, len) {
4030
+ return Array.isArray(buf) ? Buffer.concat(buf, len) : buf;
4031
+ };
@@ -5,19 +5,21 @@ var URL_RE = /^([a-z0-9.+-]+:)\/\/([^/?#]*)(\/[^?#]*)?(\?[^#]*)?(#[\s\S]*)?$/i;
5
5
  var HOST_RE = /^(.+)(?::(\d*))$/;
6
6
 
7
7
  module.exports = function (url) {
8
- if (!URL_RE.test(url)) {
8
+ var urlMatch = URL_RE.exec(url);
9
+ if (!urlMatch) {
9
10
  return parseUrl(url);
10
11
  }
11
- var protocol = RegExp.$1;
12
- var host = RegExp.$2;
13
- var pathname = RegExp.$3 || '/';
14
- var search = RegExp.$4;
15
- var hash = RegExp.$5 || null;
12
+ var protocol = urlMatch[1];
13
+ var host = urlMatch[2];
14
+ var pathname = urlMatch[3] || '/';
15
+ var search = urlMatch[4] || '';
16
+ var hash = urlMatch[5] || null;
16
17
  var port = null;
17
18
  var hostname = host;
18
- if (HOST_RE.test(host)) {
19
- hostname = RegExp.$1;
20
- port = RegExp.$2;
19
+ var hostMatch = HOST_RE.exec(host);
20
+ if (hostMatch) {
21
+ hostname = hostMatch[1];
22
+ port = hostMatch[2];
21
23
  }
22
24
 
23
25
  return {
package/lib/util/patch.js CHANGED
@@ -4,6 +4,7 @@ var Socket = require('net').Socket;
4
4
  var http = require('http');
5
5
  var https = require('https');
6
6
  var hparser = require('hparser');
7
+ var common = require('./common');
7
8
 
8
9
  var httpRequest = http.request;
9
10
  var httpsRequest = https.request;
@@ -133,12 +134,7 @@ http.request = function (options) {
133
134
  headers['x-whistle-policy'] = 'intercept';
134
135
  delete headers['X-Whistle-Policy'];
135
136
  }
136
- headers = hparser.getRawHeaders(headers);
137
- var rawData = 'CONNECT ' + tunnelPath + ' HTTP/1.1';
138
- if (headers) {
139
- rawData += '\r\n' + headers;
140
- }
141
- rawData += '\r\n\r\n';
137
+ var rawData = common.getHttpMeta('CONNECT', tunnelPath, 'HTTP/1.1', headers);
142
138
  if (res.statusCode === 200 && res.headers['x-whistle-allow-tunnel-ack']) {
143
139
  rawData = '1' + rawData;
144
140
  }
@@ -1,9 +1,11 @@
1
1
  var Transform = require('pipestream').Transform;
2
- var util = require('util');
2
+ var inherits = require('util').inherits;
3
+ var setDelayTimer = require('./common').setDelayTimer;
3
4
 
4
- function SpeedTransform(options) {
5
+ function SpeedTransform(options, req) {
5
6
  Transform.call(this);
6
7
  options = options || {};
8
+ this._req = req;
7
9
  var value = parseInt((options.speed * 1000) / 8);
8
10
  if (value > 0) {
9
11
  this._speed = value;
@@ -13,13 +15,22 @@ function SpeedTransform(options) {
13
15
  }
14
16
  }
15
17
 
16
- util.inherits(SpeedTransform, Transform);
18
+ inherits(SpeedTransform, Transform);
17
19
 
18
- SpeedTransform.prototype._transform = function (chunk, encoding, callback) {
20
+ var proto = SpeedTransform.prototype;
21
+
22
+ proto._setTimeout = function (callback, delay) {
23
+ var timer = setTimeout(callback, delay);
24
+ if (this._req) {
25
+ setDelayTimer(this._req, timer);
26
+ }
27
+ };
28
+
29
+ proto._transform = function (chunk, encoding, callback) {
19
30
  var self = this;
20
31
  var cb = function () {
21
32
  if (chunk && self._speed) {
22
- setTimeout(function () {
33
+ self._setTimeout(function () {
23
34
  callback(null, chunk);
24
35
  }, Math.round((chunk.length * 1000) / self._speed));
25
36
  } else {
@@ -30,7 +41,7 @@ SpeedTransform.prototype._transform = function (chunk, encoding, callback) {
30
41
  if (self._delay) {
31
42
  var delay = self._delay;
32
43
  self._delay = null;
33
- return setTimeout(cb, delay);
44
+ return self._setTimeout(cb, delay);
34
45
  }
35
46
 
36
47
  cb();