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

IT技术 javascript node.js amazon-s3 aws-sdk aws-sdk-nodejs
2021-03-16 04:09:09

在 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永远不会到达。

我究竟做错了什么?

6个回答

getObject()从 S3 API执行 a 时,根据文档,您的文件内容位于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()通过始终返回Promise而不是通过.promise()链接到 来选择加入,从而对原始版本进行了改进getObject()除此之外,response.Body不再是Buffer但是,其中之一Readable|ReadableStream|Blob这改变了对response.Data的处理这应该会更高效,因为我们可以流式传输返回的数据,而不是将所有内容保存在内存中,但代价是实现起来更冗长。

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

如果您想使用 a Buffer,就像原始getObject()响应一样,可以通过包装responseDataChunksaBuffer.concat()而不是 using 来完成Array#join(),这在与二进制数据交互时会很有用。请注意,由于Array#join()返回一个字符串,因此隐式调用中的每个Buffer实例并使用默认编码responseDataChunksBuffer.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 文档链接

@carter 这是一个通用的解决方案。只要改变.toString('utf8')访问时data.Body.toString('binary'),如果你想为图像的二进制字符串。如果Bufferdata.Body不需要它在这个问题上要转换为字符串一样,那么你可以返回data.Body和工作与Buffer直接。
2021-04-21 04:09:09
这适用于文本,但是否有处理文本文件以及 .png、.jpg 等的通用解决方案?
2021-04-24 04:09:09
如果内容已经是 Buffer,则无需从中创建新的 Buffer。简单地做data.Body.toString('utf-8');缓冲区是节点中二进制数据的表示,如果您需要更多信息,请参阅文档
2021-05-04 04:09:09
“Convert Body from a Buffer to a String”... 如果 AWS 文档能更清楚地说明这一点,那就太好了。我已经厌倦了与 AWS 搏斗。
2021-05-10 04:09:09
我在 AWS 文档或他们的 NPM 存储库中找不到任何只有此代码的文档,这绝对是疯了。就像,他们真的必须让它变得那么难吗?他们不能只有一个“for dummies”方法(甚至是一个愚蠢的命令类)来获取文件内容 b64 编码或其他对象数据的东西?就像它抓取了我和我想大多数人永远不需要的荒谬数量的垃圾元数据一样。但是它不能返回对象数据本身?
2021-05-17 04:09:09

基于@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(...);
@jonaskgetObject()是一个异步函数,你试过用 调用它await getObject(...)吗?
2021-04-22 04:09:09
我的回答是 'Promise { <pending> }'
2021-05-08 04:09:09
getObject() 末尾的 .promise() 对我来说是关键。我发现 AWS 开发工具包有时有点不直观。
2021-05-10 04:09:09

对于寻找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 返回缓冲区或未定义,并且无法使用typescript返回它
2021-04-28 04:09:09

或者,您可以使用minio-js 客户端库 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工作,它的开源、S3 兼容对象存储用 golang 编写,客户端库可用JavaPythonJsgolang 编写

试过 mino,但如何获取缓冲区数据,当我打印 dataStream.Body 时,它给出了“未定义”。即 console.log('datastream', dataStream.Body); //不明确的
2021-05-02 04:09:09

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