23 Commits

Author SHA1 Message Date
Roman Shtylman
006ce7733b v1.4.0 2014-08-31 22:59:17 -07:00
Roman Shtylman
624d279c26 History: update 2014-08-31 22:58:43 -07:00
Roman Shtylman
c2b8f2b7ab Merge pull request #57 from kesla/handle-etimedout
don't emit ETIMEDOUT-errors
2014-08-31 22:57:30 -07:00
David Björklund
4d9dcc1711 don't emit ETIMEDOUT-errors 2014-08-28 20:12:09 +02:00
Roman Shtylman
7b1fef982f 1.3.0 2014-05-13 20:33:41 -04:00
Roman Shtylman
b1ebef2b0b fix code style 2014-05-13 20:32:43 -04:00
Tymoteusz Paszun
9ad43778b7 fixed Host header replacement - remembers if header is already replaced and does not remove _transform method 2014-05-13 09:24:08 +02:00
Tymoteusz Paszun
887c444543 added test for chunked requests 2014-05-13 09:16:50 +02:00
Tymoteusz Paszun
828cb2afcb transform Host header when local-host is defined explicitly 2014-05-09 15:07:38 +02:00
Roman Shtylman
8768329fdd update history with two recent minor versions 2014-04-28 19:03:33 -04:00
Roman Shtylman
3026d6a42c 1.2.0 2014-04-28 19:00:12 -04:00
Roman Shtylman
abd461f83a Merge pull request #47 from dscape/patch-1
return client from 'localtunnel' api call
2014-04-28 18:58:54 -04:00
Nuno Job
2acea3d77f Return client
This allows manipulating the client from outside. Allowing, for example, to close a connection.
2014-04-28 23:32:00 +01:00
Roman Shtylman
5d0eb3382a add --version CLI flag to get version info 2014-04-22 19:55:19 -04:00
Roman Shtylman
3b67c8a8ce 1.1.2 2014-04-20 10:51:24 -04:00
Kevin Ingersoll
71552a336e Increase default Mocha timeout 2014-04-20 10:45:21 -04:00
Roman Shtylman
87a23bf28c fix status code check for url request 2014-04-19 19:33:52 -04:00
Roman Shtylman
3d54de851f handle errors from localtunnel server when requesting initial url 2014-04-19 19:30:44 -04:00
Roman Shtylman
92bb807908 fix typo
fixes #45
2014-04-18 09:34:48 -04:00
Roman Shtylman
afbdc3697e 1.1.1 2014-04-15 09:33:34 -04:00
Roman Shtylman
0049f21b55 Merge pull request #38 from LinusU/patch-1
re-throw client errors from bin/lt to let node handle it
2014-04-14 19:27:56 -04:00
Roman Shtylman
509841104b fix for RangeError stack size exceeded
This error would happen when there was a problem connecting to the local
server. The local.on('error') handler should have been a 'once' handler
because we emit the error again if it isn't a CONNREFUSED. So in the
case of a CONNRESET, it would trigger an infinite loop since the error
was being emitted back onto the local variable. Instead we just close
the remote socket and let a new one takes its place.

fixes #36
2014-04-14 15:36:42 -04:00
Linus Unnebäck
92caf2f204 lt: better error handling
Let node handle the displaying of error and setting exit code.
2014-04-14 21:15:11 +02:00
5 changed files with 142 additions and 26 deletions

View File

@@ -1,3 +1,15 @@
# 1.4.0 (2014-08-31)
* don't emit errors for ETIMEDOUT
# 1.2.0 / 2014-04-28
* return `client` from `localtunnel` API instantiation
# 1.1.0 / 2014-02-24
* add a host header transform to change the 'Host' header in requests
# 1.0.0 / 2014-02-14
* default to localltunnel.me for host

View File

@@ -3,7 +3,6 @@ var lt_client = require('../client');
var argv = require('optimist')
.usage('Usage: $0 --port [num]')
.demand(['port'])
.options('host', {
default: 'http://localtunnel.me',
describe: 'upstream server providing forwarding'
@@ -12,29 +11,41 @@ var argv = require('optimist')
describe: 'request this subdomain'
})
.options('local-host', {
describe: 'tunnel traffic to this host instead of localhost'
describe: 'tunnel traffic to this host instead of localhost, override Host header to this host'
})
.options('version', {
describe: 'print version and exit'
})
.default('local-host', 'localhost')
.describe('port', 'internal http server port')
.argv;
if (argv.version) {
console.log(require('../package.json').version);
process.exit(0);
}
if (argv.port == null) {
require('optimist').showHelp();
console.error('Missing required arguments: port');
process.exit(1);
}
var opt = {
host: argv.host,
port: argv.port,
local_host: argv['local-host'],
subdomain: argv.subdomain,
}
};
lt_client(opt.port, opt, function(err, tunnel) {
if (err) {
console.error(err);
return process.exit(1);
throw err;
}
console.log('your url is: %s', tunnel.url);
tunnel.on('error', function(err) {
console.error(err);
throw err;
});
});

