diff --git a/packages/boilerplate-generator/boilerplate_web.cordova.html b/packages/boilerplate-generator/boilerplate_web.cordova.html index 8925fef1f5..c354a1b68a 100644 --- a/packages/boilerplate-generator/boilerplate_web.cordova.html +++ b/packages/boilerplate-generator/boilerplate_web.cordova.html @@ -13,8 +13,8 @@ if (/Android/i.test(navigator.userAgent)) { // When Android app is emulated, it cannot connect to localhost, // instead it should connect to 10.0.2.2 - __meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || '').replace(/localhost/i, '10.0.2.2'); - __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || '').replace(/localhost/i, '10.0.2.2'); + //__meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || '').replace(/localhost/i, '10.0.2.2'); + //__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || '').replace(/localhost/i, '10.0.2.2'); } diff --git a/scripts/generate-android-bundle.sh b/scripts/generate-android-bundle.sh index e6e51d6a59..7486deeca8 100755 --- a/scripts/generate-android-bundle.sh +++ b/scripts/generate-android-bundle.sh @@ -46,7 +46,7 @@ else fi { - curl -O http://www.motorlogy.com/apache//ant/binaries/apache-ant-1.9.4-bin.tar.gz + curl -O http://apache.osuosl.org/ant/binaries/apache-ant-1.9.4-bin.tar.gz tar xzf apache-ant-1.9.4-bin.tar.gz rm apache-ant-1.9.4-bin.tar.gz @@ -58,9 +58,12 @@ fi # the platform that cordova likes echo y | android-sdk/tools/android update sdk -t android-19 -u - # system image for android 19 + # system image for android 19 - arm echo y | android-sdk/tools/android update sdk -t sys-img-armeabi-v7a-android-19 --all -u + # system image for android 19 - x86 + echo y | android-sdk/tools/android update sdk -t sys-img-x86-android-19 --all -u + # build tools echo y | android-sdk/tools/android update sdk -t "build-tools-20.0.0" -u diff --git a/tools/commands-cordova.js b/tools/commands-cordova.js index 7f8a965b4f..043bac00c9 100644 --- a/tools/commands-cordova.js +++ b/tools/commands-cordova.js @@ -57,7 +57,11 @@ var execFileAsyncOrThrow = function (file, args, opts, cb) { } // XXX a hack to always tell the scripts where warehouse is - if (opts) opts.env = _.extend({ "WAREHOUSE_DIR": tropo.root, "USE_GLOBAL_ADK": process.env.USE_GLOBAL_ADK || "", HOME: process.env.HOME }, opts.env); + opts = opts || {}; + opts.env = _.extend({ "USE_GLOBAL_ADK": "" }, + process.env, + opts.env || {}, + { "WAREHOUSE_DIR": tropo.root }); var execFileAsync = require('./utils.js').execFileAsync; ensureAndroidBundle(file); @@ -82,7 +86,11 @@ var execFileSyncOrThrow = function (file, args, opts) { verboseLog('Running synchronously: ', file, args); // XXX a hack to always tell the scripts where warehouse is - if (opts) opts.env = _.extend({ "WAREHOUSE_DIR": tropo.root, "USE_GLOBAL_ADK": process.env.USE_GLOBAL_ADK || "", HOME: process.env.HOME }, opts.env); + opts = opts || {}; + opts.env = _.extend({ "USE_GLOBAL_ADK": "" }, + process.env, + opts.env || {}, + { "WAREHOUSE_DIR": tropo.root }); var childProcess = execFileSync(file, args, opts); if (! childProcess.success) @@ -616,6 +624,44 @@ cordova.buildPlatforms = function (localPath, platforms, options) { buildCordova(localPath, 'build', options); }; + +cordova.buildPlatformRunners = function (localPath, platforms, options) { + var runners = []; + _.each(platforms, function (platformName) { + runners.push(new CordovaRunner(localPath, platformName, options)); + }); + return runners; +}; + + +// This is a runner, that we pass to Runner (run-all.js) +var CordovaRunner = function (localPath, platformName, options) { + var self = this; + + self.localPath = localPath; + self.platformName = platformName; + self.options = options; + + self.title = 'Cordova (' + self.platformName + ')'; +}; + +_.extend(CordovaRunner.prototype, { + start: function () { + var self = this; + + execCordovaOnPlatform(self.localPath, + self.platformName, + self.options); + }, + + stop: function () { + var self = this; + + // XXX: A no-op for now (we leave it running because it's slow!) + } +}); + + // Start the simulator or physical device for a specific platform. // platformName is of the form ios/ios-device/android/android-device // options: @@ -631,9 +677,12 @@ var execCordovaOnPlatform = function (localPath, platformName, options) { verboseLog('isDevice:', isDevice); - var args = [ 'run', - isDevice ? '--device' : '--emulator', - platform ]; + var args = [ 'run' ]; + if (options.verbose) { + args.push('--verbose'); + } + args.push(isDevice ? '--device' : '--emulator'); + args.push(platform); // XXX assert we have a valid Cordova project if (platform === 'ios' && isDevice) { @@ -645,9 +694,16 @@ var execCordovaOnPlatform = function (localPath, platformName, options) { 'platforms', 'ios', '*.xcodeproj')]); } else { verboseLog('Running emulator:', localCordova, args); + var emulatorOptions = { verbose: options.verbose, cwd: cordovaPath }; + emulatorOptions.env = _.extend({}, process.env); + if (options.httpProxyPort) { + // XXX: Is this Android only? + // This is odd; the IP address is on the host, not inside the emulator + emulatorOptions.env['http_proxy'] = '127.0.0.1:' + options.httpProxyPort; + } execFileAsyncOrThrow( localCordova, args, - { verbose: options.verbose, cwd: cordovaPath }); + emulatorOptions); } var Log = getLoadedPackages().logging.Log; @@ -733,14 +789,7 @@ var execCordovaOnPlatform = function (localPath, platformName, options) { return 0; }; -// Start the simulator or physical device for a list of platforms -// options: -// - verbose: print all build logs -cordova.runPlatforms = function (localPath, platforms, options) { - _.each(platforms, function (platformName) { - execCordovaOnPlatform(localPath, platformName, options); - }); -}; + // packages - list of strings cordova.filterPackages = function (packages) { @@ -843,10 +892,16 @@ main.registerCommand({ name: "configure-android", options: { verbose: { type: Boolean, short: "v" } - } + }, + minArgs: 0, + maxArgs: Infinity }, function (options) { + cordova.setVerboseness(options.verbose); + + var androidArgs = options.args || []; try { - execFileSyncOrThrow(localAndroid, [], options); + var execOptions = { pipeOutput: true, verbose: options.verbose }; + execFileSyncOrThrow(localAndroid, androidArgs, execOptions); } catch (err) { // this tool can crash for whatever reason, ignore its failures } diff --git a/tools/commands.js b/tools/commands.js index 1677338723..4ab2c3aa9b 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -151,6 +151,7 @@ main.registerCommand({ options: { port: { type: String, short: "p", default: '3000' }, 'app-port': { type: String }, + 'http-proxy-port': { type: String }, production: { type: Boolean }, 'raw-logs': { type: Boolean }, settings: { type: String }, @@ -199,6 +200,8 @@ main.registerCommand({ return 1; } + options.httpProxyPort = options['http-proxy-port']; + // If we are targeting the remote devices if (_.intersection(options.args, ['ios-device', 'android-device']).length) { cordova.verboseLog('A run on a device requested'); @@ -213,15 +216,22 @@ main.registerCommand({ // Always bundle for the browser by default. var webArchs = project.getWebArchs(); + var runners = []; + // If additional args were specified, then also start a mobile build. if (options.args.length) { // will asynchronously start mobile emulators/devices try { - // --clean encpasulates the behavior of once + // --clean encapsulates the behavior of once if (options.clean) { options.once = true; } + if (!options.httpProxyPort) { + console.log('Forcing http proxy on port 3002 for mobile'); + options.httpProxyPort = '3002'; + } + cordova.verboseLog('Will compile mobile builds'); var appName = path.basename(options.appDir); var localPath = path.join(options.appDir, '.meteor', 'local'); @@ -229,7 +239,7 @@ main.registerCommand({ cordova.buildPlatforms(localPath, options.args, _.extend({ appName: appName, debug: ! options.production }, options, parsedHostPort)); - cordova.runPlatforms(localPath, options.args, options); + runners = runners.concat(cordova.buildPlatformRunners(localPath, options.args, options)); } catch (err) { if (options.verbose) { process.stderr.write('Error while running for mobile platforms ' + @@ -278,6 +288,7 @@ main.registerCommand({ return runAll.run(options.appDir, { proxyPort: parsedHostPort.port, proxyHost: parsedHostPort.host, + httpProxyPort: options.httpProxyPort, appPort: appPort, appHost: appHost, settingsFile: options.settings, @@ -289,7 +300,8 @@ main.registerCommand({ rootUrl: process.env.ROOT_URL, mongoUrl: process.env.MONGO_URL, oplogUrl: process.env.MONGO_OPLOG_URL, - once: options.once + once: options.once, + extraRunners: runners }); }); @@ -1119,6 +1131,7 @@ main.registerCommand({ maxArgs: Infinity, options: { port: { type: String, short: "p", default: "localhost:3000" }, + 'http-proxy-port': { type: String }, deploy: { type: String }, production: { type: Boolean }, settings: { type: String }, @@ -1151,6 +1164,8 @@ main.registerCommand({ return 1; } + options.httpProxyPort = options['http-proxy-port']; + // XXX not good to change the options this way _.extend(options, parsedHostPort); @@ -1187,6 +1202,8 @@ main.registerCommand({ [options['driver-package'] || 'test-in-browser'], 'add'); + var runners = []; + var mobileOptions = ['ios', 'ios-device', 'android', 'android-device']; var mobilePlatforms = []; @@ -1196,6 +1213,11 @@ main.registerCommand({ }); if (! _.isEmpty(mobilePlatforms)) { + if (!options.httpProxyPort) { + console.log('Forcing http proxy on port 3002 for mobile'); + options.httpProxyPort = '3002' + } + var localPath = path.join(testRunnerAppDir, '.meteor', 'local'); var platforms = @@ -1211,13 +1233,14 @@ main.registerCommand({ appName: path.basename(testRunnerAppDir), debug: ! options.production })); - cordova.runPlatforms(localPath, mobilePlatforms, options); + runners = runners.concat(cordova.buildPlatformRunners(localPath, mobilePlatforms, options)); } catch (err) { process.stderr.write(err.message + '\n'); return 1; } } + options.extraRunners = runners; return runTestAppForPackages(testPackages, testRunnerAppDir, options); }); @@ -1394,6 +1417,7 @@ var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) { // a switch to a different release appDirForVersionCheck: options.appDir, proxyPort: options.port, + httpProxyPort: options.httpProxyPort, disableOplog: options['disable-oplog'], settingsFile: options.settings, banner: "Tests", @@ -1402,7 +1426,8 @@ var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) { mongoUrl: process.env.MONGO_URL, oplogUrl: process.env.MONGO_OPLOG_URL, once: options.once, - recordPackageUsage: false + recordPackageUsage: false, + extraRunners: options.extraRunners }); } diff --git a/tools/cordova-scripts/common_env.sh b/tools/cordova-scripts/common_env.sh index c845a699e6..3d0d008a78 100644 --- a/tools/cordova-scripts/common_env.sh +++ b/tools/cordova-scripts/common_env.sh @@ -47,9 +47,8 @@ if [ -z "$USE_GLOBAL_ADK" ] ; then export HOME="${ANDROID_BUNDLE}" export ANDROID_SDK_HOME="${ANDROID_BUNDLE}" else - # to use a global ADK we don't set PATH, ANT_HOME + # to use a global ADK we don't set PATH, ANT_HOME, ANDROID_SDK_HOME # relying that they are installed and available globally - export ANDROID_SDK_HOME="${HOME}" true fi diff --git a/tools/cordova-scripts/ensure_android_bundle.sh b/tools/cordova-scripts/ensure_android_bundle.sh index 97d7f792c2..2f07597814 100755 --- a/tools/cordova-scripts/ensure_android_bundle.sh +++ b/tools/cordova-scripts/ensure_android_bundle.sh @@ -1,4 +1,7 @@ #!/bin/bash + +set -e + BUNDLE_VERSION=0.1 # OS Check. Put here because here is where we download the precompiled @@ -92,11 +95,52 @@ command -v javac >/dev/null 2>&1 || { echo >&2 "To add the android platform, please install a JDK. Here are some directions: http://openjdk.java.net/install/"; exit 1; } +set_config () { + KEY=$1 + VALUE=$2 + + CONFIG_FILE=${ANDROID_BUNDLE}/meteor_avd/config.ini + + TEMP_FILE=`mktemp tmp.XXXXXXXXXX` + grep -v "^${KEY}=" ${CONFIG_FILE} > ${TEMP_FILE} + echo "${KEY}=${VALUE}" >> ${TEMP_FILE} + mv -f ${TEMP_FILE} ${CONFIG_FILE} +} + +install_x86 () { + echo "Android x86 System image not found. Found targets:" + android list target + echo "Downloading x86 system image..." + echo y | android update sdk -t sys-img-x86-android-19 --all -u +} # create avd if necessary if [[ ! $("${ANDROID_BUNDLE}/android-sdk/tools/android" list avd | grep Name) ]] ; then + #ABI="default/armeabi-v7a" + ABI="default/x86" + + (android list target | grep ABIs | grep default/x86 > /dev/null) || install_x86 + echo " -" | "${ANDROID_BUNDLE}/android-sdk/tools/android" create avd --target 1 --name meteor --abi default/armeabi-v7a --path ${ANDROID_BUNDLE}/meteor_avd/ 1>&2 +" | "${ANDROID_BUNDLE}/android-sdk/tools/android" create avd --target 1 --name meteor --abi ${ABI} --path ${ANDROID_BUNDLE}/meteor_avd/ 1>&2 + + # Nice keyboard support + set_config "hw.keyboard" "yes" + set_config "hw.mainKeys" "no" + + # More RAM than the default + set_config "hw.ramSize" "1024" + set_config "vm.heapSize" "64" + + # These are the settings for a Nexus 4, but it's a bit big for some screens + # (and likely a bit slow without GPU & KVM/HAXM acceleration) + #set_config "skin.dynamic" "yes" + #set_config "hw.lcd.density" "320" + #set_config "hw.device.name" "Nexus 4" + #set_config "hw.device.manufacturer" "Google" + + # XXX: hw.gpu.enabled=yes ? + fi diff --git a/tools/cordova-scripts/set_android_proxy.sh b/tools/cordova-scripts/set_android_proxy.sh new file mode 100755 index 0000000000..a8a1b8f07d --- /dev/null +++ b/tools/cordova-scripts/set_android_proxy.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# import all the environment +source $(dirname $0)/common_env.sh + +adb shell sqlite3 /data/data/com.android.providers.telephony/databases/telephony.db \ + "update carriers set proxy='10.0.2.2', port='3002' where current=1" +adb shell stop +adb shell start diff --git a/tools/run-all.js b/tools/run-all.js index 9dec9c890c..acd6bb9477 100644 --- a/tools/run-all.js +++ b/tools/run-all.js @@ -5,6 +5,7 @@ var release = require('./release.js'); var runLog = require('./run-log.js'); var Proxy = require('./run-proxy.js').Proxy; +var HttpProxy = require('./run-httpproxy.js').HttpProxy; var AppRunner = require('./run-app.js').AppRunner; var MongoRunner = require('./run-mongo.js').MongoRunner; var Updater = require('./run-updater.js').Updater; @@ -36,6 +37,8 @@ var Runner = function (appDir, options) { self.rootUrl = 'http://localhost:' + listenPort + '/'; } + self.extraRunners = options.extraRunners; + self.proxy = new Proxy({ listenPort: listenPort, listenHost: options.proxyHost, @@ -44,6 +47,13 @@ var Runner = function (appDir, options) { onFailure: options.onFailure }); + self.httpProxy = null; + if (options.httpProxyPort) { + self.httpProxy = new HttpProxy({ + listenPort: options.httpProxyPort + }) + } + self.mongoRunner = null; var mongoUrl, oplogUrl; if (options.mongoUrl) { @@ -99,6 +109,14 @@ _.extend(Runner.prototype, { self.updater.start(); } + // print the banner only once we've successfully bound the port + if (! self.stopped && self.httpProxy) { + self.httpProxy.start(); + if (! self.quiet) { + runLog.log("=> Started http proxy."); + } + } + if (! self.stopped && self.mongoRunner) { var spinner = ['-', '\\', '|', '/']; // I looked at some Unicode indeterminate progress indicators, such as: @@ -136,6 +154,17 @@ _.extend(Runner.prototype, { } } + _.forEach(self.extraRunners, function (extraRunner) { + if (! self.stopped) { + var title = extraRunner.title; + if (! self.quiet) + runLog.logTemporary("=> Starting " + title + "..."); + extraRunner.start(); + if (! self.quiet && ! self.stopped) + runLog.log("=> Started " + title + "."); + } + }); + if (! self.stopped) { if (! self.quiet) runLog.logTemporary("=> Starting your app..."); @@ -161,8 +190,12 @@ _.extend(Runner.prototype, { self.stopped = true; self.proxy.stop(); + self.httpProxy && self.httpProxy.stop(); self.updater.stop(); self.mongoRunner && self.mongoRunner.stop(); + _.forEach(self.extraRunners, function (extraRunner) { + extraRunner.stop(); + }); self.appRunner.stop(); // XXX does calling this 'finish' still make sense now that runLog is a // singleton? diff --git a/tools/run-httpproxy.js b/tools/run-httpproxy.js new file mode 100644 index 0000000000..e63c05fe34 --- /dev/null +++ b/tools/run-httpproxy.js @@ -0,0 +1,280 @@ +// The HTTP proxy is primarily so we can use localhost:3000 with OAuth, +// on devices which don't run a webserver e.g. Android / iOS +// This is a generic HTTP proxy, like a mini-Squid +// (whereas run-proxy.js is just for our app) +var _ = require('underscore'); +var Future = require('fibers/future'); +var runLog = require('./run-log.js'); +var url = require('url'); + +// options: listenPort, listenHost, onFailure +var HttpProxy = function (options) { + var self = this; + + self.listenPort = options.listenPort; + self.listenHost = options.listenHost; + + self.onFailure = options.onFailure || function () {}; + + self.mode = "proxy"; + self.httpQueue = []; // keys: req, res + self.websocketQueue = []; // keys: req, socket, head + self.connectQueue = []; // keys: req, socket, head + + self.proxy = null; + self.server = null; +}; + +_.extend(HttpProxy.prototype, { + // Start the proxy server, block (yield) until it is ready to go + // (actively listening on outer and proxying to inner), and then + // return. + start: function () { + var self = this; + + if (self.server) + throw new Error("already running?"); + + self.started = false; + + var http = require('http'); + var net = require('net'); + var httpProxy = require('http-proxy'); + + self.proxy = httpProxy.createProxyServer({ + // agent is required to handle keep-alive, and http-proxy 1.0 is a little + // buggy without it: https://github.com/nodejitsu/node-http-proxy/pull/488 + agent: new http.Agent({ maxSockets: 100 }), + xfwd: false //true + }); + + var server = self.server = http.createServer(function (req, res) { + // Normal HTTP request + self.httpQueue.push({ req: req, res: res }); + self._tryHandleConnections(); + }); + + self.server.on('connect', function (req, socket, head) { + self.connectQueue.push({ req: req, socket: socket, head: head }); + self._tryHandleConnections(); + }); + + self.server.on('upgrade', function (req, socket, head) { + // Websocket connection + self.websocketQueue.push({ req: req, socket: socket, head: head }); + self._tryHandleConnections(); + }); + + var fut = new Future; + self.server.on('error', function (err) { + if (err.code === 'EADDRINUSE') { + var port = self.listenPort; + runLog.log( + "HTTP proxy server can't listen on port " + port + ". \n" + + "If something else is using port " + port + ", you can\n" + + "specify an alternative port with --http-proxy-port ."); + } else if (self.listenHost && + (err.code === 'ENOTFOUND' || err.code === 'EADDRNOTAVAIL')) { + // This handles the case of "entered a DNS name that's unknown" + // (ENOTFOUND from getaddrinfo) and "entered some random IP that we + // can't bind to" (EADDRNOTAVAIL from listen). + runLog.log( + "Can't listen on host " + self.listenHost + + " (" + err.code + " from " + err.syscall + ")."); + } else { + runLog.log('' + err); + } + self.onFailure(); + // Allow start() to return. + fut.isResolved() || fut['return'](); + }); + + // Don't crash if the app doesn't respond; instead return an error + // immediately. + self.proxy.on('error', function (err, req, resOrSocket) { + if (resOrSocket instanceof http.ServerResponse) { + resOrSocket.writeHead(503, { + 'Content-Type': 'text/plain' + }); + resOrSocket.end('Unexpected error.'); + } else if (resOrSocket instanceof net.Socket) { + resOrSocket.end(); + } + }); + + self.server.listen(self.listenPort, self.listenHost || '0.0.0.0', function () { + if (self.server) { + self.started = true; + } else { + // stop() got called while we were invoking listen! Close the server (we + // still have the var server). The rest of the cleanup shouldn't be + // necessary. + server.close(); + } + fut.isResolved() || fut['return'](); + }); + + fut.wait(); + }, + + // Idempotent. + stop: function () { + var self = this; + + if (! self.server) + return; + + if (! self.started) { + // This probably means that we failed to listen. However, there could be a + // race condition and we could be in the middle of starting to listen! In + // that case, the listen callback will notice that we nulled out server + // here. + self.server = null; + return; + } + + // This stops listening but allows existing connections to + // complete gracefully. + self.server.close(); + self.server = null; + + // It doesn't seem to be necessary to do anything special to + // destroy an httpProxy proxyserver object. + self.proxy = null; + + // Drop any held connections. + _.each(self.httpQueue, function (c) { + c.res.statusCode = 500; + c.res.end(); + }); + self.httpQueue = []; + + _.each(self.websocketQueue, function (c) { + c.socket.destroy(); + }); + self.websocketQueue = []; + + _.each(self.connectQueue, function (c) { + c.socket.destroy(); + }); + self.connectQueue = []; + + self.mode = "hold"; + }, + + _tryHandleConnections: function () { + var self = this; + + while (self.httpQueue.length) { + if (self.mode !== "proxy") + break; + + var c = self.httpQueue.shift(); + var req = c.req; + var targetUrl = req.url; + runLog.log("Proxy request: " + req.method + " " +req.url); + var newUrl = req.url + self.proxy.web(c.req, c.res, { + target: targetUrl + }); + } + + while (self.websocketQueue.length) { + if (self.mode !== "proxy") + break; + + var c = self.websocketQueue.shift(); + var req = c.req; + var targetUrl = req.url; + runLog.log("Proxy request (websocket): " + req.method + " " +req.url); + self.proxy.ws(c.req, c.socket, c.head, { + target: targetUrl + }); + } + + while (self.connectQueue.length) { + if (self.mode !== "proxy") + break; + + var c = self.connectQueue.shift(); + runLog.log("Proxy request (connect): " + c.req.method + " " + c.req.url); + proxyConnectMethod(c.req, c.socket, c.head); + } + }, + + // The proxy can be in one of three modes: + // - "proxy": connections are proxied + // + // The initial mode is "proxy". + setMode: function (mode) { + var self = this; + self.mode = mode; + self._tryHandleConnections(); + } +}); + + + +// This is what http-proxy does +// XXX: We should submit connect support upstream +var setupSocket = function(socket) { + socket.setTimeout(0); + socket.setNoDelay(true); + + socket.setKeepAlive(true, 0); + + return socket; +}; + + +var proxyConnectMethod = function (req, socket, options, head, server, clb) { + if (req.method !== 'CONNECT') { + socket.destroy(); + return true; + } + + var tokens = req.url.split(':'); + + if (tokens.length != 2) { + runLog.log("Bad request: " + req.url); + socket.destroy(); + return true; + } + + var host = tokens[0]; + var port = tokens[1]; + + if (port != 443) { + runLog.log("Blocking request to non-443 port: " + req.url); + socket.destroy(); + return true; + } + + setupSocket(socket); + + // XXX: Needed? + // if (head && head.length) socket.unshift(head); + + var net = require('net'); + var proxySocket = net.createConnection(port, host); + setupSocket(proxySocket); + + socket.on('error', function (err) { + runLog.log("Error on socket: " + err); + proxySocket.end(); + }); + proxySocket.on('error', function (err) { + runLog.log("Error on proxySocket: " + err); + socket.end(); + }); + + proxySocket.on('connect', function(connect) { + runLog.log("Connection established to " + host + ":" + port); + socket.write("HTTP/1.0 200 Connection established\n\n"); + socket.pipe(proxySocket); + proxySocket.pipe(socket); + }); +}; + + +exports.HttpProxy = HttpProxy;