在Node.js中从S3下载文件而不将其写入文件系统

4
我有一个运行着Hapi的Node.js服务器。
服务器的其中一项工作是在用户请求时向服务API发送文件(当我发送buffer时,该API只接受streams ,否则会返回错误)。
所有文件都存储在S3中。 如果我使用promise()下载它们,我就可以得到缓冲区。如果我使用createReadStream(),我可以获得passthrough。
我的问题是当我尝试将缓冲区转换为流并将其发送到API时,API会拒绝它,而当我使用createReadStream()的结果时也是如此,但是当我使用FS来保存文件然后再使用FS读取时,API接受流并且起作用。
因此,我需要帮助来找出如何在不保存和读取文件的情况下创建相同的结果。
编辑: 这是我的代码,我知道这种方法是错误的,但它确实有效,我需要一种更好的方法来实现。
static async downloadFile(Bucket, Key) {
    const result = await s3Client
      .getObject({
        Bucket,
        Key
      })
      .promise();
    fs.writeFileSync(`${Path.basename(Key)}`,result.Body);

    const file = await fs.createReadStream(`${Path.basename(Key)}`);
    return file;
  }
1个回答

4
如果我理解正确,您想从S3存储桶获取对象并将其作为流传输到HTTP响应中。
不要尝试将数据转换为缓冲区并将整个对象加载到内存中,这样做可能会很复杂,并且具有其限制。如果您真的想利用流的强大功能,则可以创建一个请求,通过在请求上调用createReadStream方法,将返回的数据直接流式传输到Node.js Stream对象中。
调用createReadStream会返回由请求管理的原始HTTP流。然后,可以将原始数据流传输到任何Node.js Stream对象中。
对于返回有效载荷中包含原始数据的服务调用(例如,在Amazon S3服务对象上调用getObject以将数据直接流式传输到文件中),此技术非常有用,如本示例所示。
//I Imagine you have something similar.
server.get ('/image', (req, res) => {
    let s3 = new AWS.S3({apiVersion: '2006-03-01'});
    let params = {Bucket: 'myBucket', Key: 'myImageFile.jpg'};
    let readStream= s3.getObject(params).createReadStream();
    // When the stream is done being read, end the response
    readStream.on('close', () => {
        res.end()
    })

    readStream.pipe(res);
});

当您使用createReadStream从请求中流式传输数据时,只返回原始的HTTP数据。 SDK不会对数据进行后处理,这些原始的HTTP数据可以直接返回。
注意: 由于Node.js无法倒回大多数流,如果请求最初成功,则重试逻辑在响应的其余部分中被禁用。在流式传输时发生套接字故障时,SDK不会尝试重试或向流发送更多数据。您的应用程序逻辑需要识别此类流式传输故障并处理它们。
编辑: 在对原始问题进行编辑之后,我可以看到s3发送了一个PassThrough流对象,该对象与Nodejs中的FileStream不同。因此,为了解决这个问题,请使用内存(如果您的文件不是很大或者您有足够的内存)。
使用memfs包,它将替换您的应用程序中的本机fs https://www.npmjs.com/package/memfs 通过npm install memfs安装包,并按如下所示要求:
    const {fs} = require('memfs');

你的代码将会像这样

 static async downloadFile(Bucket, Key) {
        const result = await s3
        .getObject({
          Bucket,
          Key
        })
        .promise();
      fs.writeFileSync(`/${Key}`,result.Body);

      const file = await fs.createReadStream(`/${Key}`);
      return file;
    }

请注意,我在您的函数中所作出的唯一更改是将路径${Path.basename(Key)}更改为/${Key},因为现在您不需要知道原始文件系统的路径,我们正在内存中存储文件。我已测试,此解决方案可行。

我的问题是我需要获取流到formData中,以便像这样发送到其他API => file[0] = readStream,file[1] = readStream - Yotav Masa
@YotavMasa 你可以发代码吗?你在处理FS的时候怎么样了?然后我就可以用解决方法更新我的答案。 - Yousaf
@YotavMasa,请检查我的答案,希望能对你有所帮助。 - Yousaf
但如果我有包含数据的缓冲区,我是否有选项可以在不保存文件的情况下进行流式传输? - Yotav Masa
@YotavMasa,你可以这样做,但流类型将不符合API的要求。API需要一个FileStream,因为它期望文件的信息,如文件状态和元数据。如果你能够在传递流时即时将其转换为FileStream(这并不是一项容易的任务),那么你就可以直接发送流了。这就是为什么MemoryFS非常完美,因为它可以将传递流转换为FileStream。 - Yousaf
如果你阅读MemoryFS的代码,实际上它是使用缓冲区来创建正确类型的流。因此,你不必重新发明轮子,因为当你运行fs.writeFileSync(/${Key},result.Body);时,它会将你的文件保存在缓冲区数组中,当你运行createReadStream时,它会将缓冲区转换为FileStream。 - Yousaf

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