View File

@@ -30,10 +30,16 @@ HeaderHostTransformer.prototype._transform = function (chunk, enc, cb) {
// after replacing the first instance of the Host header
// we just become a regular passthrough
self.push(chunk.replace(/(\r\nHost: )\S+/, function(match, $1) {
self._transform = undefined;
return $1 + self.host;
}));
if (!self.replaced) {
self.push(chunk.replace(/(\r\nHost: )\S+/, function(match, $1) {
self.replaced = true;
return $1 + self.host;
}));
}
else {
self.push(chunk);
}
cb();
};
@@ -60,7 +66,7 @@ TunnelCluster.prototype.open = function() {
var remote_host = opt.remote_host;
var remote_port = opt.remote_port;
var local_host = opt.local_host;
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);
@@ -77,7 +83,7 @@ TunnelCluster.prototype.open = function() {
if (err.code === 'ECONNREFUSED') {
self.emit('error', new Error('connection refused: ' + remote_host + ':' + remote_port + ' (check your firewall settings)'));
}
else {
else if (err.code !== 'ETIMEDOUT') {
self.emit('error', err);
}
@@ -87,13 +93,13 @@ TunnelCluster.prototype.open = function() {
});
function conn_local() {
debug('connecting locally to %s:%d', local_host, local_port);
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
@@ -103,19 +109,24 @@ TunnelCluster.prototype.open = function() {
});
function remote_close() {
debug('remote close');
self.emit('dead');
local.end();
};
remote.once('close', remote_close);
local.on('error', function(err) {
// 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 local.emit('error', err);
return remote.end();
}
// retrying connection to local server
@@ -128,10 +139,11 @@ TunnelCluster.prototype.open = function() {
var stream = remote;
// if user requested something other than localhost
// if user requested specific local host
// then we use host header transform to replace the host header
if (local_host !== 'localhost') {
stream = remote.pipe(HeaderHostTransformer({ host: local_host }));
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);
@@ -146,8 +158,8 @@ TunnelCluster.prototype.open = function() {
// tunnel is considered open when remote connects
remote.once('connect', function() {
self.emit('open', remote);
conn_local();
});
remote.once('connect', conn_local);
};
var Tunnel = function(opt) {
@@ -194,6 +206,11 @@ Tunnel.prototype._init = function(cb) {
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;
@@ -214,7 +231,7 @@ Tunnel.prototype._establish = function(info) {
var self = this;
var opt = self._opt;
info.local_host = opt.local_host || 'localhost';
info.local_host = opt.local_host;
info.local_port = opt.port;
var tunnels = self.tunnel_cluster = TunnelCluster(info);
@@ -302,4 +319,5 @@ module.exports = function localtunnel(port, opt, fn) {
fn(null, client);
});
return client;
};

View File

@@ -2,7 +2,7 @@
"author": "Roman Shtylman <shtylman@gmail.com>",
"name": "localtunnel",
"description": "expose localhost to the world",
"version": "1.1.0",
"version": "1.4.0",
"repository": {
"type": "git",
"url": "git://github.com/shtylman/localtunnel.git"
@@ -16,10 +16,10 @@
"mocha": "~1.17.0"
},
"scripts": {
"test": "mocha --ui qunit --reporter list -- test/index.js"
"test": "mocha --ui qunit --reporter list --timeout 10000 -- test/index.js"
},
"bin": {
"lt": "./bin/client"
},
"main": "./client.js"
}
}

View File

@@ -68,7 +68,51 @@ test('request specific domain', function(done) {
});
});
suite('local-host');
suite('--local-host localhost');
test('setup localtunnel client', function(done) {
var opt = {
local_host: 'localhost'
};
localtunnel(test._fake_port, opt, function(err, tunnel) {
assert.ifError(err);
assert.ok(new RegExp('^https:\/\/.*localtunnel.me' + '$').test(tunnel.url));
test._fake_url = tunnel.url;
done();
});
});
test('override Host header with local-host', function(done) {
var uri = test._fake_url;
var parsed = url.parse(uri);
var opt = {
host: parsed.host,
port: 443,
headers: {
host: parsed.hostname
},
path: '/'
};
var req = https.request(opt, function(res) {
res.setEncoding('utf8');
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
assert.equal(body, 'localhost');
done();
});
});
req.end();
});
suite('--local-host 127.0.0.1');
test('setup localtunnel client', function(done) {
var opt = {
@@ -111,3 +155,34 @@ test('override Host header with local-host', function(done) {
req.end();
});
test('send chunked request', function(done) {
var uri = test._fake_url;
var parsed = url.parse(uri);
var opt = {
host: parsed.host,
port: 443,
headers: {
host: parsed.hostname,
'Transfer-Encoding': 'chunked'
},
path: '/'
};
var req = https.request(opt, function(res) {
res.setEncoding('utf8');
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
assert.equal(body, '127.0.0.1');
done();
});
});
req.end(require('crypto').randomBytes(1024 * 8).toString('base64'));
});