From 5bc44fb901e069f05b72a16277e4c48e4794eab9 Mon Sep 17 00:00:00 2001 From: whitecolor Date: Wed, 7 Jun 2017 15:05:05 +0500 Subject: [PATCH 1/3] add --sub-host option --- README.md | 14 +++++++++----- bin/server | 8 +++++++- server.js | 36 +++++++++++++++++++++++------------- test/simple.js | 37 ++++++++++++++++++++++++++++++++++++- test/websocket.js | 30 +++++++++++++++++++++++++++++- 5 files changed, 104 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c557c94..ad5338c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ localtunnel exposes your localhost to the world for easy testing and sharing! No This repo is the server component. If you are just looking for the CLI localtunnel app, see (https://github.com/localtunnel/localtunnel). -## overview ## +## overview The default localtunnel client connects to the `localtunnel.me` server. You can, however, easily set up and run your own server. In order to run your own localtunnel server you must ensure that your server can meet the following requirements: @@ -15,7 +15,7 @@ The default localtunnel client connects to the `localtunnel.me` server. You can, The above are important as the client will ask the server for a subdomain under a particular domain. The server will listen on any OS-assigned TCP port for client connections. -#### setup +## setup ```shell # pick a place where the files will live @@ -29,7 +29,7 @@ bin/server --port 1234 The localtunnel server is now running and waiting for client requests on port 1234. You will most likely want to set up a reverse proxy to listen on port 80 (or start localtunnel on port 80 directly). -#### use your server +## use your server You can now use your domain with the `--host` flag for the `lt` client. @@ -41,7 +41,9 @@ You will be assigned a URL similar to `qdci.sub.example.com:1234`. If your server is acting as a reverse proxy (i.e. nginx) and is able to listen on port 80, then you do not need the `:1234` part of the hostname for the `lt` client. -## Deploy +Not that to use sub-hosts you should pass `--sub-host` option to server + +## deploy You can deploy your own localtunnel server using the prebuilt docker image. @@ -54,5 +56,7 @@ docker run -d \ --restart always \ --name localtunnel \ --net host \ - defunctzombie/localtunnel-server:latest --port 3000 + defunctzombie/localtunnel-server:latest --port 3000 --sub-host sub ``` + + diff --git a/bin/server b/bin/server index e352884..70fb628 100755 --- a/bin/server +++ b/bin/server @@ -17,6 +17,10 @@ const argv = optimist default: '80', describe: 'listen on this port for outside requests' }) + .options('sub-host', { + describe: 'additionally listen on these sub hosts for outside requests', + type: 'array' + }) .options('max-sockets', { default: 10, describe: 'maximum number of tcp sockets each client is allowed to establish at one time (the tunnels)' @@ -30,7 +34,9 @@ if (argv.help) { const server = require('../server')({ max_tcp_sockets: argv['max-sockets'], - secure: argv.secure + secure: argv.secure, + port: argv.port, + subHost: argv['sub-host'] }); server.listen(argv.port, () => { diff --git a/server.js b/server.js index e812b00..c3be355 100644 --- a/server.js +++ b/server.js @@ -40,22 +40,33 @@ const stats = { // handle proxying a request to a client // will wait for a tunnel socket to become available -function maybe_bounce(req, res, sock, head) { +function maybe_bounce(req, res, sock, head, opt) { // without a hostname, we won't know who the request is for const hostname = req.headers.host; if (!hostname) { return false; } - const subdomain = tldjs.getSubdomain(hostname); + let subdomain = tldjs.getSubdomain(hostname); + + if (subdomain && opt.subHost) { + const subHost = opt.subHost.reduce((found, sub) => { + return found + || (subdomain.slice(-sub.length) == sub ? sub : '') + }, '') + subdomain = subHost + ? subdomain.slice(0, -(subHost.length + 1)) + : subdomain + } + if (!subdomain) { return false; } - const client = clients[subdomain]; + const client = clients[subdomain]; // no such subdomain - // we use 502 error to the client to signify we can't service the request + // we use 502 error to the client to signify we can't service the request if (!client) { if (res) { res.statusCode = 502; @@ -68,7 +79,7 @@ function maybe_bounce(req, res, sock, head) { return true; } - + let finished = false; if (sock) { sock.once('end', function() { @@ -233,7 +244,7 @@ module.exports = function(opt) { if (req.query['new'] === undefined) { return next(); } - + const req_id = rand_id(); debug('making new client with id %s', req_id); new_client(req_id, opt, function(err, info) { @@ -271,20 +282,19 @@ module.exports = function(opt) { app.get('/:req_id', function(req, res, next) { const req_id = req.params.req_id; - // limit requested hostnames to 63 characters - if (! /^[a-z0-9]{4,63}$/.test(req_id)) { + if (! /^[a-z0-9-]{4,63}$/.test(req_id)) { const err = new Error('Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); err.statusCode = 403; return next(err); } - + debug('making new client with id %s', req_id); - new_client(req_id, opt, function(err, info) { + new_client(req_id, opt, function(err, info) { if (err) { return next(err); } - + const url = schema + '://' + req_id + '.' + req.headers.host; info.url = url; res.json(info); @@ -312,7 +322,7 @@ module.exports = function(opt) { }); debug('request %s', req.url); - if (maybe_bounce(req, res, null, null)) { + if (maybe_bounce(req, res, null, null, opt)) { return; }; @@ -328,7 +338,7 @@ module.exports = function(opt) { console.error('ws socket', err); }); - if (maybe_bounce(req, null, socket, head)) { + if (maybe_bounce(req, null, socket, head, opt)) { return; }; diff --git a/test/simple.js b/test/simple.js index 5dab5e2..1ec2d13 100644 --- a/test/simple.js +++ b/test/simple.js @@ -4,7 +4,8 @@ var assert = require('assert'); var localtunnel = require('localtunnel'); var localtunnel_server = require('../server')({ - max_tcp_sockets: 2 + max_tcp_sockets: 2, + subHost: ['nested.sub'] }); var lt_server_port @@ -66,3 +67,37 @@ test('should respond to request', function(done) { }); }); }); + +test('set up localtunnel client (sub-host)', function (done) { + var opt = { + host: 'http://nested.sub.host.dev:' + lt_server_port, + subdomain: 'txyz' + }; + + localtunnel(test._fake_port, opt, function (err, tunnel) { + assert.ifError(err); + var url = tunnel.url; + assert.ok(new RegExp('^http:\/\/.*nested.sub.host.dev:' + lt_server_port + '$').test(url)); + test._fake_url = url; + done(err); + }); +}); + +test('should respond to request (sub-host)', function (done) { + var opt = { + host: 'txyz.nested.sub.host.dev', + port: lt_server_port, + }; + http.get(opt, function (res) { + var body = ''; + res.setEncoding('utf-8'); + res.on('data', function (chunk) { + body += chunk; + }); + + res.on('end', function () { + assert.equal(body, 'hello world!'); + done(); + }); + }); +}); diff --git a/test/websocket.js b/test/websocket.js index 2d70f72..35a3e51 100644 --- a/test/websocket.js +++ b/test/websocket.js @@ -6,7 +6,8 @@ var WebSocket = require('ws'); var WebSocketServer = require('ws').Server; var localtunnel_server = require('../server')({ - max_tcp_sockets: 2 + max_tcp_sockets: 2, + subHost: ['nested.sub', 'sub'] }); var lt_server_port @@ -72,3 +73,30 @@ test('websocket server request', function(done) { ws.send('something'); }); }); + +test('set up localtunnel client (sub host)', function (done) { + var opt = { + host: 'http://sub.host.dev:' + lt_server_port + }; + + localtunnel(test._fake_port, opt, function (err, tunnel) { + assert.ifError(err); + var url = tunnel.url; + assert.ok(new RegExp('^http:\/\/.*sub.host.dev:' + lt_server_port + '$').test(url)); + test._fake_url = url; + done(err); + }); +}); + +test('websocket server request (sub-host)', function (done) { + var ws = new WebSocket(test._fake_url); + + ws.on('message', function (msg) { + assert.equal(msg, 'something'); + done(); + }); + + ws.on('open', function open() { + ws.send('something'); + }); +}); \ No newline at end of file -- 2.54.0 From 830476db193c7b3b40ef6c9301d1db60de534556 Mon Sep 17 00:00:00 2001 From: whitecolor Date: Wed, 7 Jun 2017 17:02:51 +0500 Subject: [PATCH 2/3] fix sub-host cli handling --- bin/server | 3 +-- server.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/server b/bin/server index 70fb628..9e07f27 100755 --- a/bin/server +++ b/bin/server @@ -18,8 +18,7 @@ const argv = optimist describe: 'listen on this port for outside requests' }) .options('sub-host', { - describe: 'additionally listen on these sub hosts for outside requests', - type: 'array' + describe: 'additionally listen on this sub host for outside requests (can use this option multiple times)' }) .options('max-sockets', { default: 10, diff --git a/server.js b/server.js index c3be355..c046196 100644 --- a/server.js +++ b/server.js @@ -50,6 +50,8 @@ function maybe_bounce(req, res, sock, head, opt) { let subdomain = tldjs.getSubdomain(hostname); if (subdomain && opt.subHost) { + const subHosts = Array.isArray(opt.subHost) + ? opt.subHost : [opt.subHost] const subHost = opt.subHost.reduce((found, sub) => { return found || (subdomain.slice(-sub.length) == sub ? sub : '') -- 2.54.0 From 6fc1fa0bf6288ff93ac2ec4b4a603333478202cf Mon Sep 17 00:00:00 2001 From: whitecolor Date: Wed, 7 Jun 2017 17:26:59 +0500 Subject: [PATCH 3/3] fix sub-host cli again --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index c046196..a79a376 100644 --- a/server.js +++ b/server.js @@ -51,8 +51,8 @@ function maybe_bounce(req, res, sock, head, opt) { if (subdomain && opt.subHost) { const subHosts = Array.isArray(opt.subHost) - ? opt.subHost : [opt.subHost] - const subHost = opt.subHost.reduce((found, sub) => { + ? opt.subHost : [opt.subHost] + const subHost = subHosts.reduce((found, sub) => { return found || (subdomain.slice(-sub.length) == sub ? sub : '') }, '') -- 2.54.0