Files
localtunnel/client.js
Roman Shtylman b605e9b823 server: make sure client id are released when unused
When clients disconnect, their tcp server should be shutdown and the id
released after a grace period.
2012-11-14 13:53:33 -05:00

138 lines
3.6 KiB
JavaScript

// builtin
var net = require('net');
var url = require('url');
var request = require('request');
var EventEmitter = require('events').EventEmitter;
// request upstream url and connection info
var request_url = function(params, cb) {
request(params, function(err, res, body) {
if (err) {
cb(err);
}
cb(null, body);
});
};
var connect = function(opt) {
var ev = new EventEmitter();
// local port
var local_port = opt.port;
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;
// connect to upstream given connection parameters
var tunnel = function (remote_host, remote_port, max_conn) {
var count = 0;
// open 5 connections to the localtunnel server
// allows for resources to be served faster
for (var count = 0 ; count < max_conn ; ++count) {
var upstream = duplex(remote_host, remote_port, 'localhost', local_port);
upstream.once('end', function() {
// all upstream connections have been closed
if (--count <= 0) {
tunnel(remote_host, remote_port, max_conn);
}
});
upstream.on('error', function(err) {
console.error(err);
});
}
};
var params = {
path: '/',
json: true
};
// where to quest
params.uri = base_uri + ((assigned_domain) ? assigned_domain : '?new');
request_url(params, function(err, body) {
if (err) {
ev.emit('error', new Error('tunnel server not available: %s, retry 1s', err.message));
// retry interval for id request
return setTimeout(function() {
connect_proxy(opt);
}, 1000);
}
// our assigned hostname and tcp port
var port = body.port;
var host = upstream.hostname;
// store the id so we can try to get the same one
assigned_domain = body.id;
tunnel(host, port, body.max_conn_count || 1);
ev.emit('url', body.url);
});
return ev;
};
var duplex = function(remote_host, remote_port, local_host, local_port) {
var ev = new EventEmitter();
// connect to remote tcp server
var upstream = net.createConnection(remote_port, remote_host);
var internal;
// when upstream connection is closed, close other associated connections
upstream.once('end', function() {
ev.emit('error', new Error('upstream connection terminated'));
// sever connection to internal server
// on reconnect we will re-establish
internal.end();
ev.emit('end');
});
upstream.on('error', function(err) {
ev.emit('error', err);
});
(function connect_internal() {
internal = net.createConnection(local_port, local_host);
internal.on('error', function() {
ev.emit('error', new Error('error connecting to local server. retrying in 1s'));
setTimeout(function() {
connect_internal();
}, 1000);
});
internal.on('end', function() {
ev.emit('error', new Error('disconnected from local server. retrying in 1s'));
setTimeout(function() {
connect_internal();
}, 1000);
});
internal.on('connect', function() {
console.log('connected to local server');
});
upstream.pipe(internal).pipe(upstream);
})();
return ev;
}
module.exports.connect = connect;