Put id in path #171

Closed
kody-liou wants to merge 4 commits from put-id-in-path into master
18 changed files with 4879 additions and 1201 deletions
+1 -1
View File
@@ -1 +1 @@
node_modules node_modules
+1
View File
@@ -0,0 +1 @@
16.11.1
-4
View File
@@ -1,4 +0,0 @@
language: node_js
sudo: false
node_js:
- "9.2"
+7 -4
View File
@@ -1,13 +1,16 @@
FROM node:10.1.0-alpine FROM node:16.11.1-alpine
WORKDIR /app WORKDIR /app
COPY package.json /app/ COPY package.json /app/
COPY yarn.lock /app/ COPY package-lock.json /app/
RUN yarn install --production && yarn cache clean RUN npm install
COPY . /app COPY . /app
ENV NODE_ENV production ENV NODE_ENV production
ENTRYPOINT ["node", "-r", "esm", "./bin/server"]
EXPOSE 8080
CMD npm start
+1
View File
@@ -0,0 +1 @@
web: npm start
+2
View File
@@ -1,3 +1,5 @@
# build image
docker build -t lt-server .
# localtunnel-server # 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/localtunnel/server.svg?branch=master)](https://travis-ci.org/localtunnel/server)
+9 -8
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env node -r esm #!/usr/bin/env node -r esm
import 'localenv'; require('localenv');
import optimist from 'optimist'; const optimist = require('optimist');
import log from 'book'; const log = require('book');
import Debug from 'debug'; // const Debug = require('debug');
import CreateServer from '../server'; const CreateServer = require('../server');
const debug = Debug('localtunnel'); // const debug = Debug('localtunnel');
const argv = optimist const argv = optimist
.usage('Usage: $0 --port [num]') .usage('Usage: $0 --port [num]')
@@ -44,8 +44,9 @@ const server = CreateServer({
domain: argv.domain, domain: argv.domain,
}); });
server.listen(argv.port, argv.address, () => { server.listen(process.env.PORT || argv.port, argv.address, () => {
debug('server listening on port: %d', server.address().port); // debug('server listening on port: %d', server.address().port);
console.log(`server listening on : ${JSON.stringify(server.address())}`)
}); });
process.on('SIGINT', () => { process.on('SIGINT', () => {
+5 -5
View File
@@ -1,7 +1,7 @@
import http from 'http'; const http = require('http');
import Debug from 'debug'; const Debug = require('debug');
import pump from 'pump'; const pump = require('pump');
import EventEmitter from 'events'; const EventEmitter = require('events');
// A client encapsulates req/res handling using an agent // A client encapsulates req/res handling using an agent
// //
@@ -129,4 +129,4 @@ class Client extends EventEmitter {
} }
} }
export default Client; module.exports = Client;
+6 -6
View File
@@ -1,10 +1,10 @@
import assert from 'assert'; const assert = require('assert');
import http from 'http'; const http = require('http');
import { Duplex } from 'stream'; const { Duplex } = require('stream');
import WebSocket from 'ws'; const WebSocket = require('ws');
import net from 'net'; const net = require('net');
import Client from './Client'; const Client from './Client';
class DummySocket extends Duplex { class DummySocket extends Duplex {
constructor(options) { constructor(options) {
+5 -10
View File
@@ -1,8 +1,5 @@
import { hri } from 'human-readable-ids'; const Client = require('./Client');
import Debug from 'debug'; const TunnelAgent = require('./TunnelAgent');
import Client from './Client';
import TunnelAgent from './TunnelAgent';
// Manage sets of clients // Manage sets of clients
// //
@@ -19,8 +16,6 @@ class ClientManager {
tunnels: 0 tunnels: 0
}; };
this.debug = Debug('lt:ClientManager');
// This is totally wrong :facepalm: this needs to be per-client... // This is totally wrong :facepalm: this needs to be per-client...
this.graceTimeout = null; this.graceTimeout = null;
} }
@@ -34,7 +29,7 @@ class ClientManager {
// can't ask for id already is use // can't ask for id already is use
if (clients[id]) { if (clients[id]) {
id = hri.random(); return;
} }
const maxSockets = this.opt.max_tcp_sockets; const maxSockets = this.opt.max_tcp_sockets;
@@ -74,7 +69,7 @@ class ClientManager {
} }
removeClient(id) { removeClient(id) {
this.debug('removing client: %s', id); console.debug('removing client: %s', id)
const client = this.clients[id]; const client = this.clients[id];
if (!client) { if (!client) {
return; return;
@@ -93,4 +88,4 @@ class ClientManager {
} }
} }
export default ClientManager; module.exports = ClientManager;
+3 -3
View File
@@ -1,7 +1,7 @@
import assert from 'assert'; const assert = require('assert');
import net from 'net'; const net = require('net');
import ClientManager from './ClientManager'; const ClientManager = require('./ClientManager');
describe('ClientManager', () => { describe('ClientManager', () => {
it('should construct with no tunnels', () => { it('should construct with no tunnels', () => {
+6 -6
View File
@@ -1,8 +1,8 @@
import { Agent } from 'http'; const { Agent } = require('http');
import net from 'net'; const net = require('net');
import assert from 'assert'; const assert = require('assert');
import log from 'book'; const log = require('book');
import Debug from 'debug'; const Debug = require('debug');
const DEFAULT_MAX_SOCKETS = 10; const DEFAULT_MAX_SOCKETS = 10;
@@ -172,4 +172,4 @@ class TunnelAgent extends Agent {
} }
} }
export default TunnelAgent; module.exports = TunnelAgent;
+4 -4
View File
@@ -1,8 +1,8 @@
import http from 'http'; const http = require('http');
import net from 'net'; const net = require('net');
import assert from 'assert'; const assert = require('assert');
import TunnelAgent from './TunnelAgent'; const TunnelAgent = require('./TunnelAgent');
describe('TunnelAgent', () => { describe('TunnelAgent', () => {
it('should create an empty agent', async () => { it('should create an empty agent', async () => {
+4765
View File
File diff suppressed because it is too large Load Diff
+12 -11
View File
@@ -10,25 +10,26 @@
}, },
"dependencies": { "dependencies": {
"book": "1.3.3", "book": "1.3.3",
"debug": "3.1.0", "debug": "4.3.2",
"esm": "3.0.34", "esm": "3.2.25",
"human-readable-ids": "1.0.3", "human-readable-ids": "1.0.4",
"koa": "2.5.1", "koa": "2.13.3",
"koa-router": "7.4.0", "koa-router": "10.1.1",
"localenv": "0.2.2", "localenv": "0.2.2",
"optimist": "0.6.1", "optimist": "0.6.1",
"pump": "3.0.0", "pump": "3.0.0",
"tldjs": "2.3.1" "tldjs": "2.3.1"
}, },
"devDependencies": { "devDependencies": {
"mocha": "5.1.1", "mocha": "9.1.3",
"node-dev": "3.1.3", "node-dev": "7.0.0",
"supertest": "3.1.0", "nodemon": "2.0.14",
"ws": "5.1.1" "supertest": "6.1.6",
"ws": "8.2.3"
}, },
"scripts": { "scripts": {
"test": "mocha --check-leaks --require esm './**/*.test.js'", "test": "mocha --check-leaks --require esm './**/*.test.js'",
"start": "./bin/server", "start": "node bin/server --port 8080 --secure true",
"dev": "node-dev bin/server --port 3000" "dev": "nodemon bin/server --port 3000"
} }
} }
+41 -73
View File
@@ -1,26 +1,17 @@
import log from 'book'; const Koa = require('koa');
import Koa from 'koa'; const Debug = require('debug');
import tldjs from 'tldjs'; const http = require('http');
import Debug from 'debug'; const Router = require('koa-router');
import http from 'http';
import { hri } from 'human-readable-ids';
import Router from 'koa-router';
import ClientManager from './lib/ClientManager'; const ClientManager = require('./lib/ClientManager');
const debug = Debug('localtunnel:server'); const debug = Debug('localtunnel:server');
let counter = 0;
export default function(opt) { module.exports = function(opt) {
opt = opt || {}; opt = opt || {};
const validHosts = (opt.domain) ? [opt.domain] : undefined;
const myTldjs = tldjs.fromUserSettings({ validHosts });
const landingPage = opt.landing || 'https://localtunnel.github.io/www/'; const landingPage = opt.landing || 'https://localtunnel.github.io/www/';
function GetClientIdFromHostname(hostname) {
return myTldjs.getSubdomain(hostname);
}
const manager = new ClientManager(opt); const manager = new ClientManager(opt);
const schema = opt.secure ? 'https' : 'http'; const schema = opt.secure ? 'https' : 'http';
@@ -29,10 +20,12 @@ export default function(opt) {
const router = new Router(); const router = new Router();
router.get('/api/status', async (ctx, next) => { router.get('/api/status', async (ctx, next) => {
counter += 1;
const stats = manager.stats; const stats = manager.stats;
ctx.body = { ctx.body = {
tunnels: stats.tunnels, tunnels: stats.tunnels,
mem: process.memoryUsage(), mem: process.memoryUsage(),
counter,
}; };
}); });
@@ -53,49 +46,25 @@ export default function(opt) {
app.use(router.routes()); app.use(router.routes());
app.use(router.allowedMethods()); app.use(router.allowedMethods());
// root endpoint app.use(async (ctx) => {
app.use(async (ctx, next) => { const newReqId = ctx.query['new'];
const path = ctx.request.path; const parts = ctx.originalUrl.split('/')
const reqId = parts[1]
// skip anything not on the root path if(reqId && !newReqId) {
if (path !== '/') { ctx.status = 404;
await next(); ctx.body = {
message: `id: ${reqId}, not found`,
};
return; return;
} }
if (!newReqId) {
const isNewClientRequest = ctx.query['new'] !== undefined; ctx.redirect(landingPage);
if (isNewClientRequest) {
const reqId = hri.random();
debug('making new client with id %s', reqId);
const info = await manager.newClient(reqId);
const url = schema + '://' + info.id + '.' + ctx.request.host;
info.url = url;
ctx.body = info;
return; return;
} }
// no new client request, send to landing page
ctx.redirect(landingPage);
});
// anything after the / path is a request for a specific client name
// This is a backwards compat feature
app.use(async (ctx, next) => {
const parts = ctx.request.path.split('/');
// any request with several layers of paths is not allowed
// rejects /foo/bar
// allow /foo
if (parts.length !== 2) {
await next();
return;
}
const reqId = parts[1];
// limit requested hostnames to 63 characters // limit requested hostnames to 63 characters
if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(reqId)) { if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(newReqId)) {
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 = {
@@ -104,10 +73,16 @@ export default function(opt) {
return; return;
} }
debug('making new client with id %s', reqId); debug('making new client with id %s', newReqId);
const info = await manager.newClient(reqId); const info = await manager.newClient(newReqId);
if(!info) {
const url = schema + '://' + info.id + '.' + ctx.request.host; ctx.status = 500;
ctx.body = {
message: 'no new client info',
};
return;
}
const url = `${schema}://${ctx.request.host}/${info.id}`;
info.url = url; info.url = url;
ctx.body = info; ctx.body = info;
return; return;
@@ -117,16 +92,14 @@ export default function(opt) {
const appCallback = app.callback(); const appCallback = app.callback();
server.on('request', (req, res) => { server.on('request', (req, res) => {
// without a hostname, we won't know who the request is for const parts = req.url.split('/');
const hostname = req.headers.host; if(parts[1].indexOf('?new=') === 0){
if (!hostname) { appCallback(req, res);
res.statusCode = 400;
res.end('Host header is required');
return; return;
} }
const clientId = parts[1];
const clientId = GetClientIdFromHostname(hostname);
if (!clientId) { if (!clientId) {
appCallback(req, res); appCallback(req, res);
return; return;
@@ -134,27 +107,22 @@ export default function(opt) {
const client = manager.getClient(clientId); const client = manager.getClient(clientId);
if (!client) { if (!client) {
res.statusCode = 404; appCallback(req, res);
res.end('404');
return; return;
} }
client.handleRequest(req, res); client.handleRequest(req, res);
}); });
server.on('upgrade', (req, socket, head) => { server.on('upgrade', (req, socket) => {
const hostname = req.headers.host; const parts = req.url.split('/').filter((part=>part != ''));
if (!hostname) { const clientId = parts[0];
socket.destroy();
return;
}
const clientId = GetClientIdFromHostname(hostname);
if (!clientId) { if (!clientId) {
socket.destroy(); socket.destroy();
return; return;
} }
const client = manager.getClient(clientId); const client = manager.getClient(clientId);
if (!client) { if (!client) {
socket.destroy(); socket.destroy();
+11
View File
@@ -0,0 +1,11 @@
GET http://localhost:8080/api/status
###
GET http://localhost:8080/api/tunnels/neat-vampirebat-44/status
###
GET http://localhost:8080?new=true
###
GET https://neat-vampirebat-44.localhost:8080
###
GET http://localhost:3000?new=adogijaiog
###
GET http://localhost:3000/adogijaiog
-1066
View File
File diff suppressed because it is too large Load Diff