Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

init

  • Loading branch information...
commit 93d62b9dbb9f220cfaf03f29d3142f0e32f5f09e 0 parents
@defunctzombie authored
Showing with 393 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +2 −0  bin/lt
  3. +88 −0 client.js
  4. +23 −0 package.json
  5. +279 −0 server.js
1  .gitignore
@@ -0,0 +1 @@
+node_modules
2  bin/lt
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+require(__dirname + '/../client');
88 client.js
@@ -0,0 +1,88 @@
+// builtin
+var net = require('net');
+var url = require('url');
+var request = require('request');
+
+var argv = require('optimist')
+ .usage('Usage: $0 --port [num]')
+ .demand(['port'])
+ .options('host', {
+ default: 'http://lt.defunctzombie.com',
+ describe: 'upstream server providing forwarding'
+ })
+ .describe('port', 'internal http server port')
+ .argv;
+
+// local port
+var local_port = argv.port;
+
+// optionally override the upstream server
+var upstream = url.parse(argv.host);
+
+// query options
+var opt = {
+ host: upstream.hostname,
+ port: upstream.port || 80,
+ path: '/',
+ json: true
+};
+
+opt.uri = 'http://' + opt.host + ':' + opt.port + opt.path;
+
+var internal;
+var upstream;
+
+(function connect_proxy() {
+ request(opt, function(err, res, body) {
+ if (err) {
+ console.error('upstream not available: http status %d', res.statusCode);
+ return process.exit(-1);
+ }
+
+ // our assigned hostname and tcp port
+ var port = body.port;
+ var host = opt.host;
+
+ console.log('your url is: %s', body.url);
+
+ // connect to remote tcp server
+ upstream = net.createConnection(port, host);
+
+ // reconnect internal
+ connect_internal();
+
+ upstream.on('end', function() {
+
+ // sever connection to internal server
+ // on reconnect we will re-establish
+ internal.end();
+
+ setTimeout(function() {
+ connect_proxy();
+ }, 1000);
+ });
+ });
+})();
+
+function connect_internal() {
+
+ internal = net.createConnection(local_port);
+ internal.on('error', function(err) {
+ console.log('error connecting to local server. retrying in 1s');
+
+ setTimeout(function() {
+ connect_internal();
+ }, 1000);
+ });
+
+ internal.on('end', function() {
+ console.log('disconnected from local server. retrying in 1s');
+ setTimeout(function() {
+ connect_internal();
+ }, 1000);
+ });
+
+ upstream.pipe(internal);
+ internal.pipe(upstream);
+}
+
23 package.json
@@ -0,0 +1,23 @@
+{
+ "author": "Roman Shtylman <shtylman@gmail.com>",
+ "name": "localtunnel",
+ "description": "expose localhost to the world",
+ "version": "0.0.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/shtylman/localtunnel.git"
+ },
+ "dependencies": {
+ "request": "2.9.202",
+ "book": "1.2.0",
+ "optimist": "0.3.4"
+ },
+ "devDependencies": {},
+ "optionalDependencies": {},
+ "engines": {
+ "node": "*"
+ },
+ "bin": {
+ "lt": "./bin/lt"
+ }
+}
279 server.js
@@ -0,0 +1,279 @@
+
+// builtin
+var http = require('http');
+var net = require('net');
+var FreeList = require('freelist').FreeList;
+
+// here be dragons
+var HTTPParser = process.binding('http_parser').HTTPParser;
+var ServerResponse = http.ServerResponse;
+var IncomingMessage = http.IncomingMessage;
+
+var log = require('book');
+
+var chars = 'abcdefghiklmnopqrstuvwxyz';
+function rand_id() {
+ var randomstring = '';
+ for (var i=0; i<4; ++i) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ randomstring += chars[rnum];
+ }
+
+ return randomstring;
+}
+
+var server = http.createServer();
+
+// id -> client http server
+var clients = {};
+
+// id -> list of sockets waiting for a valid response
+var wait_list = {};
+
+var parsers = http.parsers;
+
+// data going back to a client (the last client that made a request)
+function socketOnData(d, start, end) {
+
+ var socket = this;
+ var req = this._httpMessage;
+
+ var current = clients[socket.subdomain].current;
+
+ if (!current) {
+ log.error('no current for http response from backend');
+ return;
+ }
+
+ // send the goodies
+ current.write(d.slice(start, end));
+
+ // invoke parsing so we know when all the goodies have been sent
+ var parser = current.out_parser;
+ parser.socket = socket;
+
+ var ret = parser.execute(d, start, end - start);
+ if (ret instanceof Error) {
+ debug('parse error');
+ freeParser(parser, req);
+ socket.destroy(ret);
+ }
+}
+
+function freeParser(parser, req) {
+ if (parser) {
+ parser._headers = [];
+ parser.onIncoming = null;
+ if (parser.socket) {
+ parser.socket.onend = null;
+ parser.socket.ondata = null;
+ parser.socket.parser = null;
+ }
+ parser.socket = null;
+ parser.incoming = null;
+ parsers.free(parser);
+ parser = null;
+ }
+ if (req) {
+ req.parser = null;
+ }
+}
+
+// single http connection
+// gets a single http response back
+server.on('connection', function(socket) {
+
+ var self = this;
+
+ var for_client = false;
+ var client_id;
+
+ var request;
+
+ //var parser = new HTTPParser(HTTPParser.REQUEST);
+ var parser = parsers.alloc();
+ parser.socket = socket;
+ parser.reinitialize(HTTPParser.REQUEST);
+
+ // a full request is complete
+ // we wait for the response from the server
+ parser.onIncoming = function(req, shouldKeepAlive) {
+
+ log.trace('request', req.url);
+ request = req;
+
+ for_client = false;
+
+ var hostname = req.headers.host;
+ var match = hostname.match(/^([a-z]{4})[.].*/);
+
+ if (!match) {
+ // normal processing if not proxy
+ var res = new ServerResponse(req);
+ res.assignSocket(parser.socket);
+ self.emit('request', req, res);
+ return;
+ }
+
+ client_id = match[1];
+ for_client = true;
+
+ var out_parser = parsers.alloc();
+ out_parser.reinitialize(HTTPParser.RESPONSE);
+ socket.out_parser = out_parser;
+
+ // we have a response
+ out_parser.onIncoming = function(res) {
+ res.on('end', function() {
+ log.trace('done with response for: %s', req.url);
+
+ // done with the parser
+ parsers.free(out_parser);
+
+ var next = wait_list[client_id].shift();
+
+ clients[client_id].current = next;
+
+ if (!next) {
+ return;
+ }
+
+ // write original bytes that we held cause client was busy
+ clients[client_id].write(next.queue);
+ next.resume();
+ });
+ };
+ };
+
+ // process new data on the client socket
+ // we may need to forward this it the backend
+ socket.ondata = function(d, start, end) {
+ var ret = parser.execute(d, start, end - start);
+
+ // invalid request from the user
+ if (ret instanceof Error) {
+ debug('parse error');
+ socket.destroy(ret);
+ return;
+ }
+
+ // only write data if previous request to this client is done?
+ log.trace('%s %s', parser.incoming && parser.incoming.upgrade, for_client);
+
+ // what if the subdomains are treated differently
+ // as individual channels to the backend if available?
+ // how can I do that?
+
+ if (parser.incoming && parser.incoming.upgrade) {
+ // websocket shit
+ }
+
+ // wtf do you do with upgraded connections?
+
+ // forward the data to the backend
+ if (for_client) {
+ var client = clients[client_id];
+
+ // requesting a subdomain that doesn't exist
+ if (!client) {
+ return;
+ }
+
+ // if the client is already processing something
+ // then new connections need to go into pause mode
+ // and when they are revived, then they can send data along
+ if (client.current && client.current !== socket) {
+ log.trace('pausing', request.url);
+ // prevent new data from gathering for this connection
+ // we are waiting for a response to a previous request
+ socket.pause();
+
+ var copy = Buffer(end - start);
+ d.copy(copy, 0, start, end);
+ socket.queue = copy;
+
+ wait_list[client_id].push(socket);
+
+ return;
+ }
+
+ // this socket needs to receive responses
+ client.current = socket;
+
+ // send through tcp tunnel
+ client.write(d.slice(start, end));
+ }
+ };
+
+ socket.onend = function() {
+ var ret = parser.finish();
+
+ if (ret instanceof Error) {
+ log.trace('parse error');
+ socket.destroy(ret);
+ return;
+ }
+
+ socket.end();
+ };
+
+ socket.on('close', function() {
+ parsers.free(parser);
+ });
+});
+
+server.on('request', function(req, res) {
+
+ // generate new shit for client
+ var id = 'asdf';
+ //rand_id();
+ //
+ //
+ if (wait_list[id]) {
+ wait_list[id].forEach(function(waiting) {
+ waiting.end();
+ });
+ }
+
+ var client_server = net.createServer();
+ client_server.listen(function() {
+ var port = client_server.address().port;
+ log.info('tcp server listening on port: %d', port);
+
+ var url = 'http://' + id + '.' + req.headers.host;
+
+ res.writeHead(200, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify({ url: url, port: port }));
+ });
+
+ // user has 5 seconds to connect before their slot is given up
+ var conn_timeout = setTimeout(function() {
+ client_server.close();
+ }, 5000);
+
+ client_server.on('connection', function(socket) {
+
+ // who the info should route back to
+ socket.subdomain = id;
+
+ // multiplexes socket data out to clients
+ socket.ondata = socketOnData;
+
+ clearTimeout(conn_timeout);
+
+ log.trace('new connection for id: %s', id);
+ clients[id] = socket;
+ wait_list[id] = [];
+
+ socket.on('end', function() {
+ delete clients[id];
+ });
+ });
+
+ client_server.on('err', function(err) {
+ log.error(err);
+ });
+});
+
+server.listen(8000);
+
Please sign in to comment.
Something went wrong with that request. Please try again.