TunnelCluster.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. var EventEmitter = require('events').EventEmitter;
  2. var debug = require('debug')('localtunnel:client');
  3. var net = require('net');
  4. var HeaderHostTransformer = require('./HeaderHostTransformer');
  5. // manages groups of tunnels
  6. var TunnelCluster = function(opt) {
  7. if (!(this instanceof TunnelCluster)) {
  8. return new TunnelCluster(opt);
  9. }
  10. var self = this;
  11. self._opt = opt;
  12. EventEmitter.call(self);
  13. };
  14. TunnelCluster.prototype.__proto__ = EventEmitter.prototype;
  15. // establish a new tunnel
  16. TunnelCluster.prototype.open = function() {
  17. var self = this;
  18. var opt = self._opt || {};
  19. var remote_host = opt.remote_host;
  20. var remote_port = opt.remote_port;
  21. var local_host = opt.local_host || 'localhost';
  22. var local_port = opt.local_port;
  23. debug('establishing tunnel %s:%s <> %s:%s', local_host, local_port, remote_host, remote_port);
  24. // connection to localtunnel server
  25. var remote = net.connect({
  26. host: remote_host,
  27. port: remote_port
  28. });
  29. remote.setKeepAlive(true);
  30. remote.on('error', function(err) {
  31. // emit connection refused errors immediately, because they
  32. // indicate that the tunnel can't be established.
  33. if (err.code === 'ECONNREFUSED') {
  34. self.emit('error', new Error('connection refused: ' + remote_host + ':' + remote_port + ' (check your firewall settings)'));
  35. }
  36. remote.end();
  37. });
  38. function conn_local() {
  39. if (remote.destroyed) {
  40. debug('remote destroyed');
  41. self.emit('dead');
  42. return;
  43. }
  44. debug('connecting locally to %s:%d', local_host, local_port);
  45. remote.pause();
  46. // connection to local http server
  47. var local = net.connect({
  48. host: local_host,
  49. port: local_port
  50. });
  51. function remote_close() {
  52. debug('remote close');
  53. self.emit('dead');
  54. local.end();
  55. };
  56. remote.once('close', remote_close);
  57. // TODO some languages have single threaded servers which makes opening up
  58. // multiple local connections impossible. We need a smarter way to scale
  59. // and adjust for such instances to avoid beating on the door of the server
  60. local.once('error', function(err) {
  61. debug('local error %s', err.message);
  62. local.end();
  63. remote.removeListener('close', remote_close);
  64. if (err.code !== 'ECONNREFUSED') {
  65. return remote.end();
  66. }
  67. // retrying connection to local server
  68. setTimeout(conn_local, 1000);
  69. });
  70. local.once('connect', function() {
  71. debug('connected locally');
  72. remote.resume();
  73. var stream = remote;
  74. // if user requested specific local host
  75. // then we use host header transform to replace the host header
  76. if (opt.local_host) {
  77. debug('transform Host header to %s', opt.local_host);
  78. stream = remote.pipe(HeaderHostTransformer({ host: opt.local_host }));
  79. }
  80. stream.pipe(local).pipe(remote);
  81. // when local closes, also get a new remote
  82. local.once('close', function(had_error) {
  83. debug('local connection closed [%s]', had_error);
  84. });
  85. });
  86. }
  87. remote.on('data', function(data) {
  88. const match = data.toString().match(/^(\w+) (\S+)/);
  89. if (match) {
  90. self.emit('request', {
  91. method: match[1],
  92. path: match[2],
  93. });
  94. }
  95. });
  96. // tunnel is considered open when remote connects
  97. remote.once('connect', function() {
  98. self.emit('open', remote);
  99. conn_local();
  100. });
  101. };
  102. module.exports = TunnelCluster;