简体   繁体   English

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

[英]Reading file from SFTP server using Node.js and SSH2

I have a pretty strange problem working with read streams in Node.js. 我在Node.js中使用读取流时遇到了一个非常奇怪的问题。 I'm using SSH2 to create a sftp connection between me and a sftp server. 我正在使用SSH2在我和sftp服务器之间创建一个sftp连接。 I then try to create a read stream from the sftp stream. 然后我尝试从sftp流创建一个读取流。 From the read stream's emitted 'data' event I append the data to an array. 从读取流发出的“数据”事件中,我将数据附加到数组。 When the read stream's 'close' event occurs I make a call to Buffer.concat to create concat all the chunks of data I have retrieved into one buffer. 当读取流的'close'事件发生时,我调用Buffer.concat来创建concat,将我检索到的所有数据块连接到一个缓冲区中。 This is the same technique described in other questions asked here at stack overflow. 这是在堆栈溢出时在此处提出的其他问题中描述的相同技术。 For example here . 例如这里 However, I'm not able to use the data I retrieve. 但是,我无法使用我检索的数据。 It seems like the buffer is 32 bytes less in size then the file that I'm trying to retrieve (from counting the length of the data retrieved). 似乎缓冲区的大小比我试图检索的文件小32个字节(从计算检索的数据长度)。 Could this have something to do with my SFTP connection? 这可能与我的SFTP连接有关吗? Or how I create my read stream? 或者我如何创建我的阅读流?

If it matters, the file is of type zip. 如果重要,该文件的类型为zip。 When I'm trying to unzip the file (in node.js and manually) after reading it to buffer it doesn't work. 当我尝试解压缩文件(在node.js中并手动)后,将其读取到缓冲区后,它无法正常工作。

After investigating I have found out that: 调查后我发现:

  1. When I use readdir on the file, the size of the file is correct. 当我在文件上使用readdir时,文件的大小是正确的。
  2. Using FTP (JSFTP) against my development FTP server works just fine using the same technique above. 使用FTP(JSFTP)对我的开发FTP服务器使用上面相同的技术工作正常。

Any advise is appreciated! 任何建议表示赞赏!

Here is my code: 这是我的代码:

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

So after a lot of investigation I finally realized that there was something wrong with the last bits of data that was transmitted in the 'data' event. 经过大量调查后,我终于意识到在'data'事件中传输的最后几位数据有问题。 From my understanding it seems to be a bug in the implementation of the read stream. 根据我的理解,它似乎是读取流的实现中的一个错误。 I was able to get around this problem by using the more simplistic functions (open, fstat, read) in the SSH2 library. 通过在SSH2库中使用更简单的函数(open,fstat,read),我能够解决这个问题。 This solution works for me. 这个解决方案适合我。 Wanted to share the solution if someone else comes across the same problem. 如果其他人遇到同样的问题,想要分享解决方案。

Working code: 工作代码:

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

If the byte size (or chunk size) is not mandatory and you just need to get the file, guess there is a much better lighter-and-faster way (yeah...the nodejs way!). 如果字节大小(或块大小)不是强制性的,你只需要获取文件,那么猜测有更好的更轻,更快的方式(是的......节点方式!)。 This is how I use to copy a file: 这是我用来复制文件的方式:

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

As an alternative, you can also try sftp.fastGet() which uses parallel reads to bring the file quickly. 作为替代方案,您还可以尝试使用并行读取的sftp.fastGet()来快速启动文件。 fastGet() provides you a way to show the progress of the download (if wanted) apart from giving a way to configure the number of parallel reads and chunk size. fastGet()为您提供了一种显示下载进度(如果需要)的方法,除了提供配置并行读取数和块大小的方法。 To know more, open this SFTPStream doc and search for fastGet . 要了解更多信息,请打开此SFTPStream文档并搜索fastGet

Here is a very quick code: 这是一个非常快速的代码:

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

HIH! HIH!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM