【问题标题】:How to get response from S3 getObject in Node.js?如何从 Node.js 中的 S3 getObject 获得响应?
【发布时间】:2016-08-24 20:34:22
【问题描述】:

在一个 Node.js 项目中,我试图从 S3 取回数据。

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

aws.getSignedUrl('getObject', params, function(err, url){
    console.log(url); 
}); 

我的参数是:

var params = {
              Bucket: "test-aws-imagery", 
              Key: "TILES/Level4/A3_B3_C2/A5_B67_C59_Tiles.par"

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

但是,如果我尝试使用 getObject,我会遇到各种奇怪的行为。我相信我只是使用不正确。这是我尝试过的:

aws.getObject(params, function(err, data){
    console.log(data); 
    console.log(err); 
}); 

输出:

{ 
  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

看来这工作正常。但是,当我在 console.logs 之一上设置断点时,我的 IDE (NetBeans) 会引发错误并拒绝显示数据的值。虽然这可能只是 IDE,但我决定尝试其他方式来使用 getObject

aws.getObject(params).on('httpData', function(chunk){
    console.log(chunk); 
}).on('httpDone', function(data){
    console.log(data); 
});

这不会输出任何东西。设置断点表明代码永远不会到达console.logs 中的任何一个。我也试过:

aws.getObject(params).on('success', function(data){
    console.log(data); 
});

但是,这也不会输出任何内容,并且放置断点表明永远不会到达 console.log

我做错了什么?

【问题讨论】:

  • 您的aws 对象实际上是aws.S3 对象的新实例吗?另外,来自getObject()的响应是传回一个http响应还是通过管道传输到一个文件?
  • @peteb aws = new AWS.S3()。响应不应通过管道传输到文件。我需要在 Javascript 中使用它
  • 那么假设内容是 JSON 或 XML 是否安全?
  • @peteb 都不是,它们是自定义文件格式
  • 显示您在getObject() 调用中使用的参数。如果您尝试将签名的 URL 传递给 getObject,我认为这行不通。

标签: javascript node.js amazon-s3 aws-sdk aws-sdk-nodejs


【解决方案1】:

从 S3 API 执行 getObject() 时,根据 docs,文件的内容位于 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
});

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

@aws-sdk/client-s3(2021 年更新)

自从我在 2016 年写下这个答案以来,亚马逊已经发布了一个新的 JavaScript SDK,@aws-sdk/client-s3。这个新版本改进了原来的getObject(),它始终返回一个承诺,而不是通过链接到getObject().promise() 选择加入。除此之外,response.Body 不再是Buffer,而是Readable|ReadableStream|Blob 之一。这稍微改变了response.Data 的处理方式。这应该会更高效,因为我们可以流式传输返回的数据,而不是将所有内容保存在内存中,但代价是实现起来有点冗长。

在下面的示例中,response.Body 数据将流式传输到数组中,然后作为字符串返回。这是我原始答案的等效示例。或者,response.Body 可以将stream.Readable.pipe() 用于 HTTP 响应、文件或任何其他类型的 stream.Writeable 以供进一步使用,这将是获取大型对象时更高效的方式。

如果您想使用Buffer,就像原始的getObject() 响应一样,这可以通过将responseDataChunks 包装在Buffer.concat() 中来完成,而不是使用Array#join(),这在与二进制交互时会很有用数据。需要注意的是,由于Array#join() 返回一个字符串,所以responseDataChunks 中的每个Buffer 实例都会隐式调用Buffer.toString(),并且将使用utf8 的默认编码。

const { GetObjectCommand, S3Client } = require('@aws-sdk/client-s3')
const client = new S3Client() // Pass in opts to S3 if necessary

function getObject (Bucket, Key) {
  return new Promise(async (resolve, reject) => {
    const getObjectCommand = new GetObjectCommand({ Bucket, Key })

    try {
      const response = await client.send(getObjectCommand)
  
      // Store all of data chunks returned from the response data stream 
      // into an array then use Array#join() to use the returned contents as a String
      let responseDataChunks = []

      // Handle an error while streaming the response body
      response.Body.once('error', err => reject(err))
  
      // Attach a 'data' listener to add the chunks of data to our array
      // Each chunk is a Buffer instance
      response.Body.on('data', chunk => responseDataChunks.push(chunk))
  
      // Once the stream has no more data, join the chunks into a string and return the string
      response.Body.once('end', () => resolve(responseDataChunks.join('')))
    } catch (err) {
      // Handle the error or throw
      return reject(err)
    } 
  })
}

@aws-sdk/client-s3 文档链接

【讨论】:

  • 如果内容已经是一个 Buffer,则不需要再创建一个新的 Buffer。只需执行data.Body.toString('utf-8');。缓冲区是节点中二进制数据的表示,如果您需要更多信息here are the docs
  • 这适用于文本,但是否有处理文本文件以及 .png、.jpg 等的通用解决方案?
  • @carter 这是一个通用的解决方案。如果您想要图像的二进制字符串,只需将访问data.Body 时的.toString('utf8') 更改为.toString('binary')。如果data.Body 中的Buffer 不需要像此问题中那样转换为字符串,那么您可以直接返回data.Body 并直接使用Buffer
  • "Convert Body from a Buffer to a String"... 如果 AWS 文档能更清楚地说明这一点,那就太好了。我已经厌倦了与 AWS 搏斗。
  • 在 AWS 文档中或在他们的 NPM 存储库中找不到任何只有此代码的文档,这绝对是疯了。就像,他们真的必须让它那么难吗?他们不能只有一个“傻瓜”方法(甚至是一个愚蠢的命令类)来获取文件内容 b64 编码或其他对象数据?就像它抓取了我想大多数人永远不需要的大量垃圾元数据一样。但它不能返回对象数据本身?
