使用formidable和(knox或aws-sdk)将文件流上传到Node.js上的S3

问题描述 投票:13回答:4

我正在尝试使用aws-sdkknox将通过表单提交的文件直接上传到Amazon S3存储桶。使用formidable完成表格处理。

我的问题是:如何使用aws-sdk(或knox)使用这些库的最新功能处理流来正确使用强大的功能?

我知道这个主题已经在这里被提出了不同的风格,即:

但是,我认为答案有点过时和/或偏离主题(即CORS支持,我现在不希望出于各种原因使用)和/或最重要的是,没有提及最新的功能来自aws-sdk(参见:Accessing the raw file stream from a node-formidable file upload)或knox(特别是putStream()或其readableStream.pipe(req)变体,https://github.com/aws/aws-sdk-js/issues/13#issuecomment-16085442)。

经过几个小时的挣扎,我得出结论,我需要一些帮助(免责声明:我是一个流媒体新手)。

HTML表单:

both explained in the doc

Express bodyParser中间件以这种方式配置:

<form action="/uploadPicture" method="post" enctype="multipart/form-data">
  <input name="picture" type="file" accept="image/*">
  <input type="submit">
</form>

POST请求处理程序:

app.use(express.bodyParser({defer: true}))

但是,我收到以下错误:

{[RequestTimeout:在超时期限内未读取或写入与服务器的套接字连接。空闲连接将被关闭。]

消息:'在超时期限内未读取或写入与服务器的套接字连接。空闲连接将被关闭。',代码:'RequestTimeout',名称:'RequestTimeout',statusCode:400,retryable:false}

以这种方式定制的knox版本的handlePart()函数也惨遭失败:

uploadPicture = (req, res, next) ->
  form = new formidable.IncomingForm()
  form.parse(req)

  form.onPart = (part) ->
    if not part.filename
      # Let formidable handle all non-file parts (fields)
      form.handlePart(part)
    else
      handlePart(part, form.bytesExpected)

  handlePart = (part, fileSize) ->
    # aws-sdk version
    params =
      Bucket: "mybucket"
      Key: part.filename
      ContentLength: fileSize
      Body: part # passing stream object as body parameter

    awsS3client.putObject(params, (err, data) ->
      if err
        console.log err
      else
        console.log data
    )

我还得到一个带有400 statusCode的大型res对象。

在两种情况下,区域都配置为eu-west-1。

补充说明:

节点0.10.12

最新的强大来自npm(1.0.14)

来自npm的最新aws-sdk(1.3.1)

来自npm的最新knox(0.8.3)

node.js amazon-s3 formidable knox-amazon-s3-client
4个回答
9
投票

好吧,handlePart = (part, fileSize) -> headers = "Content-Length": fileSize "Content-Type": part.mime knoxS3client.putStream(part, part.filename, headers, (err, res) -> if err console.log err else console.log res ) ,直接流式传输到Amazon S3是不可能的:

S3 API要求您在创建新文件时提供新文件的大小。在完全接收到多部分/表单数据文件之前,此信息不可用。这意味着流媒体是不可能的

实际上,form.bytesExpected指的是整个表单的大小,而不是单个文件的大小。

因此,在上传到S3之前,数据必须首先点击服务器上的内存或磁盘。


3
投票

使用AWS S3的multipartUpload(according to the creator of Formidable作为工作模块)和node-formidable的s3-upload-stream,您可以像上载readable stream一样上传流:

this

3
投票

由于这篇文章太旧了,我认为现在支持直接流式传输,我花了很多时间阅读关于这个主题的过时答案......

如果它可以帮助任何人,我可以直接从客户端流式传输到s3而无需安装包:

var formidable = require('formidable'); var http = require('http'); var util = require('util'); var AWS = require('aws-sdk'); var config = require('./config'); var s3 = new AWS.S3({ accessKeyId: config.get('S3_ACCESS_KEY'), secretAccessKey: config.get('S3_SECRET_KEY'), apiVersion: '2006-03-01' }); var s3Stream = require('s3-upload-stream')(s3); var bucket = 'bucket-name'; var key = 'abcdefgh'; http.createServer(function(req, res) { if (req.url == '/upload' && req.method.toLowerCase() == 'post') { var form = new formidable.IncomingForm(); form.on('progress', function(bytesReceived, bytesExpected) { //console.log('onprogress', parseInt( 100 * bytesReceived / bytesExpected ), '%'); }); form.on('error', function(err) { console.log('err',err); }); // This 'end' is for the client to finish uploading // upload.on('uploaded') is when the uploading is // done on AWS S3 form.on('end', function() { console.log('ended!!!!', arguments); }); form.on('aborted', function() { console.log('aborted', arguments); }); form.onPart = function(part) { console.log('part',part); // part looks like this // { // readable: true, // headers: // { // 'content-disposition': 'form-data; name="upload"; filename="00video38.mp4"', // 'content-type': 'video/mp4' // }, // name: 'upload', // filename: '00video38.mp4', // mime: 'video/mp4', // transferEncoding: 'binary', // transferBuffer: '' // } var start = new Date().getTime(); var upload = s3Stream.upload({ "Bucket": bucket, "Key": part.filename }); // Optional configuration //upload.maxPartSize(20971520); // 20 MB upload.concurrentParts(5); // Handle errors. upload.on('error', function (error) { console.log('errr',error); }); upload.on('part', function (details) { console.log('part',details); }); upload.on('uploaded', function (details) { var end = new Date().getTime(); console.log('it took',end-start); console.log('uploaded',details); }); // Maybe you could add compress like // part.pipe(compress).pipe(upload) part.pipe(upload); }; form.parse(req, function(err, fields, files) { res.writeHead(200, {'content-type': 'text/plain'}); res.write('received upload:\n\n'); res.end(util.inspect({fields: fields, files: files})); }); return; } // show a file upload form res.writeHead(200, {'content-type': 'text/html'}); res.end( '<form action="/upload" enctype="multipart/form-data" method="post">'+ '<input type="text" name="title"><br>'+ '<input type="file" name="upload" multiple="multiple"><br>'+ '<input type="submit" value="Upload">'+ '</form>' ); }).listen(8080);

服务器假设https://gist.github.com/mattlockyer/532291b6194f6d9ca40cb82564db9d2a是一个流对象,在我的例子中,在xhr(send)中使用了File对象,它将在现代浏览器中发送二进制数据。

req

是的它违反了惯例,但是如果你看一下这个要点,它比我发现依赖其他包装的任何东西都要清洁得多。

+1实用主义,感谢@SalehenRahman的帮助。


1
投票

尝试将“内容类型”添加到Upload params(qazxsw poi)

const fileUploadStream = (req, res) => {
  //get "body" args from header
  const { id, fn } = JSON.parse(req.get('body'));
  const Key = id + '/' + fn; //upload to s3 folder "id" with filename === fn
  const params = {
    Key,
    Bucket: bucketName, //set somewhere
    Body: req, //req is a stream
  };
  s3.upload(params, (err, data) => {
    if (err) {
      res.send('Error Uploading Data: ' + JSON.stringify(err) + '\n' + JSON.stringify(err.stack));
    } else {
      res.send(Key);
    }
  });
};
© www.soinside.com 2019 - 2024. All rights reserved.