Sub host options: ala whitecolor #82

Closed
gojisystems wants to merge 7 commits from master into master
10 changed files with 2415 additions and 19 deletions
+5
View File
@@ -2,3 +2,8 @@ language: node_js
sudo: false
node_js:
- "9.2"
addons:
hosts:
- host
- sub.host
- nested.sub.host
+28 -5
View File
@@ -1,12 +1,12 @@
# localtunnel-server
[![Build Status](https://travis-ci.org/localtunnel/server.svg?branch=master)](https://travis-ci.org/localtunnel/server)
[![Build Status](https://travis-ci.org/gojisystems/gojiconnect-server.svg?branch=master)](https://travis-ci.org/gojisystems/gojiconnect-server)
defunctzombie commented 2018-04-02 02:31:36 +00:00 (Migrated from github.com)
Review

When making a PR please don't alter files in this way as such a change can't be merged into this repo as-is. This is great for your repo but for the PR it should be removed.

When making a PR please don't alter files in this way as such a change can't be merged into this repo as-is. This is great for your repo but for the PR it should be removed.
localtunnel exposes your localhost to the world for easy testing and sharing! No need to mess with DNS or deploy just to have others test out your changes.
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,29 @@ 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
**Note** that to use sub-hosts you should pass `--sub-host` option during set up. Pass this argument for every sub-host level.
Alternatively, `--sub-host` may be passed in as a single argument with the all sub hosts in a string `"nested.sub"`
```shell
# server set to run on port 1234 w/ a sub host to allow for this URL to work "http://sub.example.com"
bin/server --port 1234 \
--sub-host sub
```
```shell
# server set to run on port 1234 w/ a sub host to allow for this URL to work "http://nested.sub.example.com"
bin/server --port 1234 \
--sub-host nested \
--sub-host sub
```
**Alternative**
```shell
# server set to run on port 1234 w/ a sub host to allow for this URL to work "http://nested.sub.example.com"
bin/server --port 1234 \
--sub-host "nested.sub"
```
## use your server
You can now use your domain with the `--host` flag for the `lt` client.
@@ -41,6 +63,7 @@ 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
You can deploy your own localtunnel server using the prebuilt docker image.
@@ -54,5 +77,5 @@ docker run -d \
--restart always \
--name localtunnel \
--net host \
defunctzombie/localtunnel-server:latest --port 3000
defunctzombie/localtunnel-server:latest --port 3000 --sub-host sub
```
+7 -1
View File
@@ -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, () => {
+2134
View File
File diff suppressed because it is too large Load Diff
+24 -9
View File
@@ -10,11 +10,27 @@ import rand_id from './lib/rand_id';
const debug = Debug('localtunnel:server');
function GetClientIdFromHostname(hostname) {
return tldjs.getSubdomain(hostname);
function GetClientIdFromHostname(hostname,opt) {
let subdomain = tldjs.getSubdomain(hostname);
if (subdomain && opt.subHost) {
const _subdomain = subdomain.replace(/\./g,'');
const subHosts = Array.isArray(opt.subHost)
? opt.subHost : [opt.subHost]
const subHost = subHosts.reduce((found, sub) => {
sub = sub.replace(/\./g,'');
return found + (_subdomain.includes(sub) ? sub : sub.includes(_subdomain) ? _subdomain : '');
},'')
subdomain = subHost.length > 0
? _subdomain.slice(0, -(subHost.length))
: subdomain
}
return subdomain;
}
module.exports = function(opt) {
opt = opt || {};
const manager = new ClientManager(opt);
@@ -42,7 +58,6 @@ module.exports = function(opt) {
// root endpoint
app.use(async (ctx, next) => {
const path = ctx.request.path;
// skip anything not on the root path
if (path !== '/') {
await next();
@@ -54,7 +69,6 @@ module.exports = function(opt) {
const req_id = rand_id();
debug('making new client with id %s', req_id);
const info = await manager.newClient(req_id);
const url = schema + '://' + info.id + '.' + ctx.request.host;
info.url = url;
ctx.body = info;
@@ -81,7 +95,7 @@ module.exports = function(opt) {
const req_id = parts[1];
// 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 msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.';
ctx.status = 403;
ctx.body = {
@@ -110,8 +124,9 @@ module.exports = function(opt) {
res.end('Host header is required');
return;
}
const clientId = GetClientIdFromHostname(hostname,opt);
const clientId = GetClientIdFromHostname(hostname);
if (!clientId) {
appCallback(req, res);
return;
@@ -129,13 +144,13 @@ module.exports = function(opt) {
server.on('upgrade', (req, socket, head) => {
const hostname = req.headers.host;
if (!hostname) {
sock.destroy();
socket.destroy();
return;
}
const clientId = GetClientIdFromHostname(hostname);
const clientId = GetClientIdFromHostname(hostname,opt);
if (!clientId) {
sock.destroy();
socket.destroy();
return;
}
+68
View File
@@ -0,0 +1,68 @@
var http = require('http');
var url = require('url');
var assert = require('assert');
var localtunnel = require('localtunnel');
var localtunnel_server = require('../server')({
max_tcp_sockets: 2,
subHost: ['nested','sub']
});
var lt_server_port
suite('nested-sub');
test('set up localtunnel server', function(done) {
var server = localtunnel_server.listen(function() {
lt_server_port = server.address().port;
done();
});
});
test('set up local http server', function(done) {
var server = http.createServer(function(req, res) {
res.end('hello world!');
});
server.listen(function() {
test._fake_port = server.address().port;
done();
});
});
test('set up localtunnel client (sub-host)', function (done) {
var opt = {
host: 'http://nested.sub.host:' + lt_server_port,
subdomain: 'txy-z'
};
localtunnel(test._fake_port, opt, function (err, tunnel) {
assert.ifError(err);
var url = tunnel.url;
assert.ok(new RegExp('^http:\/\/.*nested.sub.host:' + lt_server_port + '$').test(url));
test._fake_url = url;
done(err);
});
});
test('should respond to request (sub-host)', function (done) {
var hostname = url.parse(test._fake_url).hostname;
var opt = {
host: 'nested.sub.host',
port: lt_server_port,
headers: {
host: hostname + '.tld'
}
};
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();
});
});
});
+68
View File
@@ -0,0 +1,68 @@
var http = require('http');
var url = require('url');
var assert = require('assert');
var localtunnel = require('localtunnel');
var localtunnel_server = require('../server')({
max_tcp_sockets: 2,
subHost: ['nested.sub']
});
var lt_server_port
suite('nested.sub');
test('set up localtunnel server', function(done) {
var server = localtunnel_server.listen(function() {
lt_server_port = server.address().port;
done();
});
});
test('set up local http server', function(done) {
var server = http.createServer(function(req, res) {
res.end('hello world!');
});
server.listen(function() {
test._fake_port = server.address().port;
done();
});
});
test('set up localtunnel client (sub-host)', function (done) {
var opt = {
host: 'http://nested.sub.host:' + lt_server_port,
subdomain: 'txy-z'
};
localtunnel(test._fake_port, opt, function (err, tunnel) {
assert.ifError(err);
var url = tunnel.url;
assert.ok(new RegExp('^http:\/\/.*nested.sub.host:' + lt_server_port + '$').test(url));
test._fake_url = url;
done(err);
});
});
test('should respond to request (sub-host)', function (done) {
var hostname = url.parse(test._fake_url).hostname;
var opt = {
host: 'nested.sub.host',
port: lt_server_port,
headers: {
host: hostname + '.tld'
}
};
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();
});
});
});
+4 -2
View File
@@ -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
@@ -32,6 +33,7 @@ test('set up local http server', function(done) {
test('set up localtunnel client', function(done) {
var opt = {
host: 'http://localhost:' + lt_server_port,
subdomain: 'txyz'
};
localtunnel(test._fake_port, opt, function(err, tunnel) {
@@ -65,4 +67,4 @@ test('should respond to request', function(done) {
done();
});
});
});
});
+3 -2
View File
@@ -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: ['sub']
});
var lt_server_port
@@ -71,4 +72,4 @@ test('websocket server request', function(done) {
ws.on('open', function open() {
ws.send('something');
});
});
});
+74
View File
@@ -0,0 +1,74 @@
var http = require('http');
var url = require('url');
var assert = require('assert');
var localtunnel = require('localtunnel');
var WebSocket = require('ws');
var WebSocketServer = require('ws').Server;
var localtunnel_server = require('../server')({
max_tcp_sockets: 2,
subHost: ['sub']
});
var lt_server_port
suite('websocket.sub');
before('set up localtunnel server', function(done) {
var server = localtunnel_server.listen(function() {
lt_server_port = server.address().port;
done();
});
});
before('set up local websocket server', function(done) {
var wss = new WebSocketServer({ port: 0 }, function() {
test._fake_port = wss._server.address().port;
done();
});
wss.on('error', function(err) {
done(err);
});
wss.on('connection', function connection(ws) {
ws.on('error', function(err) {
done(err);
});
ws.on('message', function incoming(message) {
ws.send(message);
});
});
});
test('set up localtunnel client (sub host)', function (done) {
var opt = {
host: 'http://sub.host:' + 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:' + lt_server_port + '$').test(url));
test._fake_url = url;
done(err);
});
});
test('websocket server request (sub-host)', function (done) {
var hostname = url.parse(test._fake_url).hostname;
var ws = new WebSocket('http://sub.host:' + lt_server_port, {
headers: {
host: hostname + '.tld'
}
});
ws.on('message', function (msg) {
assert.equal(msg, 'something');
done();
});
ws.on('open', function open() {
ws.send('something');
});
});