Dlaczego to żądanie HTTP nie działa w AWS Lambda?

89

Rozpoczynam pracę z AWS Lambda i próbuję zażądać usługi zewnętrznej z mojej funkcji obsługi. Zgodnie z tą odpowiedzią żądania HTTP powinny działać dobrze, a nie znalazłem żadnej dokumentacji, która mówi inaczej. (W rzeczywistości ludzie opublikowali kod, który używa interfejsu API Twilio do wysyłania wiadomości SMS ).

Mój kod obsługi to:

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

i widzę następujące 4 wiersze w moich dziennikach 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

Spodziewałbym się tam kolejnej linii:

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

ale tego brakuje. Jeśli używam podstawowej części bez opakowania programu obsługi w węźle na moim komputerze lokalnym, kod działa zgodnie z oczekiwaniami.

inputfile.txtUżywam jest dla invoke-asyncpołączenia jest następujący:

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

Wygląda na to, że część kodu obsługi, która wykonuje żądanie, jest całkowicie pomijana. Zacząłem od żądania lib i wróciłem do używania zwykłego httpdo tworzenia minimalnego przykładu. Próbowałem również zażądać adresu URL usługi, którą kontroluję, aby sprawdzić dzienniki, ale nie przychodzą żadne żądania.

Jestem totalnie zaskoczony. Czy jest jakiś powód, dla którego Node i / lub AWS Lambda nie wykonałyby żądania HTTP?

awendt
źródło
Myślę, że może to być spowodowane brakiem klienta użytkownika w żądaniu HTTP.
Ma'moon Al-Akash
4
W chwili pisania tego tekstu jest to obecnie najpopularniejsze pytanie na forum Lambda na forach AWS. To doprowadza mnie do szału, a także wielu innych ludzi.
Nostradamus
@Nostradamus Doceniam wszelkie dodatkowe opinie, poprawki i głosy za. Wyślij je tutaj ;-)
końca
1
Wypróbowałem wszystko, od przykładu Twillo do kilku domyślnych przykładów dostarczanych z pakietem przykładowych węzłów Alexa, a także z metodą context.done (). http POST nie działa. Czy można wysłać pełną próbkę kodu żądania POST?
chheplo,

Odpowiedzi:

79

Oczywiście źle zrozumiałem problem. Jak ujął to AWS :

Dla tych, którzy po raz pierwszy napotykają nodejs w Lambdzie, częstym błędem jest zapominanie, że wywołania zwrotne są wykonywane asynchronicznie i wywoływanie context.done()w oryginalnym module obsługi, gdy naprawdę zamierzałeś czekać na zakończenie innego wywołania zwrotnego (takiego jak operacja S3.PUT), wymuszając funkcję zakończyć z niekompletną pracą.

I wołał context.donedrogę zanim jakiekolwiek wywołania zwrotne na żądanie zwolniony, powodując zakończenie mojej funkcji z wyprzedzeniem.

Działający kod jest następujący:

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

Aktualizacja: począwszy od 2017 AWS wycofał starą wersję Nodejs 0.10 i jest teraz dostępna tylko nowsza wersja 4.3 (stare funkcje powinny zostać zaktualizowane). W tym środowisku uruchomieniowym wprowadzono pewne zmiany w funkcji obsługi. Nowa obsługa ma teraz 3 parametry.

function(event, context, callback)

Chociaż można jeszcze znaleźć succeed, donea failna parametr kontekstowego AWS Proponuję używać callbackzamiast funkcji lub nullzwracane jest domyślnie.

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

Pełną dokumentację można znaleźć pod adresem http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

