Skip to content

Commit 93d62b9

Browse files
committed
init
0 parents  commit 93d62b9

File tree

5 files changed

+393
-0
lines changed

5 files changed

+393
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

bin/lt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
require(__dirname + '/../client');

client.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// builtin
2+
var net = require('net');
3+
var url = require('url');
4+
var request = require('request');
5+
6+
var argv = require('optimist')
7+
.usage('Usage: $0 --port [num]')
8+
.demand(['port'])
9+
.options('host', {
10+
default: 'http://lt.defunctzombie.com',
11+
describe: 'upstream server providing forwarding'
12+
})
13+
.describe('port', 'internal http server port')
14+
.argv;
15+
16+
// local port
17+
var local_port = argv.port;
18+
19+
// optionally override the upstream server
20+
var upstream = url.parse(argv.host);
21+
22+
// query options
23+
var opt = {
24+
host: upstream.hostname,
25+
port: upstream.port || 80,
26+
path: '/',
27+
json: true
28+
};
29+
30+
opt.uri = 'http://' + opt.host + ':' + opt.port + opt.path;
31+
32+
var internal;
33+
var upstream;
34+
35+
(function connect_proxy() {
36+
request(opt, function(err, res, body) {
37+
if (err) {
38+
console.error('upstream not available: http status %d', res.statusCode);
39+
return process.exit(-1);
40+
}
41+
42+
// our assigned hostname and tcp port
43+
var port = body.port;
44+
var host = opt.host;
45+
46+
console.log('your url is: %s', body.url);
47+
48+
// connect to remote tcp server
49+
upstream = net.createConnection(port, host);
50+
51+
// reconnect internal
52+
connect_internal();
53+
54+
upstream.on('end', function() {
55+
56+
// sever connection to internal server
57+
// on reconnect we will re-establish
58+
internal.end();
59+
60+
setTimeout(function() {
61+
connect_proxy();
62+
}, 1000);
63+
});
64+
});
65+
})();
66+
67+
function connect_internal() {
68+
69+
internal = net.createConnection(local_port);
70+
internal.on('error', function(err) {
71+
console.log('error connecting to local server. retrying in 1s');
72+
73+
setTimeout(function() {
74+
connect_internal();
75+
}, 1000);
76+
});
77+
78+
internal.on('end', function() {
79+
console.log('disconnected from local server. retrying in 1s');
80+
setTimeout(function() {
81+
connect_internal();
82+
}, 1000);
83+
});
84+
85+
upstream.pipe(internal);
86+
internal.pipe(upstream);
87+
}
88+

package.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"author": "Roman Shtylman <[email protected]>",
3+
"name": "localtunnel",
4+
"description": "expose localhost to the world",
5+
"version": "0.0.0",
6+
"repository": {
7+
"type": "git",
8+
"url": "git://github.com/shtylman/localtunnel.git"
9+
},
10+
"dependencies": {
11+
"request": "2.9.202",
12+
"book": "1.2.0",
13+
"optimist": "0.3.4"
14+
},
15+
"devDependencies": {},
16+
"optionalDependencies": {},
17+
"engines": {
18+
"node": "*"
19+
},
20+
"bin": {
21+
"lt": "./bin/lt"
22+
}
23+
}

server.js

