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

Javascript 使用Node.js和SSH2从SFTP服务器读取文件,javascript,node.js,buffer,sftp,readable,Javascript,Node.js,Buffer,Sftp,Readable,我在Node.js中处理读取流时遇到了一个非常奇怪的问题。我正在使用SSH2在我和一个sftp服务器之间创建一个sftp连接。然后,我尝试从sftp流创建一个读取流。从读取流发出的“data”事件中,我将数据附加到数组中。当读流的“关闭”事件发生时,我调用Buffer.concat创建concat,将我检索到的所有数据块集中到一个缓冲区中。这与stack overflow中的其他问题中描述的技术相同。例如但是,我无法使用检索到的数据。看起来缓冲区的大小比我试图检索的文件小32字节(从计算检索到的

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

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

经过调查,我发现:

  • 当我在文件上使用readdir时,文件的大小是正确的
  • 在我的开发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);
    

    因此,经过大量的调查,我终于意识到在“数据”事件中传输的最后几位数据有问题。据我所知,这似乎是读流实现中的一个错误。通过使用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 ');
            }
        }
    })
    });   
    
    sftp.open(config.ftpPath+“/”+m_文件名,“r”,函数(err,fd){
    sftp.fstat(fd,函数(err,stats){
    var bufferSize=stats.size,
    chunkSize=16384,
    缓冲区=新缓冲区(缓冲区大小),
    字节读取=0,
    ErrorOccursed=false;
    while(bytesRead缓冲区大小){
    chunkSize=(bufferSize-bytesRead);
    }
    sftp.read(fd,buffer,bytesRead,chunkSize,bytesRead,callbackFunc);
    字节读取+=块大小;
    }
    var totalBytesRead=0;
    函数callbackFunc(err、bytesRead、buf、pos){
    如果(错误){
    writeToErrorLog(“downloadFile():检索文件时出错。”);
    ErrorOccursed=true;
    sftp.关闭(fd);
    }
    totalBytesRead+=字节读取;
    数据推送(buf);
    if(totalBytesRead==缓冲区大小){
    m_fileBuffer=Buffer.concat(数据);
    writeToLog(“downloadFile():保存到缓冲区的文件”);
    sftp.关闭(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()
    提供了一种显示下载进度(如果需要)的方法,还提供了一种配置并行读取次数和块大小的方法。要了解更多信息,请打开此文件并搜索
    fastGet

    下面是一个非常快速的代码:

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

    sftp.fastGet/fastDownload的问题在于它不提供暂停功能,但流可以。