awendt
źródło
4
więc jak sprawić, by kod obsługi działał? Rozumiem, że musisz usunąć context.done (), aby wywołać funkcję zwrotną. ale twój kod nadal nie działa dla mnie. :(
mabeiyi
3
context.done()Wezwanie musi być przeniesiona do wywołania zwrotne (dla sukcesu i przypadku błędu).
wendt
2
nie miałem jeszcze Twojego problemu, ale wspaniale jest o tym pamiętać, gdy posuwam się do przodu z lambdą.
David
jakieś pomysły, jak mogę wywołać interfejs API w moim systemie lokalnym z Lambda?
Amit Kumar Ghosh
2
rekwizyty do aktualizacji pytania 2015 z aktualizacjami 2017!
Ace
19

Prosty przykład roboczy żądania HTTP przy użyciu 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
źródło
Dziękuję Ci za to. To najlepsza odpowiedź, jaką widzę na tej stronie w 2019 roku, teraz, gdy Lambda używa składni await.
Taneem Tee
3
Znalezienie najlepszej odpowiedzi zajęło mi ponad godzinę, ponieważ biblioteki node-fetch requestitp. Nie są domyślnie dostępne w Lambdzie.
Alex C,
Wygląda na to, że wiele z przykładowego kodu jest teraz uszkodzonych. To działa przykładowy kod z marca 2020 r., Przy użyciu AWS Lambda z Node.js 12.x
Muhammad Yussuf
Czy ktoś może wyjaśnić, jak tworzyć żądania POST z danymi wewnątrz funkcji lambda?
Pavindu
11

Tak, dobra odpowiedź jest idealna. Pokażę tylko mój działający kod ... Miałem kontekst. Powodzenie ('Bla'); linia tuż po reqPost.end (); linia. Przeniesienie go do miejsca, które pokazuję poniżej, rozwiązało wszystko.

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
źródło
4

Napotkałem ten problem w wersji Node 10.X. poniżej jest mój działający kod.

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
źródło
3

Miałem ten sam problem, a potem zdałem sobie sprawę, że programowanie w NodeJS jest w rzeczywistości inne niż Python czy Java, ponieważ opiera się na JavaScript. Spróbuję użyć prostych pojęć, ponieważ może być kilka nowych osób, które byłyby zainteresowane lub mogą przyjść do tego pytania.

Spójrzmy na następujący kod:

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

Ilekroć wywołujesz metodę w pakiecie http (1), jest ona tworzona jako zdarzenie, a to zdarzenie otrzymuje osobne zdarzenie. Funkcja „get” (2) jest właściwie punktem wyjścia tego oddzielnego zdarzenia.

Teraz funkcja w (3) będzie wykonywana w osobnym zdarzeniu, a twój kod będzie kontynuował wykonywanie ścieżki, a następnie skoczy do (4) i zakończy to, ponieważ nie ma nic więcej do zrobienia.

Ale zdarzenie wystrzelone w (2) wciąż gdzieś się odbywa i jego zakończenie zajmie swój własny słodki czas. Dość dziwne, prawda? Cóż, nie jest. Tak działa NodeJS i bardzo ważne jest, abyś owinął głowę wokół tej koncepcji. Jest to miejsce, w którym z pomocą przychodzą obietnice JavaScript.

Możesz przeczytać więcej o obietnicach JavaScript tutaj . Krótko mówiąc, potrzebowałbyś Obietnicy JavaScript, aby utrzymać wykonywanie kodu w tekście i nie powodować powstawania nowych / dodatkowych wątków.

Większość popularnych pakietów NodeJS ma dostępną wersję Promised ich API, ale istnieją inne podejścia, takie jak BlueBirdJS, które rozwiązują podobny problem.

Kod, który napisałeś powyżej, można luźno przepisać w następujący sposób.

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

Pamiętaj, że powyższy kod nie będzie działał bezpośrednio, jeśli zaimportujesz go do AWS Lambda. W przypadku Lambda będziesz musiał również spakować moduły z bazą kodu.

mmansoor
źródło
Tak, obietnice! Chociaż rozważałbym przeniesienie context.done()połączenia do finallymetody łańcuchowej .
crftr
3

Znalazłem wiele postów w Internecie na temat różnych sposobów realizacji żądania, ale żaden z nich nie pokazuje, jak synchronicznie przetwarzać odpowiedź na AWS Lambda.

Oto funkcja lambda Node 6.10.3, która używa żądania https, zbiera i zwraca pełną treść odpowiedzi oraz przekazuje kontrolę do funkcji spoza listy processBodywraz z wynikami. Uważam, że http i https są wymienne w tym kodzie.

Używam modułu narzędziowego Async , który jest łatwiejszy do zrozumienia dla początkujących. Będziesz musiał przesłać to do swojego stosu AWS, aby go użyć (polecam framework bezserwerowy ).

Zwróć uwagę, że dane wracają w kawałkach, które są gromadzone w zmiennej globalnej, a na koniec wywołanie zwrotne jest wywoływane, gdy dane endulegną zmianie.

'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
źródło
0

wprowadź opis obrazu tutaj

Dodaj powyższy kod w bramie API w sekcji GET-Integration Request> mapping.

Sher Singh
źródło
-14

Tak, w rzeczywistości istnieje wiele powodów, dla których możesz uzyskać dostęp do AWS Lambda like i HTTP Endpoint.

Architektura AWS Lambda

To mikrousługa. Działa w EC2 z Amazon Linux AMI (wersja 3.14.26–24.46.amzn1.x86_64) i działa z Node.js. Pamięć może mieć od 128 MB do 1 GB. Gdy źródło danych wyzwala zdarzenie, szczegóły są przekazywane do funkcji Lambda jako parametr.

Co się stało?

AWS Lambda działa w kontenerze, a kod jest bezpośrednio przesyłany do tego kontenera z pakietami lub modułami. Na przykład, NIGDY nie możemy wykonać SSH dla maszyny linux, na której działa twoja funkcja lambda. Jedyne, co możemy monitorować, to logi, z CloudWatchLogs i wyjątek pochodzący ze środowiska wykonawczego.

AWS zajmie się uruchamianiem i zamykaniem kontenerów za nas, a po prostu uruchom kod. Więc nawet jeśli użyjesz require ('http'), to nie zadziała, ponieważ miejsce, w którym działa ten kod, nie zostało do tego stworzone.

jonathanbaraldi
źródło
5
Mogłeś źle zrozumieć mój problem. Wiem o uruchomieniu kodu Lambda w kontenerze i wiem, że nie mogę uzyskać dostępu do maszyny bazowej. Ja też nie próbuję się dostać, mój kod próbuje się wydostać, tj. Uzyskać dostęp do zewnętrznych punktów końcowych, a Lambda może to zrobić całkiem dobrze. Problem polegał na czymś zupełnie innym, jak wskazałem w mojej własnej odpowiedzi.
awendt