realtymaps / promise-sftp

a promise-based sftp client for node.js
MIT License
16 stars 11 forks source link

Unhandled 'error' event. No such file error at at SFTPStream._transform (ssh2-streams\lib\sftp.js) #6

Open zacronos opened 7 years ago

zacronos commented 7 years ago

Originally from: https://github.com/realtymaps/promise-ftp/issues/19

This exception is actually from promise-sftp library. However promise-sftp is not letting users create an issue.

Here is a code snippet to reproduce the error:

var PromiseSftp = require('promise-sftp');

function test() {
  const ftp = new PromiseSftp();

  return ftp.connect({
    host: 'host.address',
    user: 'username',
    password: 'password',
    port: 22,
  })
    .then(() => {
      console.log('connected');

      return ftp.put(Buffer.from('this is a test'), '/invalid/path/to/file.txt')
        .then(() => {
          console.log('successfully uploaded');
        })
        .catch(err => {
          console.log('failed: ' + JSON.stringify(err));
        });
    })
    .then(() => ftp.end())
    .catch(err => {
      console.log('ftp end error: ' + JSON.stringify(err));
    });
}

Since the path does not exist on the ftp server, the promise should be rejected. It is resolving successfully and throwing an error when we attempt to end the connection.

Output:

  connected
  successfully uploaded
  events.js:160
      throw er; // Unhandled 'error' event

Error: No such file
    at SFTPStream._transform (~\node_modules\ssh2-streams\lib\sftp.js:405:27)
    at SFTPStream.Transform._read (_stream_transform.js:167:10)
    at SFTPStream._read (~\node_modules\ssh2-streams\lib\sftp.js:181:15)
    at SFTPStream.Transform._write (_stream_transform.js:155:12)
    at doWrite (_stream_writable.js:334:12)
    at writeOrBuffer (_stream_writable.js:320:5)
    at SFTPStream.Writable.write (_stream_writable.js:247:11)
    at Channel.ondata (_stream_readable.js:555:20)
    at emitOne (events.js:96:13)
    at Channel.emit (events.js:188:7)
    at readableAddChunk (_stream_readable.js:176:18)
    at Channel.Readable.push (_stream_readable.js:134:10)
    at SSH2Stream.<anonymous> (~\node_modules\ssh2\lib\Channel.js:166:15)
    at emitOne (events.js:96:13)
    at SSH2Stream.emit (events.js:188:7)
    at parsePacket (~\node_modules\ssh2-streams\lib\ssh.js:3439:10)
    at SSH2Stream._transform (~\node_modules\ssh2-streams\lib\ssh.js:667:13)
    at SSH2Stream.Transform._read (_stream_transform.js:167:10)
    at SSH2Stream._read (~\node_modules\ssh2-streams\lib\ssh.js:251:15)
    at SSH2Stream.Transform._write (_stream_transform.js:155:12)
    at doWrite (_stream_writable.js:334:12)
    at writeOrBuffer (_stream_writable.js:320:5)
zacronos commented 7 years ago

@ozankaya

zacronos commented 7 years ago

The exception originates in the underlying ssh2-streams library. The fact that it is coming through in the wrong place is a consequence of an unfortunate decision I made regarding how to handle waiting for commands to complete under the hood -- that part needs to be redesigned. Thanks for the bug report.

lfreneda commented 6 years ago

@zacronos any workaround?

zacronos commented 6 years ago

@lfreneda I would suggest using .list() to verify a file / directory's presence. And if the file/directory isn't there, do whatever makes sense (aborting for a file download, creating the directory for a file upload, etc).

It's a bit inconvenient because you'd need to do it for each path segment, but it should work.

at-teamexports commented 5 years ago

Hello

We encountered this error. We have developed a fix. In the "put" function, instead of:

      this.put = function(input, destPath) {
        return Promise["try"](function() {
          var options;
          if (restartOffset !== null) {
            options = {
              start: restartOffset,
              flags: 'r+'
            };
            restartOffset = null;
          }
          if (typeof input === 'string') {
            if (!options) {
              return promisifiedClientMethods.fastPut(input, destPath);
            }
            input = fs.createReadStream(input);
          }
          return promisifiedClientMethods.createWriteStream(destPath, options).then(function(stream) {
            finishLogic(stream);
            if (input instanceof Buffer) {
              return stream.end(input);
            }
            input.pipe(stream);
            return void 0;
          });

Just have to code:

      this.put = function(input, destPath) {
        return Promise["try"](function() {
          var options;
          if (restartOffset !== null) {
            options = {
              start: restartOffset,
              flags: 'r+'
            };
            restartOffset = null;
          }
          if (typeof input === 'string') {
            if (!options) {
              return promisifiedClientMethods.fastPut(input, destPath);
            }
            input = fs.createReadStream(input);
          }
          return promisifiedClientMethods.createWriteStream(destPath, options).then(function(stream) {
            return new Promise((resolve, reject) => {
              stream.on('error', (err) => {
                console.log('write stream error ' + err);
                reject(err);
              });
              stream.on('end', () => {
                resolve();
              });
              finishLogic(stream);
              if (input instanceof Buffer) {
                stream.end(input);
                resolve();
                return;
              }
              input.pipe(stream);
            });
          });

The problem was that the "put" function does not handle the errors of the write stream. I don't know well about bluebird promises but there was no stream.on('error', ...). That is why, when the folder does not exist, the thread was killed. With this solution, "put" function rejects the write stream errors and resolve when the write stream ends.

About your "list directory" workaround. This is not a solution for us since some FTP servers allow put actions but not list directories.

Would it be possible to integrate this fix please? I am not familiar to coffee script...

at-teamexports commented 5 years ago

A little update, here is the code we actually use:

      this.put = function(input, destPath) {
        return new Promise((resolve, reject) => {
          var options;
          if (restartOffset !== null) {
            options = {
              start: restartOffset,
              flags: 'r+'
            };
            restartOffset = null;
          }
          if (typeof input === 'string') {
            if (!options) {
              return promisifiedClientMethods.fastPut(input, destPath);
            }
            input = fs.createReadStream(input);
          }
          return promisifiedClientMethods.createWriteStream(destPath, options).then(function(stream) {
            finishLogic(stream);
            stream.on('error', (err) => {
              reject(err);
            });
            input.on('end', () => {
              resolve();
            });
            if (input instanceof Buffer) {
              stream.end(input);
              resolve();
              return;
            }
            input.pipe(stream);
          });
        });
      };
em commented 5 years ago

Please make this error message include the actual filename in it.

juanmjacobs commented 3 years ago

This fix would be awesome! Currently I have no way to catch those underlying stream errors and they make my process crash. Thanks for this great library!