【解决方案2】:

基于@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()`
const myObject = await getObject('my-bucket', 'path/to/the/object.txt');

【讨论】:

  • getObject() 末尾的 .promise() 对我来说是关键。我发现 AWS 开发工具包有时有点不直观。
  • 我的回复是'Promise { }'
  • @jonask getObject() 是一个异步函数,你试过用await getObject(...) 调用它吗?
【解决方案3】:

对于寻找上述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);
        }
    }

【讨论】:

  • fileObject.Body 返回 Buffer 或 undefined,并且无法使用 typescript 返回它
【解决方案4】:

与上面@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}`)
  }
}

【讨论】:

    【解决方案5】:

    您也可以使用minio-js client library get-object.js

    var Minio = require('minio')
    
    var s3Client = new Minio({
      endPoint: 's3.amazonaws.com',
      accessKey: 'YOUR-ACCESSKEYID',
      secretKey: 'YOUR-SECRETACCESSKEY'
    })
    
    var size = 0
    // Get a full object.
    s3Client.getObject('my-bucketname', 'my-objectname', function(e, dataStream) {
      if (e) {
        return console.log(e)
      }
      dataStream.on('data', function(chunk) {
        size += chunk.length
      })
      dataStream.on('end', function() {
        console.log("End. Total size = " + size)
      })
      dataStream.on('error', function(e) {
        console.log(e)
      })
    })
    

    免责声明:我为 Minio 工作,它使用 golang 编写的开源、S3 兼容对象存储,客户端库在 JavaPythonJsgolang 中可用。

    【讨论】:

    • 试过 mino,但是如何获取缓冲区数据,当我打印 dataStream.Body 时,它给出了“未定义”。即console.log('datastream', dataStream.Body); //未定义
    【解决方案6】:

    乍一看,您似乎没有做错任何事情,但您没有显示所有代码。当我第一次检查 S3 和 Node 时,以下内容对我有用:

    var AWS = require('aws-sdk');
    
    if (typeof process.env.API_KEY == 'undefined') {
        var config = require('./config.json');
        for (var key in config) {
            if (config.hasOwnProperty(key)) process.env[key] = config[key];
        }
    }
    
    var s3 = new AWS.S3({accessKeyId: process.env.AWS_ID, secretAccessKey:process.env.AWS_KEY});
    var objectPath = process.env.AWS_S3_FOLDER +'/test.xml';
    s3.putObject({
        Bucket: process.env.AWS_S3_BUCKET, 
        Key: objectPath,
        Body: "<rss><data>hello Fred</data></rss>",
        ACL:'public-read'
    }, function(err, data){
        if (err) console.log(err, err.stack); // an error occurred
        else {
            console.log(data);           // successful response
            s3.getObject({
                Bucket: process.env.AWS_S3_BUCKET, 
                Key: objectPath
            }, function(err, data){
                console.log(data.Body.toString());
            });
        }
    });
    

    【讨论】:

      【解决方案7】:

      更新(2022 年)

      nodejs v17.5.0 addedReadable.toArray。如果此 API 在您的节点版本中可用。代码会很短:

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

      如果你使用Typescript,你可以安全地将.Body部分转换为Readable(其他类型ReadableStreamBlob仅在浏览器环境中返回。此外,在浏览器中,Blob@不支持response.body 时,旧版提取 API 中的 987654323@)

      (...Body as Readable).toArray()
      

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

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

      原答案

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

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

      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())
      

      为什么我们必须投射response.Body as Readable?答案太长了。有兴趣的读者可以在my blog post找到更多信息。

      【讨论】:

        【解决方案8】:

        这是async / await 版本

        var getObjectAsync = async function(bucket,key) {
          try {
            const data = await s3
              .getObject({ Bucket: bucket, Key: key })
              .promise();
              var contents = data.Body.toString('utf-8');
              return contents;
          } catch (err) {
            console.log(err);
          }
        }
        var getObject = async function(bucket,key) {
            const contents = await getObjectAsync(bucket,key);
            console.log(contents.length);
            return contents;
        }
        getObject(bucket,key);
        

        【讨论】:

          【解决方案9】:

          使用 node-fetch 将 GetObjectOutput.Body 转换为 Promise&lt;string&gt;

          在 aws-sdk-js-v3 @aws-sdk/client-s3 中,GetObjectOutput.Body 是 nodejs 中 Readable 的子类(特别是 http.IncomingMessage 的实例),而不是原来的 Bufferaws-sdk v2,所以resp.Body.toString('utf-8') 会给你错误的结果“[object Object]”。相反,将GetObjectOutput.Body 转换为Promise&lt;string&gt; 的最简单方法是构造一个node-fetch Response,它采用Readable 子类(或Buffer 实例,或其他types from the fetch spec)并具有转换方法.json().text().arrayBuffer().blob()

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

          npm install node-fetch
          
          import { Response } from 'node-fetch';
          import * as s3 from '@aws-sdk/client-s3';
          
          const client = new s3.S3Client({})
          const s3Response = await client.send(new s3.GetObjectCommand({Bucket: '…', Key: '…'});
          const response = new Response(s3Response.Body);
          
          const obj = await response.json();
          // or
          const text = await response.text();
          // or
          const buffer = Buffer.from(await response.arrayBuffer());
          // or
          const blob = await response.blob();
          
          

          参考:GetObjectOutput.Body documentationnode-fetch Response documentationnode-fetch Body constructor sourceminipass-fetch Body constructor source

          感谢kennu comment in GetObjectCommand usability issue

          【讨论】:

            猜你喜欢
            • 2016-10-24
            • 2016-01-19
            • 1970-01-01
            • 2017-08-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-12-01
            相关资源
            最近更新 更多