diff --git a/src/dockerproxy.js b/src/dockerproxy.js index 41963056b..9b4d50557 100644 --- a/src/dockerproxy.js +++ b/src/dockerproxy.js @@ -6,48 +6,76 @@ exports = module.exports = { }; var assert = require('assert'), + bodyParser = require('body-parser'), config = require('./config.js'), debug = require('debug')('box:dockerproxy'), - http = require('http'); + http = require('http'), + net = require('net'); var gServer = null; function start(callback) { assert.strictEqual(typeof callback, 'function'); + var parser = bodyParser.json(); + function interceptor(req, res) { - debug(`request: ${req.method} ${req.url}`); + console.log(`request: ${req.method} ${req.url}`, req.body); + + if (req.method === 'POST' && req.url.match(/\/containers\/create/)) { + debug('patching container creation'); + } + return false; } debug(`startDockerProxy: starting proxy on port ${config.get('dockerProxyPort')}`); + gServer = http.createServer(function (req, res) { if (interceptor(req, res)) return; - // rejectUnauthorized should not be required but it doesn't work without it var options = { socketPath: '/var/run/docker.sock', method: req.method, path: req.url, - headers: req.headers, - rejectUnauthorized: false + headers: req.headers }; var dockerRequest = http.request(options, function (dockerResponse) { res.writeHead(dockerResponse.statusCode, dockerResponse.headers); - dockerResponse.on('error', console.error); + + // Force node to send out the headers, this is required for the /container/wait api to make the docker cli proceed + res.write(' '); + + dockerResponse.on('error', function (error) { console.error('dockerResponse error:', error); }); dockerResponse.pipe(res, { end: true }); }); - req.on('error', console.error); + req.on('error', function (error) { console.error('req error:', error); }); + if (!req.readable) { dockerRequest.end(); } else { req.pipe(dockerRequest, { end: true }); } - }).listen(config.get('dockerProxyPort'), callback); + + gServer.on('upgrade', function (req, client, head) { + // Create a new tcp connection to the TCP server + var remote = net.connect('/var/run/docker.sock', function () { + // two-way pipes between client and docker daemon + client.pipe(remote).pipe(client); + + // resend the upgrade event to the docker daemon, so it responds with the proper message through the pipes + remote.write(req.method + ' ' + req.url + ' HTTP/1.1\r\n' + + `Host: ${req.headers.host}\r\n` + + 'Connection: Upgrade\r\n' + + 'Upgrade: tcp\r\n' + + '\r\n' + ); + }); + }); } function stop(callback) { diff --git a/src/test/dockerproxy-test.js b/src/test/dockerproxy-test.js new file mode 100644 index 000000000..f8d1f8933 --- /dev/null +++ b/src/test/dockerproxy-test.js @@ -0,0 +1,42 @@ +/* jslint node:true */ +/* global it:false */ +/* global describe:false */ +/* global before:false */ +/* global after:false */ + +'use strict'; + +var dockerProxy = require('../dockerproxy.js'), + config = require('../config.js'), + exec = require('child_process').exec, + expect = require('expect.js'); + +const DOCKER = `docker -H tcp://localhost:${config.get('dockerProxyPort')} `; + +describe('Cloudron', function () { + this.timeout(1000000); + + before(dockerProxy.start); + after(dockerProxy.stop); + + it('can get info', function (done) { + exec(DOCKER + ' info', function (error, stdout, stderr) { + expect(error).to.be(null); + expect(stdout).to.contain('Containers:'); + expect(stderr).to.be.empty(); + done(); + }); + }); + + it('can create container', function (done) { + var cmd = DOCKER + ` run ubuntu "/bin/bash" "-c" "echo 'hello'"`; + console.log(cmd) + exec(cmd, function (error, stdout, stderr) { + console.log(error, stdout, stderr) + expect(error).to.be(null); + expect(stdout).to.contain('hello'); + expect(stderr).to.be.empty(); + done(); + }); + }); +});