如何使用AWS SDK v3将流上传到S3

32

我需要将一个文件从API端点传输到两个不同的存储桶。 最初的上传是使用以下方式完成的:

curl -X PUT -F "data=@sample" "http://localhost:3000/upload/1/1"

文件上传的终端点:

const PassThrough = require('stream').PassThrough;

async function uploadFile (req, res) {
  try {
    const firstS3Stream = new PassThrough();
    const secondS3Stream = new PassThrough();
    req.pipe(firstS3Stream);
    req.pipe(secondS3Stream);

    await Promise.all([
      uploadToFirstS3(firstS3Stream),
      uploadToSecondS3(secondS3Stream),
    ]);
    return res.end();
  } catch (err) {
    console.log(err)
    return res.status(500).send({ error: 'Unexpected error during file upload' });
  }
}

正如您所看到的,我使用两个PassThrough流,以便将请求流复制到两个可读流中,正如在这个SO线程中建议的那样

这段代码保持不变,有趣的是 uploadToFirstS3 uploadToSecondS3 函数。在这个最小化的例子中,它们都用不同的配置做着完全相同的事情,我只会在这里扩展一个。

良好的工作原理:

const aws = require('aws-sdk');

const s3 = new aws.S3({
  accessKeyId: S3_API_KEY,
  secretAccessKey: S3_API_SECRET,
  region: S3_REGION,
  signatureVersion: 'v4',
});

const uploadToFirstS3 = (stream) => (new Promise((resolve, reject) => {
  const uploadParams = {
    Bucket: S3_BUCKET_NAME,
    Key: 'some-key',
    Body: stream,
  };
  s3.upload(uploadParams, (err) => {
    if (err) reject(err);
    resolve(true);
  });
}));

这段代码(基于aws-sdk包)运行良好。我的问题在于,我希望它能够使用@aws-sdk/client-s3包来运行,以减小项目的大小。

不起作用的部分:

我首先尝试使用S3Client.send(PutObjectCommand)

const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');

const s3 = new S3Client({
  credentials: {
    accessKeyId: S3_API_KEY,
    secretAccessKey: S3_API_SECRET,
  },
  region: S3_REGION,
  signatureVersion: 'v4',
});

const uploadToFirstS3 = (stream) => (new Promise((resolve, reject) => {
  const uploadParams = {
    Bucket: S3_BUCKET_NAME,
    Key:'some-key',
    Body: stream,
  };
  s3.send(new PutObjectCommand(uploadParams), (err) => {
    if (err) reject(err);
    resolve(true);
  });
}));

然后我尝试了S3.putObject(PutObjectCommandInput)

const { S3 } = require('@aws-sdk/client-s3');

const s3 = new S3({
  credentials: {
    accessKeyId: S3_API_KEY,
    secretAccessKey: S3_API_SECRET,
  },
  region: S3_REGION,
  signatureVersion: 'v4',
});

const uploadToFirstS3 = (stream) => (new Promise((resolve, reject) => {
  const uploadParams = {
    Bucket: S3_BUCKET_NAME,
    Key:'some-key',
    Body: stream,
  };
  s3.putObject(uploadParams, (err) => {
    if (err) reject(err);
    resolve(true);
  });
}));

两个最后的例子都给了我一个501 - 未实现错误,其中包含头Transfer-Encoding。我检查了req.headers,里面没有Transfer-Encoding,所以我猜sdk会在请求中添加吗?
既然第一个基于aws-sdk的例子运行良好,我确定错误不是由请求中的空主体导致的,就像这个SO线程中建议的那样。
尽管如此,我想也许当触发上传时,流还没有准备好读取,因此我使用回调函数将对uploadToFirstS3uploadToSecondS3的调用包装在req.on('readable', callback)事件触发的回调中,但是什么也没有改变。
我想在内存中处理文件,而不必在任何时候将其存储在磁盘上。有没有办法使用@aws-sdk/client-s3包实现这一点?
2个回答

59
在v3中,您可以使用@aws-sdk/lib-storage中的Upload类进行分段上传。不幸的是,在@aws-sdk/client-s3文档站点中可能没有提到这一点。
升级指南中提到了这一点:https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-multipart-upload 以下是提供的示例的更正版本:https://github.com/aws/aws-sdk-js-v3/tree/main/lib/lib-storage
  import { Upload } from "@aws-sdk/lib-storage";
  import { S3Client } from "@aws-sdk/client-s3";

  const target = { Bucket, Key, Body };
  try {
    const parallelUploads3 = new Upload({
      client: new S3Client({}),
      tags: [...], // optional tags
      queueSize: 4, // optional concurrency configuration
      leavePartsOnError: false, // optional manually handle dropped parts
      params: target,
    });

    parallelUploads3.on("httpUploadProgress", (progress) => {
      console.log(progress);
    });

    await parallelUploads3.done();
  } catch (e) {
    console.log(e);
  }

1
嘿,谢谢分享,为什么要使用 new S3({}) || new S3Client({}) - Can Rau
1
好问题,我只是逐字逐句地复制了他们的示例。这很奇怪...我在我的代码中使用了S3Client使其工作,所以我将更新示例以使用它。 - Andy
是的,我也成功地使用S3Client,但希望你能解答他们的代码问题。 - Can Rau
不对。这是不合逻辑的,因为new S3({})总是真值。也许他们试图说明您可以使用任何一个(不确定是否可以?),但这将是一种语义上奇怪的方式。 - Andy
1
如果没有parallelUploads3.on("httpUploadProgress" ....这一行,流式上传将无法开始/完成;如何在不侦听httpUploadProgress和不打印进度的情况下启动或完成流式上传? - Kid_Learning_C
显示剩余3条评论

6

我遇到了和你一样的错误。 看起来他们有一个已知问题,他们还没有准确地记录下来:

这个错误确实是由于流的长度未知所引起的。我们需要改进错误信息和文档

为了解决这个问题,你只需要为PutObjectCommand指定Content-length属性。

以下是更新后的代码片段:

const { S3 } = require('@aws-sdk/client-s3');

const s3 = new S3({
  credentials: {
    accessKeyId: S3_API_KEY,
    secretAccessKey: S3_API_SECRET,
  },
  region: S3_REGION,
  signatureVersion: 'v4',
});

const uploadToFirstS3 = (passThroughStream) => (new Promise((resolve, reject) => {
  const uploadParams = {
    Bucket: S3_BUCKET_NAME,
    Key:'some-key',
    Body: stream,
    ContentLength: passThroughStream.readableLength, // include this new field!!
  };
  s3.putObject(uploadParams, (err) => {
    if (err) reject(err);
    resolve(true);
  });
}));
      

希望能帮到你!


steam在被分配给Body属性的地方不存在。这应该是指passThroughStream吗? - undefined
是的 @devklick,我已经相应地编辑了代码片段,谢谢! - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接