Sub host options: ala whitecolor #82
@@ -2,3 +2,8 @@ language: node_js
|
|||||||
sudo: false
|
sudo: false
|
||||||
node_js:
|
node_js:
|
||||||
- "9.2"
|
- "9.2"
|
||||||
|
addons:
|
||||||
|
hosts:
|
||||||
|
- host
|
||||||
|
- sub.host
|
||||||
|
- nested.sub.host
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# localtunnel-server
|
# localtunnel-server
|
||||||
|
|
||||||
[](https://travis-ci.org/localtunnel/server)
|
[](https://travis-ci.org/gojisystems/gojiconnect-server)
|
||||||
|
|
||||||
|
|
|||||||
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.
|
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).
|
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:
|
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.
|
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
|
```shell
|
||||||
# pick a place where the files will live
|
# 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).
|
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.
|
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.
|
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
|
## Deploy
|
||||||
|
|
||||||
You can deploy your own localtunnel server using the prebuilt docker image.
|
You can deploy your own localtunnel server using the prebuilt docker image.
|
||||||
@@ -54,5 +77,5 @@ docker run -d \
|
|||||||
--restart always \
|
--restart always \
|
||||||
--name localtunnel \
|
--name localtunnel \
|
||||||
--net host \
|
--net host \
|
||||||
defunctzombie/localtunnel-server:latest --port 3000
|
defunctzombie/localtunnel-server:latest --port 3000 --sub-host sub
|
||||||
```
|
```
|
||||||
|
|||||||
+7
-1
@@ -17,6 +17,10 @@ const argv = optimist
|
|||||||
default: '80',
|
default: '80',
|
||||||
describe: 'listen on this port for outside requests'
|
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', {
|
.options('max-sockets', {
|
||||||
default: 10,
|
default: 10,
|
||||||
describe: 'maximum number of tcp sockets each client is allowed to establish at one time (the tunnels)'
|
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')({
|
const server = require('../server')({
|
||||||
max_tcp_sockets: argv['max-sockets'],
|
max_tcp_sockets: argv['max-sockets'],
|
||||||
secure: argv.secure
|
secure: argv.secure,
|
||||||
|
port: argv.port,
|
||||||
|
subHost: argv['sub-host']
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(argv.port, () => {
|
server.listen(argv.port, () => {
|
||||||
|
|||||||
Generated
+2134
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,27 @@ import rand_id from './lib/rand_id';
|
|||||||
|
|
||||||
const debug = Debug('localtunnel:server');
|
const debug = Debug('localtunnel:server');
|
||||||
|
|
||||||
function GetClientIdFromHostname(hostname) {
|
function GetClientIdFromHostname(hostname,opt) {
|
||||||
return tldjs.getSubdomain(hostname);
|
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) {
|
module.exports = function(opt) {
|
||||||
|
|
||||||
opt = opt || {};
|
opt = opt || {};
|
||||||
|
|
||||||
const manager = new ClientManager(opt);
|
const manager = new ClientManager(opt);
|
||||||
@@ -42,7 +58,6 @@ module.exports = function(opt) {
|
|||||||
// root endpoint
|
// root endpoint
|
||||||
app.use(async (ctx, next) => {
|
app.use(async (ctx, next) => {
|
||||||
const path = ctx.request.path;
|
const path = ctx.request.path;
|
||||||
|
|
||||||
// skip anything not on the root path
|
// skip anything not on the root path
|
||||||
if (path !== '/') {
|
if (path !== '/') {
|
||||||
await next();
|
await next();
|
||||||
@@ -54,7 +69,6 @@ module.exports = function(opt) {
|
|||||||
const req_id = rand_id();
|
const req_id = rand_id();
|
||||||
debug('making new client with id %s', req_id);
|
debug('making new client with id %s', req_id);
|
||||||
const info = await manager.newClient(req_id);
|
const info = await manager.newClient(req_id);
|
||||||
|
|
||||||
const url = schema + '://' + info.id + '.' + ctx.request.host;
|
const url = schema + '://' + info.id + '.' + ctx.request.host;
|
||||||
info.url = url;
|
info.url = url;
|
||||||
ctx.body = info;
|
ctx.body = info;
|
||||||
@@ -81,7 +95,7 @@ module.exports = function(opt) {
|
|||||||
const req_id = parts[1];
|
const req_id = parts[1];
|
||||||
|
|
||||||
// limit requested hostnames to 63 characters
|
// 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.';
|
const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.';
|
||||||
ctx.status = 403;
|
ctx.status = 403;
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
@@ -111,7 +125,8 @@ module.exports = function(opt) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientId = GetClientIdFromHostname(hostname);
|
const clientId = GetClientIdFromHostname(hostname,opt);
|
||||||
|
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
appCallback(req, res);
|
appCallback(req, res);
|
||||||
return;
|
return;
|
||||||
@@ -129,13 +144,13 @@ module.exports = function(opt) {
|
|||||||
server.on('upgrade', (req, socket, head) => {
|
server.on('upgrade', (req, socket, head) => {
|
||||||
const hostname = req.headers.host;
|
const hostname = req.headers.host;
|
||||||
if (!hostname) {
|
if (!hostname) {
|
||||||
sock.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientId = GetClientIdFromHostname(hostname);
|
const clientId = GetClientIdFromHostname(hostname,opt);
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
sock.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+3
-1
@@ -4,7 +4,8 @@ var assert = require('assert');
|
|||||||
var localtunnel = require('localtunnel');
|
var localtunnel = require('localtunnel');
|
||||||
|
|
||||||
var localtunnel_server = require('../server')({
|
var localtunnel_server = require('../server')({
|
||||||
max_tcp_sockets: 2
|
max_tcp_sockets: 2,
|
||||||
|
subHost: ['nested','sub']
|
||||||
});
|
});
|
||||||
|
|
||||||
var lt_server_port
|
var lt_server_port
|
||||||
@@ -32,6 +33,7 @@ test('set up local http server', function(done) {
|
|||||||
test('set up localtunnel client', function(done) {
|
test('set up localtunnel client', function(done) {
|
||||||
var opt = {
|
var opt = {
|
||||||
host: 'http://localhost:' + lt_server_port,
|
host: 'http://localhost:' + lt_server_port,
|
||||||
|
subdomain: 'txyz'
|
||||||
};
|
};
|
||||||
|
|
||||||
localtunnel(test._fake_port, opt, function(err, tunnel) {
|
localtunnel(test._fake_port, opt, function(err, tunnel) {
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@ var WebSocket = require('ws');
|
|||||||
var WebSocketServer = require('ws').Server;
|
var WebSocketServer = require('ws').Server;
|
||||||
|
|
||||||
var localtunnel_server = require('../server')({
|
var localtunnel_server = require('../server')({
|
||||||
max_tcp_sockets: 2
|
max_tcp_sockets: 2,
|
||||||
|
subHost: ['sub']
|
||||||
});
|
});
|
||||||
|
|
||||||
var lt_server_port
|
var lt_server_port
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user
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.