work.suroh.tk/node_modules/localtunnel/lib/TunnelCluster.js

134 lines
3.8 KiB
JavaScript
Raw Normal View History

2019-12-02 12:22:45 +00:00
var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('localtunnel:client');
var net = require('net');
var HeaderHostTransformer = require('./HeaderHostTransformer');
// manages groups of tunnels
var TunnelCluster = function(opt) {
if (!(this instanceof TunnelCluster)) {
return new TunnelCluster(opt);
}
var self = this;
self._opt = opt;
EventEmitter.call(self);
};
TunnelCluster.prototype.__proto__ = EventEmitter.prototype;
// establish a new tunnel
TunnelCluster.prototype.open = function() {
var self = this;
var opt = self._opt || {};
var remote_host = opt.remote_host;
var remote_port = opt.remote_port;
var local_host = opt.local_host || 'localhost';
var local_port = opt.local_port;
debug('establishing tunnel %s:%s <> %s:%s', local_host, local_port, remote_host, remote_port);
// connection to localtunnel server
var remote = net.connect({
host: remote_host,
port: remote_port
});
remote.setKeepAlive(true);
remote.on('error', function(err) {
// emit connection refused errors immediately, because they
// indicate that the tunnel can't be established.
if (err.code === 'ECONNREFUSED') {
self.emit('error', new Error('connection refused: ' + remote_host + ':' + remote_port + ' (check your firewall settings)'));
}
remote.end();
});
function conn_local() {
if (remote.destroyed) {
debug('remote destroyed');
self.emit('dead');
return;
}
debug('connecting locally to %s:%d', local_host, local_port);
remote.pause();
// connection to local http server
var local = net.connect({
host: local_host,
port: local_port
});
function remote_close() {
debug('remote close');
self.emit('dead');
local.end();
};
remote.once('close', remote_close);
// TODO some languages have single threaded servers which makes opening up
// multiple local connections impossible. We need a smarter way to scale
// and adjust for such instances to avoid beating on the door of the server
local.once('error', function(err) {
debug('local error %s', err.message);
local.end();
remote.removeListener('close', remote_close);
if (err.code !== 'ECONNREFUSED') {
return remote.end();
}
// retrying connection to local server
setTimeout(conn_local, 1000);
});
local.once('connect', function() {
debug('connected locally');
remote.resume();
var stream = remote;
// if user requested specific local host
// then we use host header transform to replace the host header
if (opt.local_host) {
debug('transform Host header to %s', opt.local_host);
stream = remote.pipe(HeaderHostTransformer({ host: opt.local_host }));
}
stream.pipe(local).pipe(remote);
// when local closes, also get a new remote
local.once('close', function(had_error) {
debug('local connection closed [%s]', had_error);
});
});
}
remote.on('data', function(data) {
const match = data.toString().match(/^(\w+) (\S+)/);
if (match) {
self.emit('request', {
method: match[1],
path: match[2],
});
}
});
// tunnel is considered open when remote connects
remote.once('connect', function() {
self.emit('open', remote);
conn_local();
});
};
module.exports = TunnelCluster;