¿Cómo obtener respuesta de S3 getObject en Node.js?

88

En un proyecto de Node.js, estoy intentando recuperar datos de S3.

Cuando lo uso getSignedURL, todo funciona:

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

Mis params son:

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

Si llevo la salida de la URL a la consola y la pego en un navegador web, descarga el archivo que necesito.

Sin embargo, si trato de usarlo getObject, obtengo todo tipo de comportamiento extraño. Creo que lo estoy usando incorrectamente. Esto es lo que he probado:

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

Salidas:

{ 
  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

Entonces parece que esto está funcionando correctamente. Sin embargo, cuando pongo un punto de interrupción en uno de los console.logs, mi IDE (NetBeans) arroja un error y se niega a mostrar el valor de los datos. Si bien este podría ser solo el IDE, decidí probar otras formas de uso getObject.

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

Esto no genera nada. Poner un punto de interrupción muestra que el código nunca llega a ninguno de los console.logs. También probé:

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

Sin embargo, esto tampoco genera nada y colocar un punto de interrupción muestra que console.lognunca se alcanza.

¿Qué estoy haciendo mal?

Sara Tibbetts
fuente
¿Es su awsobjeto realmente una nueva instancia del aws.S3objeto? Además, ¿la respuesta getObject()se devuelve a una respuesta http o se envía a un archivo?
peteb
@peteb aws = new AWS.S3(). La respuesta no debe enviarse a un archivo. Necesito usarlo en Javascript
Sara Tibbetts
Entonces, ¿es seguro asumir que los contenidos son JSON o XML?
peteb
@peteb tampoco, son un formato de archivo personalizado
Sara Tibbetts
Muestre los parámetros que está usando en la getObject()llamada. Si está intentando pasar una URL firmada a getObject, no creo que funcione.
Mark B

Respuestas:

172

Al hacer una getObject()desde la API de S3, según los documentos, el contenido de su archivo se encuentra en la Bodypropiedad, que puede ver en su salida de muestra. Debería tener un código que se parezca a lo siguiente

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

Es posible que no necesite crear un nuevo búfer a partir del data.Bodyobjeto, pero si lo necesita, puede usar el ejemplo anterior para lograrlo.

peteb
fuente
Entonces, los datos que regresan parecen ser un Bufferobjeto con el que no estoy familiarizado. ¿Teóricamente podría utilizar new Buffer(data.Body).toString('utf-8');para llegar al contenido?
Sara Tibbetts
4
Si el contenido ya es un búfer, no es necesario crear un búfer nuevo a partir de él. Simplemente hazlo data.Body.toString('utf-8');. Un búfer es una representación de datos binarios en el nodo, si necesita más información aquí están los documentos
peteb
4
Esto funciona para texto, pero ¿existe una solución genérica para manejar archivos de texto, así como .png, .jpg, etc.?
Carter
4
@carter Esta es una solución general. Simplemente cambie el .toString('utf8')al acceder data.Bodya .toString('binary')si desea una cadena binaria para las imágenes. Si el Bufferde data.Bodyno necesita ser convertido a una cadena como en esta pregunta, a continuación, puedes regresar data.Bodyy trabajar con el Bufferdirectamente.
peteb
1
"Convertir el cuerpo de un búfer a una cadena" ... sería genial si los documentos de AWS dejaran esto un poco más claro. Me estoy cansando de luchar con AWS.
Osullic
29

Basado en la respuesta de @peteb, pero usando Promisesy 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
fuente
5
El .promise () al final de getObject () fue la clave para mí. A veces, encuentro AWS SDK un poco poco intuitivo.
Andrew Harris
Mi respuesta es "Promesa {<pending>}"
jonask
1
@jonask getObject()es una función asíncrona, ¿intentaste llamarla con await getObject(...)?
Arian Acosta
5

Para alguien que busque una NEST JS TYPESCRIPTversión de lo anterior:

    /**
     * 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ón del Caos
fuente
4

Alternativamente, puede usar la 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)
  })
})

Descargo de responsabilidad: trabajo para Minio Su almacenamiento de objetos de código abierto compatible con S3 escrito en golang con bibliotecas cliente disponibles en Java , Python , Js , golang .

koolhead17
fuente
Intenté mino, pero cómo obtener datos de búfer, cuando imprimo dataStream.Body está dando 'undefined'. es decir, console.log ('flujo de datos', flujo de datos.Body); // undefined
Dibish
3

A primera vista, no parece que esté haciendo nada mal, pero no muestra todo su código. Lo siguiente funcionó para mí cuando revisé por primera vez S3 y 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());
        });
    }
});
caballeros
fuente