From d376b796226c9c3a33758dadf93a7f850fe2f002 Mon Sep 17 00:00:00 2001 From: Koong Kyungmi Date: Tue, 10 Nov 2015 20:32:42 +0900 Subject: [PATCH 1/2] [IMPROVEMENT] Change the terminal protocol [DESC.] - IDE terminal module is changed from terminal.js to term.js. So adjust the protocol to the new client. - Remove the `socket.io-stream` dependency from `package.json` Thanks to WSDK team. Related with the commit#b57958795c3104f987edb78d2a5db5044cbb45b1 --- src/server/fs/lib/console-manager.js | 319 +++++++++++++++------------ src/server/package.json | 1 - 2 files changed, 173 insertions(+), 147 deletions(-) diff --git a/src/server/fs/lib/console-manager.js b/src/server/fs/lib/console-manager.js index 6b7e82e..120b1b2 100644 --- a/src/server/fs/lib/console-manager.js +++ b/src/server/fs/lib/console-manager.js @@ -1,12 +1,12 @@ /* * Copyright (c) 2012-2015 S-Core Co., Ltd. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,9 +23,10 @@ var ptyjs = require('pty.js'); var path = require('path'); var _ = require('lodash'); var express = require('express'); +var url = require('url'); +var async = require('async'); var socketio = require('socket.io'); -var ss = require('socket.io-stream'); var authMgr = require('../../common/auth-manager'); var utils = require('../../common/utils'); @@ -38,10 +39,10 @@ var ClientError = utils.ClientError; var ServerError = utils.ServerError; var execTable = {}; + function removeProc(proc) { if (execTable[proc.pid]) { - logger.debug('Remove proc', proc.pid); - + logger.debug('remove proc', proc.pid); proc.removeAllListeners(); if (proc._timeoutId) { clearTimeout(proc._timeoutId); @@ -64,7 +65,7 @@ function termProc(proc) { cexec.kill('SIGKILL', function (err) { if (err) { /* TODO: retry termProc again */ - logger.warn('Failed to kill container exec', err); + logger.warning('failed to kill container exec', err); } removeProc(proc); }); @@ -72,14 +73,14 @@ function termProc(proc) { } function addProc(proc, cexec, timeout) { - logger.debug('Add proc', proc.pid); + logger.debug('add proc', proc.pid); proc._cexec = cexec; cexec.setProc(proc); proc._stdout = ''; proc._stderr = ''; if (timeout) { proc._timeoutId = setTimeout(function () { - logger.debug('Timeout proc', proc.pid); + logger.debug('timeout proc', proc.pid); termProc(proc); }, config.services.fs.exec.timeoutSecs * 1000); } @@ -98,7 +99,7 @@ function startProc(cwdRsc, cexec, callback) { var cmd = cexec.getCmd(); var args = cexec.getArgs(); - logger.debug('Exec start', cmd, args); + logger.debug('exec start', cmd, args); // TODO env will be removed var env = _.clone(process.env); @@ -125,11 +126,6 @@ function startProc(cwdRsc, cexec, callback) { proc.on('exit', function (code) { logger.debug('Exec close', proc.pid, 'code:'+code, 'stdout:'+proc._stdout, 'stderr:'+proc._stderr); removeProc(proc); - /* - if (code === 0) { - return callback(code, proc._stdout, proc._stderr); - } - */ if (code === null) { return callback(new Error('Abnormal exit'), proc._stdout, proc._stderr); } @@ -161,28 +157,176 @@ function exec(cwdUrl, cmdInfo, callback) { } containerExec(cwdRsc.wfs, cmd, args, - {cwd: cwdRsc.pathname}, function (err, cexec) { + {cwd: cwdRsc.pathname}, function (err, cexec) { + if (err) { + return callback(err); + } + startProc(cwdRsc, cexec, callback); + }); +} + +function handleNewEvent(socket, options, cb) { + var req = socket.request; + var fsid = req.query.fsid; + var userId = req.user.userId; + + logger.debug('new event handler'); + async.waterfall([ + function (next) { + /* get wfs by fsid */ + logger.debug('get wfs', fsid); + fsMgr.getWfsByFsid(fsid, next); + }, + function (wfs, next) { + /* get wfs owner */ + logger.debug('get owner', fsid); + wfs.getOwner(_.partialRight(next, wfs)); + }, + function (ownerId, wfs, next) { + /* check wfs access permission */ + if (ownerId === userId) { + return next(null, wfs); + } + return next(new Error('User(' + + userId + ') has no permission to FS(' + fsid + ')')); + }, + function (wfs, next) { + logger.debug('get container', fsid); + containerExec(wfs, null, null, {interactive: true}, next); + }, + function (cexec, next) { + /* execute terminal container */ + var pid; + var cwd = options.cwd; + var cmd = cexec.getCmd(); + var args = cexec.getArgs(); + + logger.debug('start terminal', cmd, args, options); + var pty = ptyjs.spawn(cmd, args, { + name: 'xterm-color', + cols: options.cols, + rows: options.rows + }); + + pid = pty.pid; + addProc(pty, cexec, false); + + socket.on('data', function(data) { + pty.write(data); + }); + + socket.on('resize', function (col, row) { + pty.resize(col, row); + }); + + socket.on('disconnect', function () { + logger.debug('disconnect terminal', pid); + termProc(pty); + }); + + pty.on('exit', function () { + logger.debug('exit terminal', pid); + cexec.setProc(null); + socket.disconnect(true); + }); + + return next(null, pty, cexec, cwd); + }, + function (pty, cexec, cwd, next) { + /* change directory & clear terminal */ + var pos; + var msg = ''; + var KEYWORD = 'WSDKTERMINAL'; + var cmd; + var cpid; + var STATE = Object.freeze({ + SEARCH:0, + NEWLINE:1, + CPID:2, + DONE:3}); + var state = STATE.SEARCH; + + if (!cwd) { + cwd = ''; + } + cwd = path.join('.', cwd); + + cmd = 'cd ' + cwd + ';'; + cmd += 'echo ' + KEYWORD + ';'; + cmd += 'echo ${BASHPID};'; + cmd += 'history -c; history -r\r'; + KEYWORD += '\r'; + + pty.pause(); + pty.write(cmd); + var dropMsg = function() { + /* accumulate all data from lxc */ + var c; + while (null !== (c = pty.socket.read())) { + msg += c; + } + + /* find keyword */ + while ((state !== STATE.DONE) && + (pos = msg.indexOf(KEYWORD)) !== -1) { + /* parse & get cpid */ + if (state === STATE.CPID) { + cpid = parseInt(msg.substr(0, pos)); + cexec.setCPid(cpid); + } + + /* discard data with keyword */ + msg = msg.substr(pos + KEYWORD.length); + state++; + if (state === STATE.NEWLINE) { + KEYWORD = '\n'; + } + } + + if (state === STATE.DONE) { + pty.removeListener('readable', dropMsg); + pty.resume(); + if (msg.length !== 0) { + pty.write(msg); + } + return next(null, pty); + } + }; + logger.debug('setup terminal'); + pty.on('readable', dropMsg); + } + ], function (err, pty) { if (err) { - return callback(err); + logger.error('terminal failed', err); + socket.disconnect(true); + } else { + /* bind to client */ + logger.debug('bind terminal to client'); + pty.on('data', function(data) { + socket.emit('data', data); + }); + cb(); } - startProc(cwdRsc, cexec, callback); }); } function registerTerminalService(httpServer) { if (!container.supportTerminal()) { logger.debug('Container did not support terminal. ' + - 'So terminal service cannot be run.'); + 'So terminal service cannot be run.'); return; } + var io = socketio(httpServer); - var customResponse = Object.create(express.response); io.use(function (socket, next) { var req = socket.request; + var customResponse = Object.create(express.response); + req.parsedUrl = require('url').parse(req.url); req.query = require('querystring').parse(req.parsedUrl.query); - logger.debug('io.use: ', req.url, req.headers); + + logger.debug('io.use:', req.url); customResponse.send = function (result) { logger.debug('custom send:', arguments, this); if (this.statusCode >= 400) { @@ -191,133 +335,14 @@ function registerTerminalService(httpServer) { next(); } }; + customResponse.sendfail = function (err) { + next(err); + }; authMgr.ensureLogin(req, customResponse, next); }); io.of('pty').on('connection', function (socket) { - ss(socket).on('new', function (stream, options) { - var req = socket.request; - var async = require('async'); - var fsid = req.query.fsid; - var userId = req.user.userId; - async.waterfall([ - function (next) { - /* get wfs by fsid */ - fsMgr.getWfsByFsid(fsid, next); - }, - function (wfs, next) { - /* get wfs owner */ - wfs.getOwner(_.partialRight(next, wfs)); - }, - function (ownerId, wfs, next) { - /* check wfs access permission */ - if (ownerId === userId) { - return next(null, wfs); - } - return next(new Error('User(' + userId + ') has no permission to FS(' + fsid + ')')); - }, - function (wfs, next) { - containerExec(wfs, null, null, {interactive: true}, next); - }, - function (cexec, next) { - /* execute terminal container */ - var pid; - var cwd = options.cwd; - var cmd = cexec.getCmd(); - var args = cexec.getArgs(); - - var pty = ptyjs.spawn(cmd, args, { - name: 'xterm-color', - cols: options.columns, - rows: options.rows - }); - - pid = pty.pid; - logger.debug('Start terminal: ', pid, cmd, args, options); - addProc(pty, cexec, false); - socket.on('disconnect', function () { - logger.debug('Disconnect terminal: ', pid); - termProc(pty); - }); - pty.on('exit', function () { - logger.debug('Exit terminal: ', pid); - cexec.setProc(null); - socket.disconnect(true); - }); - - return next(null, pty, cexec, cwd); - }, - function (pty, cexec, cwd, next) { - /* change directory & clear terminal */ - var pos; - var msg = ''; - var KEYWORD = 'WSDKTERMINAL'; - var cmd; - var cpid; - var STATE = Object.freeze({ - SEARCH:0, - NEWLINE:1, - CPID:2, - DONE:3}); - var state = STATE.SEARCH; - - if (!cwd) { - cwd = ''; - } - cwd = path.join('.', cwd); - - cmd = 'cd ' + cwd + ';'; - cmd += 'echo ' + KEYWORD + ';'; - cmd += 'echo ${BASHPID};'; - cmd += 'history -c; history -r\r'; - KEYWORD += '\r'; - - pty.pause(); - pty.write(cmd); - var dropMsg = function() { - /* accumulate all data from lxc */ - var c; - while (null !== (c = pty.socket.read())) { - msg += c; - } - - /* find keyword */ - while ((state !== STATE.DONE) && - (pos = msg.indexOf(KEYWORD)) !== -1) { - /* parse & get cpid */ - if (state === STATE.CPID) { - cpid = parseInt(msg.substr(0, pos)); - cexec.setCPid(cpid); - } - - /* discard data with keyword */ - msg = msg.substr(pos + KEYWORD.length); - state++; - if (state === STATE.NEWLINE) { - KEYWORD = '\n'; - } - } - - if (state === STATE.DONE) { - pty.removeListener('readable', dropMsg); - pty.resume(); - if (msg.length !== 0) { - stream.write(msg); - } - return next(null, pty); - } - }; - pty.on('readable', dropMsg); - }, - ], function (err, pty) { - if (err) { - logger.debug('terminal failed: ', err.message); - socket.disconnect(true); - } else { - /* bind to client */ - stream.pipe(pty).pipe(stream); - } - }); - }); + logger.debug('pty connection'); + socket.on('create', _.partial(handleNewEvent, socket)); }); logger.debug('Terminal service is running'); @@ -339,7 +364,6 @@ exports.router.post('/webida/api/fs/exec/:fsid/*', function (req, res) { var fsid = req.params.fsid; var cwdPath = path.join('/', req.params[0]); - logger.info('exec path = ', cwdPath); var cwdUrl = 'wfs://' + fsid + cwdPath; var cmdInfo = JSON.parse(req.body.info); var sessionID = req.body.sessionID; @@ -348,15 +372,18 @@ exports.router.post('/webida/api/fs/exec/:fsid/*', return res.sendfail(new ClientError('invalid uid')); } + logger.debug('exec path=' + cwdPath, cmdInfo); fsMgr.checkLock(fsid, cwdPath, cmdInfo, function(err) { if (err) { return res.sendfail(err); } + logger.debug('exec check lock succeed'); exec(cwdUrl, cmdInfo, function (err, stdout, stderr, ret) { if (err) { return res.sendfail(err); } fsMgr.updateByExec(cmdInfo,uid, fsid, cwdPath, cwdUrl, sessionID, function() { + logger.debug('exec notification done'); return res.sendok({stdout: stdout, stderr: stderr, ret:ret}); }); }); diff --git a/src/server/package.json b/src/server/package.json index b249fcd..2b2b517 100644 --- a/src/server/package.json +++ b/src/server/package.json @@ -72,7 +72,6 @@ "mkdirp": "0.5.0", "guid": "0.0.12", "replace": "0.2.9", - "socket.io-stream": "^0.6.0", "terminal.js": "^1.0.3", "lodash": "3.9.3", "data-mapper": "*", From bc6397ab4b0c96a54bc83ffb1c110eae63e890a6 Mon Sep 17 00:00:00 2001 From: Koong Kyungmi Date: Tue, 10 Nov 2015 20:47:01 +0900 Subject: [PATCH 2/2] [TASK] follow jshint --- src/server/fs/lib/console-manager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/fs/lib/console-manager.js b/src/server/fs/lib/console-manager.js index 120b1b2..9213e60 100644 --- a/src/server/fs/lib/console-manager.js +++ b/src/server/fs/lib/console-manager.js @@ -23,7 +23,6 @@ var ptyjs = require('pty.js'); var path = require('path'); var _ = require('lodash'); var express = require('express'); -var url = require('url'); var async = require('async'); var socketio = require('socket.io');