web-dev-qa-db-ja.com

Node.jsとSSH2を使用してSFTPサーバーからファイルを読み取る

Node.jsで読み取りストリームを操作するときにかなり奇妙な問題が発生します。 SSH2を使用して、自分とsftpサーバーの間にsftp接続を作成しています。次に、sftpストリームから読み取りストリームを作成しようとします。読み取りストリームの発行された「データ」イベントから、データを配列に追加します。読み取りストリームの「close」イベントが発生すると、Buffer.concatを呼び出して、取得したデータのすべてのチャンクを1つのバッファーに連結します。これは、スタックオーバーフローでここで尋ねられる他の質問で説明されているのと同じテクニックです。たとえば ここ 。ただし、取得したデータを使用できません。バッファのサイズは、取得しようとしているファイルよりも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);
9
Adam Ramberg

そのため、多くの調査の結果、「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 ');
        }
    }
})
});   
12
Adam Ramberg

バイトサイズ(またはチャンクサイズ)が必須ではなく、ファイルを取得するだけでよい場合は、はるかに軽量で高速な方法があると思います(ええ... 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}!`);
}
_

こんにちは!