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

89

Je commence avec AWS Lambda et j'essaie de demander un service externe à 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, des gens ont publié du code qui utilise 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

Je m'attendrais à une autre ligne là-dedans:

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

mais cela manque. Si j'utilise la partie essentielle sans le wrapper de gestionnaire dans le nœud sur ma machine locale, le code fonctionne comme prévu.

Le que inputfile.txtj'utilise pour l' invoke-asyncappel est le suivant:

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

Il semble que la partie du code du gestionnaire qui exécute la requête soit entièrement ignorée. J'ai commencé avec la librairie de requête et je suis revenu à l'utilisation de plain httppour créer un exemple minimal. J'ai également essayé de demander une URL d'un service que je contrôle pour vérifier les journaux et il n'y a aucune demande entrant.

Je suis totalement perplexe. Y a-t-il une raison pour laquelle Node et / ou AWS Lambda n'exécuteraient pas la requête HTTP?

awendt
la source
Je pense que cela peut être dû à un user-agent manquant dans votre requête HTTP.
Ma'moon Al-Akash du
4
Au moment de la rédaction de cet article, il s'agit actuellement de la principale question du forum Lambda des forums AWS. Cela me rend fou et aussi un tas d'autres gens.
Nostradamus
@Nostradamus J'apprécie tous les commentaires, corrections et votes positifs supplémentaires. Envoyez-les ici ;-)
awendt
1
J'ai tout essayé, de l'exemple Twillo à quelques exemples par défaut livrés avec le bundle d'exemples de nœuds Alexa et également votre méthode context.done (). http POST ne fonctionne pas. Est-il possible de publier un échantillon complet de votre code de demande POST?
chheplo

Réponses:

79

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

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

J'appelais context.donebien avant que les rappels pour la demande ne soient déclenchés, provoquant la fin de ma fonction à l'avance.

Le code de travail est le suivant:

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 déprécié l'ancien Nodejs 0.10 et seul le plus récent environnement d'exécution 4.3 est désormais disponible (les anciennes fonctions doivent être mises à jour). Ce runtime a introduit quelques modifications dans la fonction de gestionnaire. Le nouveau gestionnaire a maintenant 3 paramètres.

function(event, context, callback)

Bien que vous trouviez toujours le succeed, doneet failsur le paramètre de contexte, AWS suggère d'utiliser la callbackfonction à la place ou nullest renvoyé par défaut.

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

La documentation complète peut être trouvée sur http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

awendt
la source
4
alors, comment faites-vous fonctionner le code de votre gestionnaire? Je comprends que vous devez supprimer context.done () pour que la fonction de rappel soit appelée. mais votre code ne fonctionne toujours pas pour moi. :(
mabeiyi
3
L' context.done()appel doit être déplacé dans les rappels (en cas de succès et d'erreur).
awendt
2
Je n'ai pas encore eu votre problème, mais il est bon de le garder à l'esprit alors que j'avance avec lambda.
David
des idées sur la façon d'appeler une API dans mon système local à partir de Lambda?
Amit Kumar Ghosh
2
accessoires pour mettre à jour une question de 2015 avec les mises à jour de 2017!
Ace
19

Exemple de travail simple de requête Http utilisant node.

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();
    });
}
smsivaprakaash
la source
Merci pour ça. C'est la meilleure réponse que je vois sur cette page en 2019, maintenant que Lambda utilise la syntaxe d'attente.
Taneem Tee
3
Cela m'a pris plus d'une heure pour trouver la meilleure réponse car les bibliothèques, node-fetch requestetc. ne sont pas disponibles sur Lambda par défaut.
Alex C
Une grande partie de l'exemple de code semble être cassée maintenant. Cet exemple de code fonctionne à partir de mars 2020, en utilisant AWS Lambda avec Node.js 12.x
Muhammad Yussuf
Quelqu'un peut-il expliquer comment effectuer des requêtes POST avec des données dans des fonctions lambda?
Pavindu
11

Ouais, toute réponse est parfaite. Je vais juste montrer mon code de travail ... J'ai eu le context.succeed ('Blah'); ligne juste après le reqPost.end (); ligne. Le déplacer là 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();
};
imTachu
la source
4

J'ai rencontré ce problème sur la version Node 10.X. ci-dessous est mon code de travail.

const https = require('https');

