如何从Node.js中的S3 getObject获得响应?

Sar*_*tts 58 javascript amazon-s3 node.js aws-sdk aws-sdk-nodejs

在Node.js项目中,我试图从S3获取数据.

当我使用时getSignedURL,一切正常:

aws.getSignedUrl('getObject', params, function(err, url){
    console.log(url); 
}); 
Run Code Online (Sandbox Code Playgroud)

我的参数是:

var params = {
              Bucket: "test-aws-imagery", 
              Key: "TILES/Level4/A3_B3_C2/A5_B67_C59_Tiles.par"
Run Code Online (Sandbox Code Playgroud)

如果我将URL输出带到控制台并将其粘贴到Web浏览器中,它会下载我需要的文件.

但是,如果我尝试使用,getObject我会得到各种奇怪的行为.我相信我只是错误地使用它.这就是我尝试过的:

aws.getObject(params, function(err, data){
    console.log(data); 
    console.log(err); 
}); 
Run Code Online (Sandbox Code Playgroud)

输出:

{ 
  AcceptRanges: 'bytes',
  LastModified: 'Wed, 06 Apr 2016 20:04:02 GMT',
  ContentLength: '1602862',
  ETag: '9826l1e5725fbd52l88ge3f5v0c123a4"',
  ContentType: 'application/octet-stream',
  Metadata: {},
  Body: <Buffer 01 00 00 00  ... > }

  null
Run Code Online (Sandbox Code Playgroud)

所以它似乎正常运作.但是,当我在其中一个console.logs 上放置断点时,我的IDE(NetBeans)会抛出错误并拒绝显示数据的值.虽然这可能只是IDE,但我决定尝试其他方式使用getObject.

aws.getObject(params).on('httpData', function(chunk){
    console.log(chunk); 
}).on('httpDone', function(data){
    console.log(data); 
});
Run Code Online (Sandbox Code Playgroud)

这不输出任何东西.在断点处显示代码永远不会到达任何一个console.logs.我也尝试过:

aws.getObject(params).on('success', function(data){
    console.log(data); 
});
Run Code Online (Sandbox Code Playgroud)

但是,这也没有输出任何东西,并且放置一个断点表明console.log永远不会到达.

我究竟做错了什么?

pet*_*teb 121

当执行getObject()从S3 API,按照文档文件的内容都位于Body属性,您可以从您的样品输出看到.您应该具有类似于以下内容的代码

const aws = require('aws-sdk');
const s3 = new aws.S3(); // Pass in opts to S3 if necessary

var getParams = {
    Bucket: 'abc', // your bucket name,
    Key: 'abc.txt' // path to the object you're looking for
}

s3.getObject(getParams, function(err, data) {
    // Handle any error and exit
    if (err)
        return err;

  // No error happened
  // Convert Body from a Buffer to a String

  let objectData = data.Body.toString('utf-8'); // Use the encoding necessary
});
Run Code Online (Sandbox Code Playgroud)

您可能不需要从data.Body对象创建新缓冲区,但如果需要,可以使用上面的示例来实现.

  • 我在 AWS 文档或他们的 NPM 存储库中找不到任何包含此代码的文档,这绝对是疯狂的。就像,他们真的必须让事情变得那么困难吗?他们不能只有一个“傻瓜式”方法(甚至是一个愚蠢的命令类)来获取 b64 编码的文件内容或其他对象数据?就像它获取了大量荒谬的垃圾元数据,我和我想大多数人都永远不需要这些元数据。但它不能返回对象数据本身? (51认同)
  • “将 Body 从缓冲区转换为字符串”...如果 AWS 文档能够更清楚地说明这一点,那就太好了。我已经厌倦了与 AWS 的较量。 (5认同)
  • 如果内容已经是缓冲区,则无需从中创建新的缓冲区.只需要`data.Body.toString('utf-8');`.缓冲区是节点中二进制数据的表示,如果您需要更多信息[这里是文档](https://nodejs.org/api/buffer.html) (3认同)
  • 这适用于文本,但是有一个处理文本文件以及.png,.jpg等的通用解决方案吗? (2认同)
  • @carter这是一个通用的解决方案.如果想要图像的二进制字符串,只需在访问`data.Body`到`.toString('binary')`时更改`.toString('utf8')`.如果`data.Body`中的`Buffer`不需要像这个问题那样转换为String,那么你只需返回`data.Body`并直接使用`Buffer`. (2认同)
  • @peteb,你的 2021 年更新救了我两天,因为我被这个问题困住了。我最终根据您的建议使用 Buffer.concat() 来获取缓冲区(后来我在这篇文章之后将其显示为 Base64 图像:/sf/ask/4038973991/ -文件缓冲区到img-tag-src)。谢谢! (2认同)

tra*_*ang 22

更新(2022)

nodejs v17.5.0 添加了 Readable.toArray。如果您的节点版本提供此 API。代码会很短:

const buffer = Buffer.concat(
    await (
        await s3Client
            .send(new GetObjectCommand({
                Key: '<key>',
                Bucket: '<bucket>',
            }))
    ).Body.toArray()
)
Run Code Online (Sandbox Code Playgroud)

如果您使用 Typescript,则可以安全地将.Body部分转换为Readable(其他类型ReadableStream并且Blob仅在浏览器环境中返回。此外,在浏览器中,Blob response.body在不支持时在旧版 fetch API 中使用)

(response.Body as Readable).toArray()
Run Code Online (Sandbox Code Playgroud)

请注意:Readable.toArray是一个实验性(但很方便)的功能,请谨慎使用。

在此输入图像描述

=============

原答案

如果您使用的是 aws sdk v3,则 sdk v3 返回 nodejs Readable(准确地说,是扩展 Readable 的IncomingMessage)而不是 Buffer。

这是 Typescript 版本。请注意,这仅适用于节点,如果您从浏览器发送请求,请检查下面提到的博客文章中的较长答案。

import {GetObjectCommand, S3Client} from '@aws-sdk/client-s3'
import type {Readable} from 'stream'

const s3Client = new S3Client({
    apiVersion: '2006-03-01',
    region: 'us-west-2',
    credentials: {
        accessKeyId: '<access key>',
        secretAccessKey: '<access secret>',
    }
})
const response = await s3Client
    .send(new GetObjectCommand({
        Key: '<key>',
        Bucket: '<bucket>',
    }))
const stream = response.Body as Readable

return new Promise<Buffer>((resolve, reject) => {
    const chunks: Buffer[] = []
    stream.on('data', chunk => chunks.push(chunk))
    stream.once('end', () => resolve(Buffer.concat(chunks)))
    stream.once('error', reject)
})
// if readable.toArray() is support
// return Buffer.concat(await stream.toArray())
Run Code Online (Sandbox Code Playgroud)

为什么我们必须铸造response.Body as Readable?答案太长了。有兴趣的读者可以在我的博文中找到更多信息。


Ari*_*sta 18

基于@peteb的答案,但使用PromisesAsync/Await:

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

const s3 = new AWS.S3();

async function getObject (bucket, objectKey) {
  try {
    const params = {
      Bucket: bucket,
      Key: objectKey 
    }

    const data = await s3.getObject(params).promise();

    return data.Body.toString('utf-8');
  } catch (e) {
    throw new Error(`Could not retrieve file from S3: ${e.message}`)
  }
}

// To retrieve you need to use `await getObject()` or `getObject().then()`
getObject('my-bucket', 'path/to/the/object.txt').then(...);
Run Code Online (Sandbox Code Playgroud)

  • getObject()末尾的.promise()对我来说很关键.我发现AWS SDK有时不直观. (4认同)

yon*_*ran 9

转换GetObjectOutput.BodyPromise<string>使用节点获取

\n

在 aws-sdk-js-v3 @aws-sdk/client-s3 中,GetObjectOutput.Body是 nodejs 中的子类Readable(特别是 的实例http.IncomingMessage),而不是Bufferaws -sdk v2中的 a ,因此resp.Body.toString(\'utf-8\')会给您错误的结果 \xe2 \x80\x9c[对象对象]\xe2\x80\x9d。GetObjectOutput.Body相反,转换为 a 的最简单方法Promise<string>是构造一个 node-fetch Response,它采用Readable子类(或Buffer实例,或来自 fetch spec 的其他类型)并具有转换方法.json().text().arrayBuffer().blob()

\n

这也应该适用于 aws-sdk 和平台的其他变体(@aws-sdk v3 Node Buffer、 v3 浏览器Uint8Array子类、 v2 Node Readable、 v2 browserReadableStreamBlob

\n
npm install node-fetch\n
Run Code Online (Sandbox Code Playgroud)\n
import { Response } from \'node-fetch\';\nimport * as s3 from \'@aws-sdk/client-s3\';\n\nconst client = new s3.S3Client({})\nconst s3Response = await client.send(new s3.GetObjectCommand({Bucket: \'\xe2\x80\xa6\', Key: \'\xe2\x80\xa6\'});\nconst response = new Response(s3Response.Body);\n\nconst obj = await response.json();\n// or\nconst text = await response.text();\n// or\nconst buffer = Buffer.from(await response.arrayBuffer());\n// or\nconst blob = await response.blob();\n\n
Run Code Online (Sandbox Code Playgroud)\n

参考:GetObjectOutput.Body文档node-fetchResponse文档node-fetchBody构造函数源minipass-fetchBody构造函数源

\n

感谢kennu 对可用性问题的评论GetObjectCommand

\n


Cha*_*ion 8

对于寻找NEST JS TYPESCRIPT上述版本的人:

    /**
     * to fetch a signed URL of a file
     * @param key key of the file to be fetched
     * @param bucket name of the bucket containing the file
     */
    public getFileUrl(key: string, bucket?: string): Promise<string> {
        var scopeBucket: string = bucket ? bucket : this.defaultBucket;
        var params: any = {
            Bucket: scopeBucket,
            Key: key,
            Expires: signatureTimeout  // const value: 30
        };
        return this.account.getSignedUrlPromise(getSignedUrlObject, params);
    }

    /**
     * to get the downloadable file buffer of the file
     * @param key key of the file to be fetched
     * @param bucket name of the bucket containing the file
     */
    public async getFileBuffer(key: string, bucket?: string): Promise<Buffer> {
        var scopeBucket: string = bucket ? bucket : this.defaultBucket;
        var params: GetObjectRequest = {
            Bucket: scopeBucket,
            Key: key
        };
        var fileObject: GetObjectOutput = await this.account.getObject(params).promise();
        return Buffer.from(fileObject.Body.toString());
    }

    /**
     * to upload a file stream onto AWS S3
     * @param stream file buffer to be uploaded
     * @param key key of the file to be uploaded
     * @param bucket name of the bucket 
     */
    public async saveFile(file: Buffer, key: string, bucket?: string): Promise<any> {
        var scopeBucket: string = bucket ? bucket : this.defaultBucket;
        var params: any = {
            Body: file,
            Bucket: scopeBucket,
            Key: key,
            ACL: 'private'
        };
        var uploaded: any = await this.account.upload(params).promise();
        if (uploaded && uploaded.Location && uploaded.Bucket === scopeBucket && uploaded.Key === key)
            return uploaded;
        else {
            throw new HttpException("Error occurred while uploading a file stream", HttpStatus.BAD_REQUEST);
        }
    }
Run Code Online (Sandbox Code Playgroud)


小智 8

就像替代解决方案一样:

根据同一主题的此问题,似乎在 2022 年 10 月,有一种方法可以处理从 S3 GetObject 请求返回的正文。假设您使用的是AWS SDK V3,您可以利用@aws-sdk/util-stream-node官方AWS SDK中的软件包:

import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { sdkStreamMixin } from '@aws-sdk/util-stream-node';

const s3Client = new S3Client({});
const { Body } = await s3Client.send(
  new GetObjectCommand({
    Bucket: 'your-bucket',
    Key: 'your-key',
  }),
);

// Throws error if Body is undefined
const body = await sdkStreamMixin(Body).transformToString();
Run Code Online (Sandbox Code Playgroud)

.transformToByteArray()您还可以使用和函数将正文转换为字节数组或 Web 流.transformToWebStream()

请记住,该包表示您不应该直接使用它,但这似乎是处理请求正文的最直接的方法。

这是在这个回复中发现的,该回复突出显示了添加此功能的 PR。


ser*_*ays 6

与上面@ArianAcosta 的答案极其相似。除了我正在使用import(对于 Node 12.x 及更高版本),添加 AWS 配置并嗅探图像负载并将base64处理应用于return.

// using v2.x of aws-sdk
import aws from 'aws-sdk'

aws.config.update({
  accessKeyId: process.env.YOUR_AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.YOUR_AWS_SECRET_ACCESS_KEY,
  region: "us-east-1" // or whatever
})

const s3 = new aws.S3();

/**
 * getS3Object()
 * 
 * @param { string } bucket - the name of your bucket
 * @param { string } objectKey - object you are trying to retrieve
 * @returns { string } - data, formatted
 */
export async function getS3Object (bucket, objectKey) {
  try {
    const params = {
      Bucket: bucket,
      Key: objectKey 
    }

    const data = await s3.getObject(params).promise();

    // Check for image payload and formats appropriately
    if( data.ContentType === 'image/jpeg' ) {
      return data.Body.toString('base64');
    } else {
      return data.Body.toString('utf-8');
    }

  } catch (e) {
    throw new Error(`Could not retrieve file from S3: ${e.message}`)
  }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

AWS-SDK V3 简单解决方案

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';

const getS3Object = async (record): Promise<string> => {
  const client = new S3Client();
  const command = new GetObjectCommand({
    Bucket: 'bucket_name',
    Key: 'object_key'
  });
  let output = '';
  const response = await client.send(command);
  output = await response.Body.transformToString();
  return output;
}
Run Code Online (Sandbox Code Playgroud)