记录一次 BUG 发现经历:使用 ssh2-stream sftp 上传文件出错的问题
场景
最近的一个业务需求,开发一款桌面端应用,用来部署企业级的物联网应用,需要使用本机进行目标主机进行命令交互。桌面端应用使用的是 electron 进行跨平台开发,远程命令交互使用的 ssh2 模块。应用中使用了 sftp 进行文件上传,而 sftp 功能是在 ssh2 依赖的 ssh2-stream 里的功能实现。
问题
在与后端开发人员进行正常愉快的联调开发时,突然发现之前已经通畅的业务流程突然被中断了,而中断的地方正好是文件上传的步骤。而更奇怪的是,文件上传有多个地方,第 1、2 个地方都正常执行,而第 3 个地方出现问题。上传失败打印的错误非常的模糊。
runtimes/ssh 201909042012340210000: upload src /Users/cloudcome/Downloads/allpktss.tar.gz +46ms
runtimes/ssh 201909042012340210000: upload dst /root/201909042012337810001.tar.gz +0ms
runtimes/ssh upload error Error: Failure
at SFTPStream._transform (/Users/cloudcome/development/app/node_modules/ssh2-streams/lib/sftp.js:412:27)
at SFTPStream.Transform._read (_stream_transform.js:189:10)
at SFTPStream._read (/Users/cloudcome/development/isyscore/app/node_modules/ssh2-streams/lib/sftp.js:183:15)
at SFTPStream.Transform._write (_stream_transform.js:177:12)
at doWrite (_stream_writable.js:417:12)
at writeOrBuffer (_stream_writable.js:401:5)
at SFTPStream.Writable.write (_stream_writable.js:301:11)
at Channel.ondata (_stream_readable.js:713:22)
at Channel.emit (events.js:200:13)
at addChunk (_stream_readable.js:294:12) {
code: 4,
lang: ''
} +6ms
排查
查看错误上下文
错误的原因只有 Failure 7 个字母。
if (pktType === RESPONSE.STATUS) {
/*
uint32 error/status code
string error message (ISO-10646 UTF-8)
string language tag
*/
var code = readInt(buffer, 4, this, callback);
if (code === false) return;
if (code === STATUS_CODE.OK) {
cb();
} else {
// We borrow OpenSSH behavior here, specifically we make the
// message and language fields optional, despite the
// specification requiring them (even if they are empty). This
// helps to avoid problems with buggy implementations that do
// not fully conform to the SFTP(v3) specification.
var msg;
var lang = '';
if (buffer.length >= 12) {
msg = readString(buffer, 8, 'utf8', this, callback);
if (msg === false) return;
if (buffer._pos + 4 < buffer.length) {
lang = readString(buffer, buffer._pos, 'ascii', this, callback);
if (lang === false) return;
}
}
var err = new Error(msg || STATUS_CODE_STR[code] || 'Unknown status');
err.code = code;
err.lang = lang;
cb(err);
}
}
其他 STATUS_CODE_STR
定义为
var STATUS_CODE_STR = {
0: 'No error',
1: 'End of file',
2: 'No such file or directory',
3: 'Permission denied',
4: 'Failure',
5: 'Bad message',
6: 'No connection',
7: 'Connection lost',
8: 'Operation unsupported'
};
执行的 code 是 4,对应结果就是 Failure
,定位上下文并没有其他地方可以明确指定错误的原因。
进入目标主机查看文件 /root/201909042012337810001.tar.gz
是否存在,结果显示的是目录!!
[root@master ~]# ll
总用量 13060
drwxr-xr-x 2 root root 6 9月 4 08:12 201909042012337810001.tar.gz
然后接下来一段时间,是搜索如何将 d
修改成 -
的问题,其他 sftp 的使用方法是:
sftp.fastPut(src, dest, { step, mode }, callback);
而 mode
参数并不能决定路径的属性,遗憾结束。
搜索引擎
通过谷歌搜各种关键字 sfp remotePath directory
、ssh2-stream fastPut
,其中有一个 issue 跟我遇到的是一样的,结果提问题的人自问自答:
I think I put this under the wrong project. Disregard.
代码历史回顾
在上一个版本的时候,可以明确的是,第 3 步是正常执行的,而目前是失败的,因为从代码历史上能够回顾蛛丝马迹。回顾代码 N 遍,提交代码量不多,并且也没有任何影响的地方。最后还讲当前代码回退到上一个版本,惊奇的是,该错误竟然重现了。
代码历史回顾还拉上同事进行一同审阅,遗憾结束。
错误重现
将错写的代码进行最小化,执行的时候,意外的报错的。
const Client = require('ssh2');
const options = {
host: '...',
port: 22,
username: 'root',
password: '...'
};
const src = '/Users/cloudcome/Downloads/test.tar.gz';
const dest = '/root/test-upload';
const client = new Client();
client.on('ready', function() {
client.sftp(function(err, sftp) {
if (err) {
throw err;
}
console.log('ready');
const step = function() {
console.log(arguments);
};
sftp.fastPut(src, dest, { step }, function(err) {
if (err) {
throw err;
}
console.log('upload success');
});
});
});
client.connect(options);
最后报错了,检查了下,dest
的值竟然是目标主机目录。将 dest
修改为 /root/test-upload/abc.tar.gz
,如你所愿,结果是成功的。
错误解决
很明显了,文件上传的时候,目标路径 /root/201909042012337810001.tar.gz
已经是个目录了,所以一直上传失败。跟后端确认逻辑,是因为我们的业务有这样的一个需求,目标路径需要提前判断是否满足条件,然后再进行上传。而后端多做了一步,将该路径新建为目录了。因此导致了多次上传失败。
总结
发现错误一定要心平气和,从最直接的方式开始溯源排查,并且逐级定位,一定能解决问题。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论