function localtunnel(port, opt, fn) { if (typeof opt === 'function') { fn = opt; opt = {}; } opt = opt || {}; opt.port = port; var client = Tunnel(opt); client.open(function(err) { if (err) { return fn(err); } fn(null, client); }); return client; }
n/a
HeaderHostTransformer = function (opts) { if (!(this instanceof HeaderHostTransformer)) { return new HeaderHostTransformer(opts); } opts = opts || {} Transform.call(this, opts); var self = this; self.host = opts.host || 'localhost'; self.replaced = false; }
n/a
Tunnel = function (opt) { if (!(this instanceof Tunnel)) { return new Tunnel(opt); } var self = this; self._closed = false; self._opt = opt || {}; self._opt.host = self._opt.host || 'https://localtunnel.me'; }
n/a
TunnelCluster = function (opt) { if (!(this instanceof TunnelCluster)) { return new TunnelCluster(opt); } var self = this; self._opt = opt; EventEmitter.call(self); }
n/a
HeaderHostTransformer = function (opts) { if (!(this instanceof HeaderHostTransformer)) { return new HeaderHostTransformer(opts); } opts = opts || {} Transform.call(this, opts); var self = this; self.host = opts.host || 'localhost'; self.replaced = false; }
n/a
function Transform(options) { if (!(this instanceof Transform)) return new Transform(options); Duplex.call(this, options); this._transformState = new TransformState(this); var stream = this; // start out asking for a readable event once data is transformed. this._readableState.needReadable = true; // we have implemented the _read method, and done the other things // that Readable wants before the first _read call, so unset the // sync guard flag. this._readableState.sync = false; if (options) { if (typeof options.transform === 'function') this._transform = options.transform; if (typeof options.flush === 'function') this._flush = options.flush; } // When the writable side finishes, then flush out anything remaining. this.once('prefinish', function() { if (typeof this._flush === 'function') this._flush(function(er) { done(stream, er); }); else done(stream); }); }
n/a
_transform = function (chunk, enc, cb) { var self = this; // after replacing the first instance of the Host header // we just become a regular passthrough if (!self.replaced) { chunk = chunk.toString(); self.push(chunk.replace(/(\r\n[Hh]ost: )\S+/, function(match, $1) { self.replaced = true; return $1 + self.host; })); } else { self.push(chunk); } cb(); }
n/a
Tunnel = function (opt) { if (!(this instanceof Tunnel)) { return new Tunnel(opt); } var self = this; self._closed = false; self._opt = opt || {}; self._opt.host = self._opt.host || 'https://localtunnel.me'; }
n/a
_establish = function (info) { var self = this; var opt = self._opt; // increase max event listeners so that localtunnel consumers don't get // warning messages as soon as they setup even one listener. See #71 self.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10)); info.local_host = opt.local_host; info.local_port = opt.port; var tunnels = self.tunnel_cluster = TunnelCluster(info); // only emit the url the first time tunnels.once('open', function() { self.emit('url', info.url); }); // re-emit socket error tunnels.on('error', function(err) { self.emit('error', err); }); var tunnel_count = 0; // track open count tunnels.on('open', function(tunnel) { tunnel_count++; debug('tunnel open [total: %d]', tunnel_count); var close_handler = function() { tunnel.destroy(); }; if (self._closed) { return close_handler(); } self.once('close', close_handler); tunnel.once('close', function() { self.removeListener('close', close_handler); }); }); // when a tunnel dies, open a new one tunnels.on('dead', function(tunnel) { tunnel_count--; debug('tunnel dead [total: %d]', tunnel_count); if (self._closed) { return; } tunnels.open(); }); // establish as many tunnels as allowed for (var count = 0 ; count < info.max_conn ; ++count) { tunnels.open(); } }
...
self._init(function(err, info) {
if (err) {
return cb(err);
}
self.url = info.url;
self._establish(info);
cb();
});
};
// shutdown tunnels
Tunnel.prototype.close = function() {
var self = this;
...
_init = function (cb) { var self = this; var opt = self._opt; var params = { path: '/', json: true }; var base_uri = opt.host + '/'; // optionally override the upstream server var upstream = url.parse(opt.host); // no subdomain at first, maybe use requested domain var assigned_domain = opt.subdomain; // where to quest params.uri = base_uri + ((assigned_domain) ? assigned_domain : '?new'); (function get_url() { request(params, function(err, res, body) { if (err) { // TODO (shtylman) don't print to stdout? console.log('tunnel server offline: ' + err.message + ', retry 1s'); return setTimeout(get_url, 1000); } if (res.statusCode !== 200) { var err = new Error((body && body.message) || 'localtunnel server returned an error, please try again'); return cb(err); } var port = body.port; var host = upstream.hostname; var max_conn = body.max_conn_count || 1; cb(null, { remote_host: upstream.hostname, remote_port: body.port, name: body.id, url: body.url, max_conn: max_conn }); }); })(); }
...
tunnels.open();
}
};
Tunnel.prototype.open = function(cb) {
var self = this;
self._init(function(err, info) {
if (err) {
return cb(err);
}
self.url = info.url;
self._establish(info);
cb();
...
close = function () { var self = this; self._closed = true; self.emit('close'); }
n/a
open = function (cb) { var self = this; self._init(function(err, info) { if (err) { return cb(err); } self.url = info.url; self._establish(info); cb(); }); }
...
tunnel_count--;
debug('tunnel dead [total: %d]', tunnel_count);
if (self._closed) {
return;
}
tunnels.open();
});
// establish as many tunnels as allowed
for (var count = 0 ; count < info.max_conn ; ++count) {
tunnels.open();
}
};
...
TunnelCluster = function (opt) { if (!(this instanceof TunnelCluster)) { return new TunnelCluster(opt); } var self = this; self._opt = opt; EventEmitter.call(self); }
n/a
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); }); }); } // tunnel is considered open when remote connects remote.once('connect', function() { self.emit('open', remote); conn_local(); }); }
...
tunnel_count--;
debug('tunnel dead [total: %d]', tunnel_count);
if (self._closed) {
return;
}
tunnels.open();
});
// establish as many tunnels as allowed
for (var count = 0 ; count < info.max_conn ; ++count) {
tunnels.open();
}
};
...