Comment obtenir la réponse de S3 getObject dans Node.js?

88

Dans un projet Node.js, j'essaie de récupérer des données de S3.

Quand j'utilise getSignedURL, tout fonctionne:

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

Mes paramètres sont:

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

Si je prends la sortie URL dans la console et la colle dans un navigateur Web, il télécharge le fichier dont j'ai besoin.

Cependant, si j'essaye d'utiliser, getObjectj'obtiens toutes sortes de comportements étranges. Je pense que je ne l'utilise pas correctement. Voici ce que j'ai essayé:

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

Les sorties:

{ 
  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

Il semble donc que cela fonctionne correctement. Cependant, lorsque je mets un point d'arrêt sur l'un des console.logs, mon IDE (NetBeans) renvoie une erreur et refuse d'afficher la valeur des données. Bien que cela puisse être juste l'IDE, j'ai décidé d'essayer d'autres façons d'utiliser getObject.

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

Cela ne produit rien. Mettre un point d'arrêt dans montre que le code n'atteint jamais aucun des console.logs. J'ai aussi essayé:

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

Cependant, cela ne produit rien non plus et le fait de placer un point d'arrêt montre que le console.logn'est jamais atteint.

Qu'est-ce que je fais mal?

Sara Tibbetts
la source
Votre awsobjet est-il réellement une nouvelle instance de l' aws.S3objet? De plus, la réponse getObject()est-elle renvoyée à une réponse http ou est-elle redirigée vers un fichier?
peteb
@peteb aws = new AWS.S3(). La réponse ne doit pas être redirigée vers un fichier. Je dois l'utiliser dans le Javascript
Sara Tibbetts
Alors, est-il prudent de supposer que le contenu est JSON ou XML?
peteb
@peteb non plus, il s'agit d'un format de fichier personnalisé
Sara Tibbetts
Affichez les paramètres que vous utilisez dans l' getObject()appel. Si vous essayez de transmettre une URL signée à getObject, je ne pense pas que cela fonctionnera.
Mark B

Réponses:

172

Lorsque vous effectuez une opération à getObject()partir de l'API S3, selon les documents, le contenu de votre fichier se trouve dans la Bodypropriété, que vous pouvez voir à partir de votre exemple de sortie. Vous devriez avoir un code qui ressemble à ceci

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

Vous n'aurez peut-être pas besoin de créer un nouveau tampon à partir de l' data.Bodyobjet, mais si vous en avez besoin, vous pouvez utiliser l'exemple ci-dessus pour y parvenir.

peteb
la source
Les données qui reviennent semblent donc être un Bufferobjet que je ne connais pas. Théoriquement, je pourrais utiliser new Buffer(data.Body).toString('utf-8');pour accéder au contenu?
Sara Tibbetts
4
Si le contenu est déjà un tampon, inutile de créer un nouveau tampon à partir de celui-ci. Faites simplement data.Body.toString('utf-8');. Un tampon est une représentation de données binaires dans un nœud, si vous avez besoin de plus d'informations, voici la documentation
peteb
4
Cela fonctionne pour le texte, mais existe-t-il une solution générique pour gérer les fichiers texte ainsi que .png, .jpg, etc.?
carter
4
@carter C'est une solution générale. Changez simplement le .toString('utf8')lors de l'accès data.Bodyà .toString('binary')si vous voulez une chaîne binaire pour les images. Si le Bufferin data.Bodyn'a pas besoin d'être converti en chaîne comme dans cette question, vous pouvez simplement revenir data.Bodyet travailler avec le Bufferdirectement.
peteb
1
"Convertir le corps d'un tampon en une chaîne" ... serait formidable si la documentation AWS clarifiait un peu les choses. J'en ai assez de me battre avec AWS.
osullic le
29

Basé sur la réponse de @peteb, mais en utilisant Promiseset 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
la source
5
Le .promise () à la fin de getObject () était la clé pour moi. Je trouve parfois le SDK AWS un peu intuitif.
Andrew Harris
Ma réponse est «Promise {<pending>}»
jonask
1
@jonask getObject()est une fonction asynchrone, avez-vous essayé de l'appeler avec await getObject(...)?
Arian Acosta
5

Pour quelqu'un qui recherche une NEST JS TYPESCRIPTversion de ce qui précède:

    /**
     * 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);
        }
    }
Légion du chaos
la source
4

Vous pouvez également utiliser la bibliothèque 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)
  })
})

Clause de non-responsabilité: Je travaille pour Minio Son stockage d'objets open source, compatible S3 écrit en golang avec des bibliothèques clientes disponibles en Java , Python , Js , golang .

koolhead17
la source
Mino essayé, mais comment obtenir des données de tampon, lorsque j'imprime dataStream.Body son donnant «indéfini». c'est-à-dire console.log ('datastream', dataStream.Body); // undefined
Dibish
3

À première vue, il ne semble pas que vous fassiez quelque chose de mal, mais vous ne montrez pas tout votre code. Ce qui suit a fonctionné pour moi lorsque j'ai vérifié pour la première fois S3 et 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());
        });
    }
});
bknights
la source