exports.handler = (event,context,callback) => {
    let body='';
    let jsonObject = JSON.stringify(event);

    // the post options
    var optionspost = {
      host: 'example.com', 
      path: '/api/mypath',
      method: 'POST',
      headers: {
      'Content-Type': 'application/json',
      'Authorization': 'blah blah',
    }
    };

    let reqPost =  https.request(optionspost, function(res) {
        console.log("statusCode: ", res.statusCode);
        res.on('data', function (chunk) {
            body += chunk;
        });
        res.on('end', function () {
           console.log("Result", body.toString());
           context.succeed("Sucess")
        });
        res.on('error', function () {
          console.log("Result Error", body.toString());
          context.done(null, 'FAILURE');
        });
    });
    reqPost.write(jsonObject);
    reqPost.end();
};
Ameya Salagre
la source
3

J'ai eu le même problème, puis j'ai réalisé que la programmation dans NodeJS est en fait différente de Python ou Java car elle est basée sur JavaScript. Je vais essayer d'utiliser des concepts simples car il y a peut-être quelques nouvelles personnes qui seraient intéressées ou pourraient venir à 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), elle est créée en tant qu'événement et cet événement obtient un événement distinct. La fonction 'get' (2) est en fait le point de départ de cet événement séparé.

Maintenant, la fonction en (3) s'exécutera dans un événement séparé, et votre code continuera d'exécuter le chemin et passera directement à (4) et le terminera, car il n'y a plus rien à faire.

Mais l'événement déclenché sur (2) est toujours en cours d'exécution quelque part et cela prendra son temps pour se terminer. Assez bizarre, non?. Eh bien, non, ce n'est pas le cas. C'est ainsi que fonctionne NodeJS et il est assez important de comprendre ce concept. C'est ici que les promesses JavaScript viennent vous aider.

Vous pouvez en savoir plus sur les promesses JavaScript ici . En un mot, vous auriez besoin d'une promesse JavaScript pour maintenir l'exécution du code en ligne et ne créera pas de nouveaux threads / supplémentaires.

La plupart des packages NodeJS courants ont une version Promised de leur API disponible, mais il existe d'autres approches comme BlueBirdJS qui résolvent le problème similaire.

Le code que vous aviez écrit ci-dessus peut être librement réécrit 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 empaqueter les modules avec la base de code.

mmansoor
la source
Oui, des promesses! Bien que j'envisage de déplacer l' context.done()appel dans une finallyméthode en chaîne .
crftr
3

J'ai trouvé de nombreux articles sur le Web sur les différentes façons de faire la demande, mais aucun ne montre réellement comment traiter la réponse de manière synchrone sur AWS Lambda.

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

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

Notez que les données reviennent par blocs, qui sont rassemblés dans une variable globale, et enfin le rappel est appelé lorsque les données ont été endéditées.

'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);
        });
    }
};
Zodman
la source
0

entrez la description de l'image ici

Ajoutez le code ci-dessus dans la passerelle API sous GET-Integration Request> section de mappage.

Sher Singh
la source
-14

Oui, il existe en fait de nombreuses raisons pour lesquelles vous pouvez accéder à AWS Lambda comme et HTTP Endpoint.

L'architecture d'AWS Lambda

C'est un microservice. Exécution dans EC2 avec Amazon Linux AMI (version 3.14.26–24.46.amzn1.x86_64) et s'exécute avec Node.js. La mémoire peut être comprise entre 128 Mo et 1 Go. Lorsque la source de données déclenche l'événement, les détails sont transmis à une fonction Lambda en tant que paramètre.

Qu'est-ce qui se passe?

AWS Lambda s'exécute dans un conteneur et le code est directement téléchargé dans ce conteneur avec des packages ou des modules. Par exemple, nous ne pouvons JAMAIS faire de SSH pour la machine Linux exécutant votre fonction lambda. Les seules choses que nous pouvons surveiller sont les journaux, avec CloudWatchLogs et l'exception provenant du runtime.

AWS s'occupe du lancement et de l'arrêt des conteneurs pour nous, et exécute simplement le code. Donc, même si vous utilisez require ('http'), cela ne fonctionnera pas, car l'endroit où ce code s'exécute n'a pas été fait pour cela.

Jonathanbaraldi
la source
5
Vous avez peut-être mal compris mon problème. Je sais que du code Lambda est exécuté dans un conteneur et je sais que je ne peux pas accéder à la machine sous-jacente. Je n'essaye pas non plus d'entrer, mon code essaie de sortir, c'est-à-dire d'accéder aux points de terminaison externes, et Lambda peut très bien le faire. Le problème était autre chose, comme je l'ai souligné dans ma propre réponse.
awendt