粘贴图片

场景

最近的一个业务需求,开发一款桌面端应用,用来部署企业级的物联网应用,需要使用本机进行目标主机进行命令交互。桌面端应用使用的是 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 directoryssh2-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 已经是个目录了,所以一直上传失败。跟后端确认逻辑,是因为我们的业务有这样的一个需求,目标路径需要提前判断是否满足条件,然后再进行上传。而后端多做了一步,将该路径新建为目录了。因此导致了多次上传失败。

总结

发现错误一定要心平气和,从最直接的方式开始溯源排查,并且逐级定位,一定能解决问题。

0 条评论
作者
状态
  • 发布于 ,0 次更新
  • 被浏览 114 次
  • 被评论 0 次
  • 隶属于 文章 版块
  • 归纳于 NodeJS 分类
二维码
目录