web-dev-qa-db-fra.com

Pourquoi cette requête HTTP ne fonctionne-t-elle pas sur AWS Lambda?

Je commence à utiliser AWS Lambda et j'essaie de demander un service externe à partir de ma fonction de gestionnaire. Selon cette réponse , les requêtes HTTP devraient fonctionner correctement et je n'ai trouvé aucune documentation indiquant le contraire. (En fait, les gens ont posté code utilisant l’API Twilio pour envoyer des SMS .)

Mon code de gestionnaire est:

var http = require('http');

exports.handler = function(event, context) {
  console.log('start request to ' + event.url)
  http.get(event.url, function(res) {
    console.log("Got response: " + res.statusCode);
  }).on('error', function(e) {
    console.log("Got error: " + e.message);
  });

  console.log('end request to ' + event.url)
  context.done(null);
}

et je vois les 4 lignes suivantes dans mes journaux CloudWatch:

2015-02-11 07:38:06 UTC START RequestId: eb19c89d-b1c0-11e4-bceb-d310b88d37e2
2015-02-11 07:38:06 UTC eb19c89d-b1c0-11e4-bceb-d310b88d37e2 start request to http://www.google.com
2015-02-11 07:38:06 UTC eb19c89d-b1c0-11e4-bceb-d310b88d37e2 end request to http://www.google.com
2015-02-11 07:38:06 UTC END RequestId: eb19c89d-b1c0-11e4-bceb-d310b88d37e2

J'attendrais une autre ligne dedans:

2015-02-11 07:38:06 UTC eb19c89d-b1c0-11e4-bceb-d310b88d37e2 Got response: 302

mais ça manque. Si j'utilise la partie essentielle sans le wrapper du gestionnaire dans le noeud de ma machine locale, le code fonctionne comme prévu.

Le inputfile.txt que j'utilise est destiné à l'appel invoke-async est le suivant:

{
   "url":"http://www.google.com"
}

Il semble que la partie du code du gestionnaire responsable de la demande soit entièrement ignorée. J'ai commencé avec la request lib et je suis revenu à utiliser plain http pour créer un exemple minimal. J'ai également essayé de demander à l'URL d'un service que je contrôle de vérifier les journaux et aucune demande n'a été reçue.

Je suis totalement perplexe. Existe-t-il une raison quelconque pour que Node et/ou AWS Lambda n'exécute pas la requête HTTP?

71
awendt

Bien sûr, je ne comprenais pas le problème. Comme le dit AWS :

Pour ceux qui rencontrent nodejs pour la première fois dans Lambda, une erreur courante consiste à oublier que les rappels s'exécutent de manière asynchrone et à appeler context.done() dans le gestionnaire d'origine alors que vous vouliez réellement attendre un autre rappel (comme une opération S3.PUT). complete, forçant la fonction à se terminer avec un travail incomplet.

J'appelais context.done bien avant tout rappel de la demande, ce qui a entraîné l'arrêt de ma fonction à l'avance.

Le code de travail est ceci:

var http = require('http');

exports.handler = function(event, context) {
  console.log('start request to ' + event.url)
  http.get(event.url, function(res) {
    console.log("Got response: " + res.statusCode);
    context.succeed();
  }).on('error', function(e) {
    console.log("Got error: " + e.message);
    context.done(null, 'FAILURE');
  });

  console.log('end request to ' + event.url);
}

Mise à jour: à partir de 2017, AWS a obsolète l'ancien Nodejs 0.10 et seule la version 4.3 la plus récente est désormais disponible (les anciennes fonctions doivent être mises à jour). Cette exécution a apporté quelques modifications à la fonction de gestionnaire. Le nouveau gestionnaire a maintenant 3 paramètres.

function(event, context, callback)

Bien que vous trouviez toujours les variables succeed, done et fail sur le paramètre de contexte, AWS suggère d'utiliser la fonction callback à la place ou null est renvoyé par défaut .

callback(new Error('failure')) // to return error
callback(null, 'success msg') // to return ok

La documentation complète est disponible sur http://docs.aws.Amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

70
awendt

Ouais, la réponse est parfaite. Je montrerai simplement mon code de travail ... J'avais la ligne context.succeed ('Blah'); juste après la ligne reqPost.end ();. Le déplacer vers l'endroit où je montre ci-dessous a tout résolu.

console.log('GW1');

var https = require('https');

exports.handler = function(event, context) {

    var body='';
    var jsonObject = JSON.stringify(event);

    // the post options
    var optionspost = {
        Host: 'the_Host',
        path: '/the_path',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        }
    };

    var reqPost = https.request(optionspost, function(res) {
        console.log("statusCode: ", res.statusCode);
        res.on('data', function (chunk) {
            body += chunk;
        });
        context.succeed('Blah');
    });

    reqPost.write(jsonObject);
    reqPost.end();
};
11
imTachu

J'ai trouvé de nombreux messages sur le Web sur les différentes manières de traiter la demande, mais aucun n'indique comment traiter la réponse de manière synchrone sur AWS Lambda.

