使用Promises

时间:2018-01-11 02:52:45

标签: javascript node.js ssh promise

我正在构建一个node.js应用程序,该应用程序在生产中将充当许多服务器的SSH客户端,其中一些服务器在任何给定时间都可能无法访问。我试图在启动时尝试编写一个试图在其配置中为每个客户端运行SSH命令的函数,而且我无法处理成功的会话和那些以错误结束的会话。我在一个承诺中包装了一个ssh2客户端。如果我删除第三个(垃圾)服务器只有成功结果,这工作正常!见输出:

STDOUT: Hello World

STDOUT: Hello World

Session closed
Session closed
Successful session: Hello World,Hello World

但如果其中一个连接超时,即使我处理错误,我也无法保留任何数据。看起来错误消息会覆盖所有已解决的承诺

Successful session: Error: Timed out while waiting for handshake,Error: 
Timed out while waiting for handshake,Error: Timed out while waiting 
for handshake

这是我的代码,请原谅我,如果这有点分散,因为为了这个问题,我已经合并了一些模块。我的目标是保持成功会话的数据并优雅地处理失败。

var Client = require('ssh2').Client;

 const labs = {
    "ny1": "192.168.1.2",
    "ny2": "192.168.1.3",
    "ny3": "1.1.1.1"
};

function checkLabs() {
    let numLabs = Object.keys(labs).length;
    let promises = [];

    for(i=0;i<numLabs;i++){
        let labName = Object.keys(labs)[i];
        promises.push(asyncSSH("echo 'Hello World'", labs[labName]));
    }

    Promise.all(promises.map(p => p.catch(e => e)))
        .then(results => console.log("Successful session: " + results))
        .catch(e => console.log("Error! " + e));
}

var sendSSH = function (command, dest, callback) {
var conn = new Client();

        conn.on('ready', function() {
            return conn.exec(command, function(err, stream) {
                if (err) throw err;
                stream.on('data', function(data) {
                    callback(null, data);
                    console.log('STDOUT: ' + data);
                }).stderr.on('data', function(data){
                    callback(err);
                    console.log('STDERR: ' + data);
                }).on('close', function(err) {
                    if(err) {
                        console.log('Session closed due to error');
                    } else {
                        console.log('Session closed');
                    }
                });
                stream.end('ls -l\nexit\n');
            });
        }).on('error', function(err){
            callback(err);
        }).connect({
            host: dest,
            port: 22,
            username: 'root',
            readyTimeout: 10000,
            privateKey: require('fs').readFileSync('link-to-my-key')
        });
};

function asyncSSH(command, dest) {
    return new Promise(function(resolve, reject){
        sendSSH(command, dest, function(err,data) {
            if (!err) {
                resolve(data);
            } else {
                reject(err);
            }
        });
    });
}

checklabs();

如何更好地使用此承诺包装器来处理来自ssh2客户端的任何错误?任何提示都表示赞赏。

1 个答案:

答案 0 :(得分:1)

为了充分利用每个连接,您可以(并且可以说应该)单独宣传:

  • 每个Client()实例的实例化
  • 每个实例的conn.exec()方法(以及所需的任何其他异步方法)

这将允许Client()的每个实例与不同的命令一起重用(尽管在此示例中不需要)。

通过调用client_.end(),您还应确保在作业完成后断开每个套接字。为此,一个&#34;处理器模式&#34;推荐。

考虑到这些要点并做了一些假设,这就是我最终得到的结果:

var Client = require('ssh2').Client;

const labs = {
    "ny1": "192.168.1.2",
    "ny2": "192.168.1.3",
    "ny3": "1.1.1.1"
};

function checkLabs() {
    let promises = Object.keys(labs).map((key) => {
        return withConn(labs[key], (conn) => { 
            return conn.execAsync("echo 'Hello World'")
            .catch((e) => "Error: " + e.message); // catch in order to immunise the whole process against any single failure.
                                                  // and inject an error message into the success path.
        });
    });
    Promise.all(promises)
    .then(results => console.log("Successful session: " + results))
    .catch(e => console.log("Error! " + e.message)); // with individual errors caught above, you should not end up here.
}

// disposer pattern, based on https://stackoverflow.com/a/28915678/3478010
function withConn(dest, work) {
    var conn_;
    return getConnection(dest).then((conn) => {
        conn_ = conn;
        return work(conn);
    }).then(() => {
        if(conn_) {
            conn_.end(); // on success, disconnect the socket (ie dispose of conn_).
        }
    }, () => {
        if(conn_) {
            conn_.end(); // on error, disconnect the socket (ie dispose of conn_).
        }
    });
    // Note: with Bluebird promises, simplify .then(fn,fn) to .finally(fn).
}

function getConnection(dest) {
    return new Promise((resolve, reject) => {
        let conn = promisifyConnection(new Client());
        conn.on('ready', () => {
            resolve(conn);
        })
        .on('error', reject)
        .connect({
            host: dest,
            port: 22,
            username: 'root',
            readyTimeout: 10000,
            privateKey: require('fs').readFileSync('link-to-my-key')
        });
    });
}

function promisifyConnection(conn) {
    conn.execAsync = (command) => { // promisify conn.exec()
        return new Promise((resolve, reject) => {
            conn.exec(command, (err, stream) => {
                if(err) {
                    reject(err);
                } else {
                    let streamSegments = []; // array in which to accumulate streamed data
                    stream.on('close', (err) => {
                        if(err) {
                            reject(err);
                        } else {
                            resolve(streamSegments.join('')); // or whatever is necessary to combine the accumulated stream segments
                        }
                    }).on('data', (data) => {
                        streamSegments.push(data);
                    }).stderr.on('data', function(data) {
                        reject(new Error(data)); // assuming `data` to be String
                    });
                    stream.end('ls -l\nexit\n'); // not sure what this does?
                }
            });
        });
    };
    // ... promisify any further Client methods here ...
    return conn;
}

注意:

  • conn.exec()的宣传包括假设可以在一系列片段(例如分组)中接收数据。如果此假设无效,则streamSegments数组的需求将消失。
  • getConnection()promisifyConnection()可以作为一个功能编写,但通过单独的功能,您可以更轻松地查看正在进行的操作。
  • getConnection()promisifyConnection()将所有杂乱的内容远离应用程序代码。