node.js lambda 中带有 ssh2-sftp-client 的链式 then() 未使用错误回调

Error callback not used for chained then() with ssh2-sftp-client in node.js lambda

我对节点和 lambda 很陌生,所以我可能犯了一些愚蠢的错误。我创建了一个 node.js aws lambda 函数,它从 s3 事件中获取文件。如果文件是 gzip,它会解压缩,将其上传到 sftp 服务器,然后创建一个 sig 文件并将其上传到同一 sftp 服务器。当一切顺利时它会起作用,但它似乎无法正确触发错误。

sftp 命令是使用 then 链接起来的,所以我希望任何错误都会导致后续的 then 失败。例如,如果我关闭我的 sftp 服务器,sftp 客户端将生成超时错误,但 lambda 永远不会看到回调错误,只会成功。日志确实向控制台显示了错误输出,但它似乎在跟随其余的 .then() 项目之后使用了成功回调。连接是否未正确记录为承诺?

示例日志:

...
Starting SFTP
Connected to sftp, starting sftp put for lastsub2.dat file.
{ Error: Timed out while waiting for handshake
    at Timeout._onTimeout (/var/task/node_modules/ssh2/lib/client.js:687:19)
    at ontimeout (timers.js:386:14)
    at tryOnTimeout (timers.js:250:5)
    at Timer.listOnTimeout (timers.js:214:5) level: 'client-timeout' } 'Error occured during sftp relay.'
END

示例代码:

console.log('Loading function');
const aws = require('aws-sdk');
const s3 = new aws.S3({
    apiVersion: '2006-03-01'
  });
const zlib = require('zlib');
const fs = require("fs");
const connSettings = {
  host: 'xxx',
  port: '22',
  username: 'xxx',
  password: 'xxx'
};

exports.handler = function (event, context, callback) {
  console.log('Received event:', JSON.stringify(event, null, 2));
  console.log('Bucket Name: ' + event.Records[0].s3.bucket.name);
  console.log('Object Key: ' + decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' ')));
  const bucket = event.Records[0].s3.bucket.name;
  const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
  const params = {
    Bucket: bucket,
    Key: key,
  };

  s3.getObject(params, (err, data) => {
    if (err) {
      console.log(err);
      const message = 'Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.';
      console.log(message);
      callback(message);
    } else {
      if (data.ContentType == 'application/x-gzip') {
        console.log('CONTENT TYPE is application/x-gzip');
        var dataStream = s3.getObject(params).createReadStream().pipe(zlib.Unzip());
        console.log('Created unzip datastream');
        console.log('Starting SFTP');
        let Client = require('ssh2-sftp-client');
        let sftp = new Client();
        sftp.connect(connSettings)
        .then(console.log('Connected to sftp, starting sftp put for ' + key.replace('.gz', '.dat') + ' file.'))
        .then(() => {
            console.log('Finished sftp put for ' + key.replace('.gz', '.dat') + ' file.');
            return sftp.put(dataStream, key.replace('.gz', '.dat'), true, 'utf-8');
        }).then(() => {
          var sigFileName = key.replace('.gz', '.sig');
          var sigStream = fs.createWriteStream('/tmp/' + sigFileName);
          sigStream.end();
          console.log('Created ' + sigFileName + ' sig file.');
          var readStream = fs.createReadStream('/tmp/' + sigFileName);
          console.log('Uploaded ' + sigFileName + ' sig file.');
          return sftp.put(readStream, sigFileName, true, 'utf-8');
        }).then(() => {
            console.log('Ended sftp connection.');
            return sftp.end();
        })
        .then(callback(null, 'Success'))
        .catch ((err) => {
          console.log(err, 'Error occured during sftp relay.');
          callback('Error', err);
        });
      } else {
        callback(null, 'Uploaded file not in gzip format, will not be processed.');
      }
    }
  });
};

您遇到的问题之所以发生,是因为您没有 returning then() 中的任何内容。结果是每个 then() 都会立即执行,而无需等待任何异步 sftp 函数到 return,因为它会立即解析为未定义。

您没有提到您使用的是什么 sftp 库,但假设它 return 是一个承诺,您应该能够简单地 return 来自 then() 的那些承诺s.

例如:

.then(() => {
      console.log('Finished sftp put for ' + key.replace('.gz', '.dat') + ' file.');
      // assumes stfp.put returns a promise, just return it into the chain
      return sftp.put(dataStream, key.replace('.gz', '.dat'), true, 'utf-8');
    })

根据评论进行编辑:

您应该能够从 then() 调用回调。现在在您的编辑中查看日志输出,这难道不是您在出现错误时所期望的——它会跳到捕获点。由于您当时调用它的方式,您将获得控制台输出 'Connected to sftp…' 。而不是:

.then(console.log('Connected to sftp, starting sftp put for ' + key.replace('.gz', '.dat') + ' file.'))

大概应该是:

.then(() => console.log('Connected to sftp, starting sftp put for ' + key.replace('.gz', '.dat') + ' file.'))

按照您的方式,控制台将在来自 sftp 的错误 returns 之前记录。