From 0aaaa866e4b8aae24eaff7ffdd1d7f7cea1d8d4e Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Sun, 22 May 2016 00:13:56 -0700 Subject: [PATCH] Add a whole bunch of magic for docker.exec to work --- src/apps.js | 19 ++++++++++++++++--- src/routes/apps.js | 12 +++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/apps.js b/src/apps.js index 12b2a8503..be8d92671 100644 --- a/src/apps.js +++ b/src/apps.js @@ -748,10 +748,14 @@ function exec(appId, options, callback) { var container = docker.connection.getContainer(app.containerId); - var execOptions = { + var execOptions = { AttachStdin: true, AttachStdout: true, AttachStderr: true, + // A pseudo tty is a terminal which processes can detect (for example, disable colored output) + // Creating a pseudo terminal also assigns a terminal driver which detects control sequences + // When passing binary data, tty must be disabled. In addition, the stdout/stderr becomes a single + // unified stream because of the nature of a tty (see https://github.com/docker/docker/issues/19696) Tty: options.tty, Cmd: cmd }; @@ -761,9 +765,18 @@ function exec(appId, options, callback) { var startOptions = { Detach: false, Tty: options.tty, - stdin: true // this is a dockerode option that enabled openStdin in the modem + // hijacking upgrades the docker connection from http to tcp. because of this upgrade, + // we can work with half-close connections (not defined in http). this way, the client + // can properly signal that stdin is EOF by closing it's side of the socket. In http, + // the whole connection will be dropped when stdin get EOF. + // https://github.com/apocas/dockerode/commit/b4ae8a03707fad5de893f302e4972c1e758592fe + hijack: true, + stream: true, + stdin: true, + stdout: true, + stderr: true }; - exec.start(startOptions, function(error, stream) { + exec.start(startOptions, function(error, stream /* in hijacked mode, this is a net.socket */) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); if (options.rows && options.columns) { diff --git a/src/routes/apps.js b/src/routes/apps.js index ce3ec11f5..6ae8da71d 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -357,7 +357,7 @@ function exec(req, res, next) { var tty = req.query.tty === 'true' ? true : false; - apps.exec(req.params.id, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) { + apps.exec(req.params.id, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, execStream) { if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app')); if (error && error.reason === AppsError.BAD_STATE) return next(new HttpError(409, error.message)); if (error) return next(new HttpError(500, error)); @@ -367,8 +367,14 @@ function exec(req, res, next) { req.clearTimeout(); res.sendUpgradeHandshake(); - duplexStream.pipe(res.socket); - res.socket.pipe(duplexStream); + // allowHalfOpen allows the client to close it's connection (end()) to signal a stdin EOF. If allowHalfOpen + // is not set, our socket will call end() automatically. Setting this to true, leaves our socket open + // and allows us to send the result of the command (made possible by docker hijacking feature). + res.socket.allowHalfOpen = true; + + // When tty is disabled, the execStream is a duplex stream. When enabled, it has stdout/stderr merged. + execStream.pipe(res.socket); + res.socket.pipe(execStream); }); }