【问题标题】:Reading file from SFTP server using Node.js and SSH2使用 Node.js 和 SSH2 从 SFTP 服务器读取文件
【发布时间】:2016-09-19 09:21:53
【问题描述】:

我在 Node.js 中处理读取流时遇到了一个非常奇怪的问题。我正在使用 SSH2 在我和 sftp 服务器之间创建一个 sftp 连接。然后我尝试从 sftp 流创建一个读取流。从读取流发出的“数据”事件中,我将数据附加到数组中。当读取流的“关闭”事件发生时,我会调用 Buffer.concat 来创建连接所有已检索到一个缓冲区的数据块。这与此处在堆栈溢出时提出的其他问题中描述的技术相同。例如here。但是,我无法使用检索到的数据。似乎缓冲区的大小比我尝试检索的文件小 32 个字节(从计算检索到的数据的长度)。这可能与我的 SFTP 连接有关吗?或者我如何创建我的阅读流?

如果重要的话,文件是 zip 类型的。当我在读取文件以缓冲后尝试解压缩文件(在 node.js 中并手动)时,它不起作用。

经过调查,我发现:

  1. 当我对文件使用 readdir 时,文件的大小是正确的。
  2. 对我的开发 FTP 服务器使用 FTP (JSFTP) 使用上述相同的技术可以正常工作。

感谢任何建议!

这是我的代码:

        var Client = require('ssh2').Client;
        var m_ssh2Credentials = {
           host: config.ftpHostName,
           port: config.ftpPort,
           username: config.ftpUser,
           password: config.ftpPassword,
           readyTimeout: 20000,
           algorithms: { cipher: ["3des-cbc", "aes256-cbc", "aes192-cbc","aes128-cbc"]}
        };
        ...
        var conn = new Client();
        var dataLength = 0;
        conn.on('ready', function() {
            conn.sftp(function(err, sftp) {
                if (err) {
                    writeToErrorLog("downloadFile(): Failed to open SFTP connection.");
                } else {
                    writeToLog("downloadFile(): Opened SFTP connection.");
                }

                var streamErr = "";
                var dataLength = 0;
                var stream = sftp.createReadStream(config.ftpPath + "/" + m_fileName)
                stream.on('data', function(d){
                    data.push(d);
                    dataLength += d.length;
                });
                .on('error', function(e){
                    streamErr = e;
                })
                .on('close', function(){
                    if(streamErr) {
                        writeToErrorLog("downloadFile(): Error retrieving the file: " + streamErr);
                    } else {
                        writeToLog("downloadFile(): No error using read stream.");
                        m_fileBuffer = Buffer.concat(data, dataLength);
                        writeToLog("Data length: " + dataLength);

                        writeToLog("downloadFile(): File saved to buffer.");
                    }
                    conn.end();
                });
            })
        })
        .on('error', function(err) {
            writeToErrorLog("downloadFile(): Error connecting: " + err);
        }).connect(m_ssh2Credentials);

【问题讨论】:

    标签: javascript node.js buffer sftp readable


    【解决方案1】:

    所以经过大量调查后,我终于意识到在“数据”事件中传输的最后一位数据有问题。据我了解,这似乎是读取流实现中的一个错误。通过使用 SSH2 库中更简单的函数(open、fstat、read),我能够解决这个问题。这个解决方案对我有用。如果其他人遇到同样的问题,想分享解决方案。

    工作代码:

    sftp.open(config.ftpPath + "/" + m_fileName, "r", function(err, fd) {
    sftp.fstat(fd, function(err, stats) {
        var bufferSize = stats.size,
            chunkSize = 16384,
            buffer = new Buffer(bufferSize),
            bytesRead = 0,
            errorOccured = false;
    
        while (bytesRead < bufferSize && !errorOccured) {
            if ((bytesRead + chunkSize) > bufferSize) {
                chunkSize = (bufferSize - bytesRead);
            }
            sftp.read(fd, buffer, bytesRead, chunkSize, bytesRead, callbackFunc);
            bytesRead += chunkSize;
        }
    
        var totalBytesRead = 0;
        function callbackFunc(err, bytesRead, buf, pos) {
            if(err) {
                writeToErrorLog("downloadFile(): Error retrieving the file.");
                errorOccured = true;
                sftp.close(fd);
            }
            totalBytesRead += bytesRead;
            data.push(buf);
            if(totalBytesRead === bufferSize) {
                m_fileBuffer = Buffer.concat(data);
                writeToLog("downloadFile(): File saved to buffer.");
                sftp.close(fd);
                m_eventEmitter.emit(' downloadFile_Completed ');
            }
        }
    })
    });   
    

    【讨论】:

      【解决方案2】:

      如果字节大小(或块大小)不是强制性的,而您只需要获取文件,猜测有一个更好的更轻更快的方式(是的...... nodejs 方式!)。这是我用来复制文件的方式:

      function getFile(remoteFile, localFile) {
           conn.on('ready', function () {
           conn.sftp(function (err, sftp) {
               if (err) throw err;
               var rstream = sftp.createReadStream(remoteFile);
               var wstream = fs.createWriteStream(localFile);
               rstream.pipe(wstream);
               rstream.on('error', function (err) { // To handle remote file issues
                   console.log(err.message);
                   conn.end();
                   rstream.destroy();
                   wstream.destroy();
               });
               rstream.on('end', function () {
                   conn.end();
               });
               wstream.on('finish', function () {
                   console.log(`${remoteFile} has successfully download to ${localFile}!`);
               });
           });
         }).connect(m_ssh2Credentials);
      }
      

      作为替代方案,您也可以尝试sftp.fastGet(),它使用并行读取来快速获取文件。 fastGet() 为您提供了一种显示下载进度的方法(如果需要),除了提供一种配置并行读取数量和块大小的方法。要了解更多信息,请打开此SFTPStream doc 并搜索fastGet

      这是一个非常快速的代码:

      sftp.fastGet(remoteFile, localFile, function (err) {
          if (err) throw err;
          console.log(`${remoteFile} has successfully download to ${localFile}!`);
      }
      

      喂!

      【讨论】:

      • sftp.fastGet/fastDownload的问题是不提供暂停功能,但是stream可以。