使用 Node.js 和 SSH2 从 SFTP 服务器读取文件

Reading file from SFTP server using Node.js and SSH2

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

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

经过调查我发现:

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

如有任何建议,我们将不胜感激!

这是我的代码:

        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);

所以经过大量调查,我终于意识到在 'data' 事件中传输的最后一位数据有问题。据我了解,这似乎是读取流实现中的错误。通过使用 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 ');
        }
    }
})
});   

如果字节大小(或块大小)不是强制性的,而您只需要获取文件,那么猜测有一种更好的更轻更快的方法(是的...... 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}!`);
}

嗨!