Javascript 使用承诺处理来自ssh2的成功/错误响应
我正在构建一个node.js应用程序,在生产中它将充当许多服务器的SSH客户端,其中一些服务器可能在任何给定时间都无法访问。我试图编写一个函数,在启动时尝试在每个客户机的配置中运行SSH命令,但我无法处理成功的会话和以错误结束的会话。我用承诺包装了一个ssh2客户机。如果我删除了第三个(垃圾)服务器,并且只有成功的结果,这就可以了!请参见输出:Javascript 使用承诺处理来自ssh2的成功/错误响应,javascript,node.js,ssh,promise,Javascript,Node.js,Ssh,Promise,我正在构建一个node.js应用程序,在生产中它将充当许多服务器的SSH客户端,其中一些服务器可能在任何给定时间都无法访问。我试图编写一个函数,在启动时尝试在每个客户机的配置中运行SSH命令,但我无法处理成功的会话和以错误结束的会话。我用承诺包装了一个ssh2客户机。如果我删除了第三个(垃圾)服务器,并且只有成功的结果,这就可以了!请参见输出: STDOUT: Hello World STDOUT: Hello World Session closed Session closed Succ
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();
var-Client=require('ssh2')。客户端;
常数实验室={
“ny1”:“192.168.1.2”,
“ny2”:“192.168.1.3”,
“ny3”:“1.1.1.1”
};
函数checkLabs(){
设numLabs=Object.keys(labs).length;
让承诺=[];
对于(i=0;i p.catch(e=>e)))
.then(results=>console.log(“成功会话:+results))
.catch(e=>console.log(“Error!”+e));
}
var sendsh=函数(命令、目标、回调){
var conn=新客户端();
连接on('ready',function(){
返回conn.exec(命令、函数(错误、流){
如果(错误)抛出错误;
stream.on('data',函数(data){
回调(空,数据);
log('STDOUT:'+数据);
}).stderr.on('data',函数(data){
回调(err);
log('STDERR:'+数据);
}).on('close',函数(err){
如果(错误){
log('由于错误而关闭了会话');
}否则{
console.log(“会话已关闭”);
}
});
stream.end('ls-l\nexit\n');
});
}).on('error',函数(err){
回调(err);
}).连接({
主持人:dest,,
港口:22,
用户名:'根',
readyTimeout:10000,
privateKey:require('fs')。readFileSync('link-to-my-key'))
});
};
函数asynchssh(命令,dest){
返回新承诺(功能(解决、拒绝){
sendsh(命令、目标、函数(错误、数据){
如果(!err){
解析(数据);
}否则{
拒绝(错误);
}
});
});
}
checklabs();
如何更好地使用此承诺包装器来处理来自ssh2客户端的任何错误?任何提示都非常感谢。要从每个连接中获得最佳使用,您可以(也可以说应该)单独承诺:
- 每个Client()实例的安装
- 每个实例的
方法(以及所需的任何其他异步方法)conn.exec()
Client()
的每个实例(尽管在本例中不是必需的)
您还应该确保在完成每个套接字的工作后,通过调用client.end()
断开其连接。为此,建议采用“处置器模式”
考虑到这些观点和一些假设,我得出以下结论:
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()
labs
而不是const labs={…
您只能解析或拒绝一个承诺一次,但解析多次,因为on data发生多次。这不会导致错误,但在第一次解析之后的任何解析调用都将被忽略。相反,您应该在close和on data上解析保存数组中的任何数据,以便在close时使用该数组进行解析。这看起来不像是任何可以实际运行并演示问题的代码。@HMR这非常有用,谢谢。我在一个单独的文件中使用了define方法作为配置,也许我应该将其称为const。另外,如果我删除了ssh2代码并在psuedo代码中演示了success/err,这将作为演示更有用。I猜测处理ssh2行为是我的问题的核心,我需要花更多的时间处理该部分。为了将问题提高一点,我这里的问题是我需要使用同一段ssh2代码进行多个事务。每个事务都已关闭,或者成功关闭,在这种情况下,(“数据”
API导致close
或打开('error'
在连接失败的情况下。每个事务可能需要10秒的超时时间。我不在乎它们是同步发生的还是异步发生的,我只需要保存每个事务的数据,并优雅地处理发生的任何错误,恢复任何剩余的ssh会话。看起来有点奇怪,您应该走得更远作为“Hello World,Hello World”,只有一个客户端实例()
。我原以为您需要(a)每个主机一个实例,或者(b)执行串联连接。如果回显消息包含一些独特的内容,例如ny1
/ny2
键,会发生什么情况。这很好,非常感谢您的设计。Th