如何在以多部分形式处理文件流之前等待字段

时间:2015-10-16 18:03:37

标签: node.js stream promise busboy

我正在使用SendGrid通过电子邮件接收文件。 SendGrid解析传入的电子邮件,并将多部分表单中的文件发送到我设置的端点。

我不想要本地磁盘上的文件,所以我将它们直接传输到Amazon S3。这很完美。

但在我可以流式传输到S3之前,我需要获取目标邮件地址,以便我可以找到正确的s3文件夹。这是在表单post中名为“to”的字段中发送的。不幸的是,这个字段有时会在文件到达后到达,因此在我准备好接收流之前,我需要一种等待to-field的方法。

我以为我可以将onField包装在一个promise中,并等待onFile中的to-field。但是当这个字段到达文件后,这个概念似乎会自动锁定它。

我对展台流和承诺不熟悉。如果有人能告诉我怎么做,我真的很感激。

这是非工作伪代码:

function sendGridUpload(req, res, next) { 
  var busboy = new Busboy({ headers: req.headers });

  var awaitEmailAddress = new Promise(function(resolve, reject) {
    busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
      if(fieldname === 'to') {
        resolve(val);
      } else {
        return;
      }
    });
  });


  busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {

    function findInbox(emailAddress) {
      console.log('Got email address: ' + emailAddress);

      ..find the inbox and generate an s3Key
      return s3Key;
    }

    function saveFileStream(s3Key) {
      ..pipe the file directly to S3
    }

    awaitEmailAddress.then(findInbox)
    .then(saveFileStream)
    .catch(function(err) {
      log.error(err)
    });
  });

  req.pipe(busboy);
}

1 个答案:

答案 0 :(得分:0)

我终于有了这个工作。解决方案不是很漂亮,我实际上已经切换到另一个概念(在帖子末尾描述)。

要缓冲传入的数据,直到“到”字段到达,我使用@samcday的 stream-buffers 。当我掌握了to-field时,我将可读流发布到为数据排列的管道中。

这是代码(省略了一些部分,但有必要的部分)。

var streamBuffers = require('stream-buffers');

function postInboundMail(req, res, next) {
  var busboy = new Busboy({ headers: req.headers});

  //Sometimes the fields arrives after the files are streamed.
  //We need the "to"-field before we are ready for the files
  //Therefore the onField is wrapped in a promise which gets
  //resolved when the to field arrives
  var awaitEmailAddress = new Promise(function(resolve, reject) {
    busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
      var emailAddress;

      if(fieldname === 'to') {
        try {
          emailAddress = emailRegexp.exec(val)[1]
          resolve(emailAddress)
        } catch(err) {
          return reject(err);        
        }
      } else {
        return;
      }
    });
  });


  busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
    var inbox;

    //I'm using readableStreamBuffer to accumulate the data before
    //I get the email field so I can send the stream through to S3
    var readBuf = new streamBuffers.ReadableStreamBuffer();

    //I have to pause readBuf immediately. Otherwise stream-buffers starts
    //sending as soon as I put data in in with put().
    readBuf.pause();

    function getInbox(emailAddress) {
      return model.inbox.findOne({email: emailAddress})
      .then(function(result) {
        if(!result) return Promise.reject(new Error(`Inbox not found for ${emailAddress}`))

        inbox = result;
        return Promise.resolve();
      });
    }

    function saveFileStream() {
      console.log('=========== starting stream to S3 ========= ' + filename)

      //Have to resume readBuf since we paused it before
      readBuf.resume();

      //file.save will approximately do the following:
      // readBuf.pipe(gzip).pipe(encrypt).pipe(S3)
      return model.file.save({
        inbox: inbox,
        fileStream: readBuf
      });
    }

    awaitEmailAddress.then(getInbox)
    .then(saveFileStream)
    .catch(function(err) {
      log.error(err)
    });


    file.on('data', function(data) {
      //Fill readBuf with data as it arrives
      readBuf.put(data);
    });

    file.on('end', function() {
      //This was the only way I found to get the S3 streaming finished.
      //Destroysoon will let the pipes finish the reading bot no more writes are allowed
      readBuf.destroySoon()
    });
  });


  busboy.on('finish', function() {
    res.writeHead(202, { Connection: 'close', Location: '/' });
    res.end();
  });

  req.pipe(busboy);
}

我真的非常喜欢这个解决方案的反馈,即使我没有使用它。我觉得这可以做得更简单和优雅。

新解决方案: 我没有等待to-field,而是将流直接发送到S3。我想,在输入流和S3保存之间放入的东西越多,由于我的代码中的错误,丢失传入文件的风险就越高。 (如果我没有响应200,SendGrid最终将重新发送该文件,但这需要一些时间。)

我就是这样做的:

  1. 在数据库中保存文件的占位符
  2. 将流管道传输到S3
  3. 在占位符到达时提供更多信息
  4. 此解决方案还让我有机会轻松掌握不成功的上传,因为不成功上传的占位符将不完整。

    //迈克尔