Como obter resposta do S3 getObject no Node.js?

88

Em um projeto Node.js, estou tentando obter dados de S3.

Quando eu uso getSignedURL, tudo funciona:

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

Meus parâmetros são:

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

Se eu pegar a saída do URL para o console e colá-la em um navegador da web, ele baixa o arquivo de que preciso.

No entanto, se tento usar getObject, recebo todos os tipos de comportamento estranho. Eu acredito que estou apenas usando incorretamente. Isso é o que eu tentei:

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

Saídas:

{ 
  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

Portanto, parece que isso está funcionando corretamente. No entanto, quando coloco um ponto de interrupção em um dos console.logs, meu IDE (NetBeans) lança um erro e se recusa a mostrar o valor dos dados. Embora esse possa ser apenas o IDE, decidi tentar outras maneiras de usar getObject.

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

Isso não produz nada. Colocar um ponto de interrupção em mostra que o código nunca atinge nenhum dos console.logs. Eu também tentei:

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

No entanto, isso também não produz nada e colocar um ponto de interrupção mostra que console.lognunca foi alcançado.

O que estou fazendo errado?

Sara Tibbetts
fonte
O seu awsobjeto é realmente uma nova instância do aws.S3objeto? Além disso, a resposta getObject()está sendo passada de volta para uma resposta http ou está sendo canalizada para um arquivo?
peteb
@peteb aws = new AWS.S3(). A resposta não deve ser canalizada para um arquivo. Preciso usá-lo no Javascript
Sara Tibbetts
Então, é seguro assumir que o conteúdo é JSON ou XML?
peteb
nem @peteb, eles são um formato de arquivo personalizado
Sara Tibbetts,
Mostra os parâmetros que você está usando na getObject()chamada. Se você está tentando passar um URL assinado para getObject, não acho que funcione.
Mark B

Respostas:

172

Ao fazer um a getObject()partir da API S3, de acordo com os documentos, o conteúdo do seu arquivo está localizado na Bodypropriedade, que você pode ver na sua saída de amostra. Você deve ter um código parecido com o seguinte

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
});

Você pode não precisar criar um novo buffer a partir do data.Bodyobjeto, mas se precisar, pode usar o exemplo acima para fazer isso.

peteb
fonte
Portanto, os dados que retornam parecem ser um Bufferobjeto, com o qual não estou familiarizado. Teoricamente, eu poderia usar new Buffer(data.Body).toString('utf-8');para chegar ao conteúdo?
Sara Tibbetts
4
Se o conteúdo já for um Buffer, não há necessidade de criar um novo Buffer a partir dele. Simplesmente faça data.Body.toString('utf-8');. Um Buffer é uma representação de dados binários no nó, se você precisar de mais informações aqui estão os documentos
peteb
4
Isso funciona para texto, mas existe uma solução genérica para lidar com arquivos de texto, bem como .png, .jpg, etc.?
Carter
4
@carter Esta é uma solução geral. Basta alterar o .toString('utf8')ao acessar data.Bodypara .toString('binary')se você quer uma cadeia binária para imagens. Se o Bufferin data.Bodynão precisa ser convertido em uma String como nesta pergunta, você pode simplesmente retornar data.Bodye trabalhar com o Bufferdiretamente.
peteb
1
"Converter corpo de buffer em string" ... seria ótimo se os documentos da AWS deixassem isso um pouco mais claro. Estou ficando muito farto de lutar com a AWS.
osullic
29

Com base na resposta de @peteb, mas usando Promisese Async/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(...);
Arian Acosta
fonte
5
A .promise () no final de getObject () era a chave para mim. Acho o SDK da AWS um pouco não intuitivo às vezes.
Andrew Harris
Minha resposta é dizendo 'Promessa {<pending>}'
jonask
1
@jonask getObject()é uma função assíncrona, você tentou chamá-la com await getObject(...)?
Arian Acosta
5

Para quem procura uma NEST JS TYPESCRIPTversão do acima:

    /**
     * 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);
        }
    }
Legião do Caos
fonte
4

Como alternativa, você pode usar a biblioteca cliente 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)
  })
})

Aviso de isenção de responsabilidade: Eu trabalho para o Minio Seu código aberto, armazenamento de objetos compatível com S3 escrito em golang com bibliotecas de cliente disponíveis em Java , Python , Js , golang .

koolhead17
fonte
Tentei mino, mas como obter dados do buffer, quando imprimo dataStream.Body está dando 'undefined'. ou seja, console.log ('datastream', dataStream.Body); // undefined
Dibish
3

À primeira vista, não parece que você está fazendo nada de errado, mas você não mostra todo o seu código. O seguinte funcionou para mim quando eu estava conferindo o S3 e o Node pela primeira vez:

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());
        });
    }
});
bknights
fonte