+279
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
2+
// builtin
3+
var http = require('http');
4+
var net = require('net');
5+
var FreeList = require('freelist').FreeList;
6+
7+
// here be dragons
8+
var HTTPParser = process.binding('http_parser').HTTPParser;
9+
var ServerResponse = http.ServerResponse;
10+
var IncomingMessage = http.IncomingMessage;
11+
12+
var log = require('book');
13+
14+
var chars = 'abcdefghiklmnopqrstuvwxyz';
15+
function rand_id() {
16+
var randomstring = '';
17+
for (var i=0; i<4; ++i) {
18+
var rnum = Math.floor(Math.random() * chars.length);
19+
randomstring += chars[rnum];
20+
}
21+
22+
return randomstring;
23+
}
24+
25+
var server = http.createServer();
26+
27+
// id -> client http server
28+
var clients = {};
29+
30+
// id -> list of sockets waiting for a valid response
31+
var wait_list = {};
32+
33+
var parsers = http.parsers;
34+
35+
// data going back to a client (the last client that made a request)
36+
function socketOnData(d, start, end) {
37+
38+
var socket = this;
39+
var req = this._httpMessage;
40+
41+
var current = clients[socket.subdomain].current;
42+
43+
if (!current) {
44+
log.error('no current for http response from backend');
45+
return;
46+
}
47+
48+
// send the goodies
49+
current.write(d.slice(start, end));
50+
51+
// invoke parsing so we know when all the goodies have been sent
52+
var parser = current.out_parser;
53+
parser.socket = socket;
54+
55+
var ret = parser.execute(d, start, end - start);
56+
if (ret instanceof Error) {
57+
debug('parse error');
58+
freeParser(parser, req);
59+
socket.destroy(ret);
60+
}
61+
}
62+
63+
function freeParser(parser, req) {
64+
if (parser) {
65+
parser._headers = [];
66+
parser.onIncoming = null;
67+
if (parser.socket) {
68+
parser.socket.onend = null;
69+
parser.socket.ondata = null;
70+
parser.socket.parser = null;
71+
}
72+
parser.socket = null;
73+
parser.incoming = null;
74+
parsers.free(parser);
75+
parser = null;
76+
}
77+
if (req) {
78+
req.parser = null;
79+
}
80+
}
81+
82+
// single http connection
83+
// gets a single http response back
84+
server.on('connection', function(socket) {
85+
86+
var self = this;
87+
88+
var for_client = false;
89+
var client_id;
90+
91+
var request;
92+
93+
//var parser = new HTTPParser(HTTPParser.REQUEST);
94+
var parser = parsers.alloc();
95+
parser.socket = socket;
96+
parser.reinitialize(HTTPParser.REQUEST);
97+
98+
// a full request is complete
99+
// we wait for the response from the server
100+
parser.onIncoming = function(req, shouldKeepAlive) {
101+
102+
log.trace('request', req.url);
103+
request = req;
104+
105+
for_client = false;
106+
107+
var hostname = req.headers.host;
108+
var match = hostname.match(/^([a-z]{4})[.].*/);
109+
110+
if (!match) {
111+
// normal processing if not proxy
112+
var res = new ServerResponse(req);
113+
res.assignSocket(parser.socket);
114+
self.emit('request', req, res);
115+
return;
116+
}
117+
118+
client_id = match[1];
119+
for_client = true;
120+
121+
var out_parser = parsers.alloc();
122+
out_parser.reinitialize(HTTPParser.RESPONSE);
123+
socket.out_parser = out_parser;
124+
125+
// we have a response
126+
out_parser.onIncoming = function(res) {
127+
res.on('end', function() {
128+
log.trace('done with response for: %s', req.url);
129+
130+
// done with the parser
131+
parsers.free(out_parser);
132+
133+
var next = wait_list[client_id].shift();
134+
135+
clients[client_id].current = next;
136+
137+
if (!next) {
138+
return;
139+
}
140+
141+
// write original bytes that we held cause client was busy
142+
clients[client_id].write(next.queue);
143+
next.resume();
144+
});
145+
};
146+
};
147+
148+
// process new data on the client socket
149+
// we may need to forward this it the backend
150+
socket.ondata = function(d, start, end) {
151+
var ret = parser.execute(d, start, end - start);
152+
153+
// invalid request from the user
154+
if (ret instanceof Error) {
155+
debug('parse error');
156+
socket.destroy(ret);
157+
return;
158+
}
159+
160+
// only write data if previous request to this client is done?
161+
log.trace('%s %s', parser.incoming && parser.incoming.upgrade, for_client);
162+
163+
// what if the subdomains are treated differently
164+
// as individual channels to the backend if available?
165+
// how can I do that?
166+
167+
if (parser.incoming && parser.incoming.upgrade) {
168+
// websocket shit
169+
}
170+
171+
// wtf do you do with upgraded connections?
172+
173+
// forward the data to the backend
174+
if (for_client) {
175+
var client = clients[client_id];
176+
177+
// requesting a subdomain that doesn't exist
178+
if (!client) {
179+
return;
180+
}
181+
182+
// if the client is already processing something
183+
// then new connections need to go into pause mode
184+
// and when they are revived, then they can send data along
185+
if (client.current && client.current !== socket) {
186+
log.trace('pausing', request.url);
187+
// prevent new data from gathering for this connection
188+
// we are waiting for a response to a previous request
189+
socket.pause();
190+
191+
var copy = Buffer(end - start);
192+
d.copy(copy, 0, start, end);
193+
socket.queue = copy;
194+
195+
wait_list[client_id].push(socket);
196+
197+
return;
198+
}
199+
200+
// this socket needs to receive responses
201+
client.current = socket;
202+
203+
// send through tcp tunnel
204+
client.write(d.slice(start, end));
205+
}
206+
};
207+
208+
socket.onend = function() {
209+
var ret = parser.finish();
210+
211+
if (ret instanceof Error) {
212+
log.trace('parse error');
213+
socket.destroy(ret);
214+
return;
215+
}
216+
217+
socket.end();
218+
};
219+
220+
socket.on('close', function() {
221+
parsers.free(parser);
222+
});
223+
});
224+
225+
server.on('request', function(req, res) {
226+
227+
// generate new shit for client
228+
var id = 'asdf';
229+
//rand_id();
230+
//
231+
//
232+
if (wait_list[id]) {
233+
wait_list[id].forEach(function(waiting) {
234+
waiting.end();
235+
});
236+
}
237+
238+
var client_server = net.createServer();
239+
client_server.listen(function() {
240+
var port = client_server.address().port;
241+
log.info('tcp server listening on port: %d', port);
242+
243+
var url = 'http://' + id + '.' + req.headers.host;
244+
245+
res.writeHead(200, { 'Content-Type': 'application/json' });
246+
res.end(JSON.stringify({ url: url, port: port }));
247+
});
248+
249+
// user has 5 seconds to connect before their slot is given up
250+
var conn_timeout = setTimeout(function() {
251+
client_server.close();
252+
}, 5000);
253+
254+
client_server.on('connection', function(socket) {
255+
256+
// who the info should route back to
257+
socket.subdomain = id;
258+
259+
// multiplexes socket data out to clients
260+
socket.ondata = socketOnData;
261+
262+
clearTimeout(conn_timeout);
263+
264+
log.trace('new connection for id: %s', id);
265+
clients[id] = socket;
266+
wait_list[id] = [];
267+
268+
socket.on('end', function() {
269+
delete clients[id];
270+
});
271+
});
272+
273+
client_server.on('err', function(err) {
274+
log.error(err);
275+
});
276+
});
277+
278+
server.listen(8000);
279+

0 commit comments

Comments
 (0)