Voici une Node 6.10.3 fonction lambda qui utilise une requête https, collecte et renvoie le corps complet de la réponse et passe le contrôle à une fonction non répertoriée processBody avec les résultats. Je crois que http et https sont interchangeables dans ce code.

J'utilise le module utilitaire asynchrone , qui est plus facile à comprendre pour les débutants. Vous devrez envoyer cela à votre pile AWS pour l'utiliser (je recommande le framework sans serveur ).

Notez que les données sont renvoyées en morceaux, qui sont rassemblés dans une variable globale, et finalement, le rappel est appelé lorsque les données ont ended.

'use strict';

const async = require('async');
const https = require('https');

module.exports.handler = function (event, context, callback) {

    let body = "";
    let countChunks = 0;

    async.waterfall([
        requestDataFromFeed,
        // processBody,
    ], (err, result) => {
        if (err) {
            console.log(err);
            callback(err);
        }
        else {
            const message = "Success";
            console.log(result.body);
            callback(null, message);
        }
    });

    function requestDataFromFeed(callback) {
        const url = 'https://put-your-feed-here.com';
        console.log(`Sending GET request to ${url}`);
        https.get(url, (response) => {
            console.log('statusCode:', response.statusCode);
            response.on('data', (chunk) => {
                countChunks++;
                body += chunk;
            });
            response.on('end', () => {
                const result = {
                    countChunks: countChunks,
                    body: body
                };
                callback(null, result);
            });
        }).on('error', (err) => {
            console.log(err);
            callback(err);
        });
    }
};
3
Zodman

J'ai eu le même problème et puis j'ai réalisé que la programmation dans NodeJS est en réalité différente de Python ou Java, car elle est basée sur JavaScript. J'essaierai d'utiliser des concepts simples car il se peut que quelques nouveaux membres soient intéressés ou en viennent à cette question.

Regardons le code suivant:

var http = require('http'); // (1)
exports.handler = function(event, context) {
  console.log('start request to ' + event.url)
  http.get(event.url,  // (2)
  function(res) {  //(3)
    console.log("Got response: " + res.statusCode);
    context.succeed();
  }).on('error', function(e) {
    console.log("Got error: " + e.message);
    context.done(null, 'FAILURE');
  });

  console.log('end request to ' + event.url); //(4)
}

Chaque fois que vous appelez une méthode dans le package http (1), celle-ci est créée en tant qu'événement et cet événement l'obtient séparément. La fonction 'get' (2) est en fait le point de départ de cet événement séparé.

Maintenant, la fonction en (3) sera exécutée dans un événement séparé, et votre code le poursuivra en exécutant son chemin, sautera directement en (4) et le terminera, car il n’ya rien de plus à faire.

Mais l'événement déclenché sur (2) est toujours en cours d'exécution et il faudra du temps pour le terminer. Assez bizarre, non? Eh bien, non ce n'est pas. C’est ainsi que NodeJS fonctionne et qu’il est très important de bien comprendre ce concept. C'est l'endroit où les promesses JavaScript viennent vous aider.

Vous pouvez en savoir plus sur JavaScript Promises here . En un mot, vous aurez besoin d'une promesse JavaScript pour conserver l'exécution du code en ligne et ne pas générer de threads nouveaux/supplémentaires.

La plupart des packages NodeJS courants disposent d'une version promise de leur API, mais d'autres approches, telles que BlueBirdJS, traitent le même problème.

Le code que vous avez écrit ci-dessus peut être réécrit librement comme suit.

'use strict';
console.log('Loading function');
var rp = require('request-promise');
exports.handler = (event, context, callback) => {    

    var options = {
    uri: 'https://httpbin.org/ip',
    method: 'POST',
    body: {

    },
    json: true 
};


    rp(options).then(function (parsedBody) {
            console.log(parsedBody);
        })
        .catch(function (err) {
            // POST failed... 
            console.log(err);
        });

    context.done(null);
};

Veuillez noter que le code ci-dessus ne fonctionnera pas directement si vous l'importez dans AWS Lambda. Pour Lambda, vous devrez également conditionner les modules avec la base de code.

3
mmansoor

Exemple de travail simple d’une requête HTTP utilisant un nœud.

const http = require('https')
exports.handler = async (event) => {
    return httprequest().then((data) => {
        const response = {
            statusCode: 200,
            body: JSON.stringify(data),
        };
    return response;
    });
};
function httprequest() {
     return new Promise((resolve, reject) => {
        const options = {
            Host: 'jsonplaceholder.typicode.com',
            path: '/todos',
            port: 443,
            method: 'GET'
        };
        const req = http.request(options, (res) => {
          if (res.statusCode < 200 || res.statusCode >= 300) {
                return reject(new Error('statusCode=' + res.statusCode));
            }
            var body = [];
            res.on('data', function(chunk) {
                body.Push(chunk);
            });
            res.on('end', function() {
                try {
                    body = JSON.parse(Buffer.concat(body).toString());
                } catch(e) {
                    reject(e);
                }
                resolve(body);
            });
        });
        req.on('error', (e) => {
          reject(e.message);
        });
        // send the request
       req.end();
    });
}
0
smsivaprakaash