Por que esta solicitação HTTP não está funcionando no AWS Lambda?

90

Estou começando a usar o AWS Lambda e tentando solicitar um serviço externo de minha função de manipulador. De acordo com esta resposta , as solicitações HTTP devem funcionar bem e não encontrei nenhuma documentação que diga o contrário. (Na verdade, as pessoas postaram códigos que usam a API do Twilio para enviar SMS .)

Meu código de manipulador é:

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

e vejo as seguintes 4 linhas em meus logs do 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

Eu esperaria outra linha lá:

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

mas está faltando. Se eu estiver usando a parte essencial sem o wrapper do manipulador no nó em minha máquina local, o código funcionará conforme o esperado.

O inputfile.txtque estou usando é para a invoke-asyncchamada é este:

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

Parece que a parte do código do manipulador que faz a solicitação foi totalmente ignorada. Comecei com o request lib e voltei a usar o plain httppara criar um exemplo mínimo. Também tentei solicitar uma URL de um serviço que eu controlo para verificar os logs e não há solicitações entrando.

Estou totalmente perplexo. Existe algum motivo pelo qual o Node e / ou AWS Lambda não executaria a solicitação HTTP?

Awendt
fonte
Acho que isso pode ser causado devido à falta de um user agent em sua solicitação HTTP.
Ma'moon Al-Akash
4
No momento em que este artigo foi escrito, esta é atualmente a principal questão no fórum Lambda dos fóruns da AWS. Isso está me deixando louco e também um monte de outras pessoas.
Nostradamus
@Nostradamus Agradeço qualquer feedback adicional, correções e votos positivos. Envie-os aqui ;-)
awendt
1
Tentei de tudo, desde o exemplo Twillo até alguns exemplos padrão enviados com o pacote de exemplo do nó Alexa e também o método context.done (). O HTTP POST não está funcionando. É possível postar uma amostra completa do seu código de solicitação POST?
chheplo

Respostas:

80

Claro, eu estava entendendo mal o problema. Como a própria AWS colocou :

Para aqueles que encontram nodejs pela primeira vez no Lambda, um erro comum é esquecer que callbacks são executados de forma assíncrona e chamar context.done()o manipulador original quando você realmente pretendia esperar que outro callback (como uma operação S3.PUT) fosse concluído, forçando a função terminar com seu trabalho incompleto.

Eu estava chamando context.donemuito antes de qualquer retorno de chamada para a solicitação ser acionado, causando o encerramento da minha função antes do tempo.

O código de trabalho é este:

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

Atualização: a partir de 2017, a AWS suspendeu o uso do Nodejs 0.10 antigo e apenas o tempo de execução 4.3 mais recente está disponível (as funções antigas devem ser atualizadas). Este tempo de execução introduziu algumas mudanças na função do manipulador. O novo manipulador agora tem 3 parâmetros.

function(event, context, callback)

Embora você ainda encontre o parâmetro de contexto succeed, donee failno, a AWS sugere usar a callbackfunção em seu lugar ou ela nullé retornada por padrão.

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

A documentação completa pode ser encontrada em http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

Awendt
fonte
4
então, como você faz o código do manipulador funcionar? Pelo que entendi, você precisa remover context.done () para que a função de retorno de chamada seja chamada. mas seu código ainda não funciona para mim. :(
mabeiyi
3
A context.done()chamada precisa ser movida para os retornos de chamada (para casos de sucesso e erro).
awendt
2
não tive seu problema ainda, mas é ótimo ter em mente enquanto eu continuo com lambda.
David
alguma ideia sobre como posso invocar uma API em meu sistema local do Lambda?
Amit Kumar Ghosh
2
adereços para atualizar uma questão de 2015 com atualizações de 2017!
Ás
21

Exemplo de trabalho simples de solicitação Http usando o nó.

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
fonte
Obrigado por isso. Esta é a melhor resposta que vejo nesta página em 2019, agora que o Lambda está usando a sintaxe await.
Taneem Tee de
3
Levei mais de uma hora para encontrar a melhor resposta, já que as bibliotecas, node-fetch requestetc, não estão disponíveis no Lambda por padrão.
Alex C
Muito do código de amostra que existe parece estar quebrado agora. Este é um exemplo de código de trabalho em março de 2020, usando AWS Lambda com Node.js 12.x
Muhammad Yussuf
Alguém pode explicar como fazer solicitações POST com dados dentro de funções lambda?
Pavindu
11

Sim, uma resposta awendt é perfeita. Vou apenas mostrar meu código de trabalho ... Tive o context.succeed ('Blah'); linha logo após o reqPost.end (); linha. Movê-lo para onde mostro abaixo resolveu tudo.

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
fonte
4

Enfrentei esse problema na versão Node 10.X. abaixo está meu código de trabalho.

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
fonte
3

Eu tive o mesmo problema e então percebi que a programação em NodeJS é realmente diferente de Python ou Java, pois é baseada em JavaScript. Tentarei usar conceitos simples, pois pode haver algumas pessoas novas que estariam interessadas ou podem fazer essa pergunta.

Vejamos o seguinte código:

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

Sempre que você faz uma chamada para um método no pacote http (1), ele é criado como evento e este evento o obtém como evento separado. A função 'get' (2) é na verdade o ponto de partida desse evento separado.

Agora, a função em (3) será executada em um evento separado, e seu código continuará executando o caminho e irá direto para (4) e finalizará, porque não há mais nada a fazer.

Mas o evento disparado em (2) ainda está sendo executado em algum lugar e levará seu próprio tempo para terminar. Muito bizarro, certo? Bem, não, não é. É assim que o NodeJS funciona e é muito importante que você entenda esse conceito. Este é o lugar onde JavaScript Promises vem para ajudar.

Você pode ler mais sobre as Promessas de JavaScript aqui . Resumindo, você precisaria de uma promessa de JavaScript para manter a execução do código embutido e não gerará threads novas / extras.

A maioria dos pacotes NodeJS comuns tem uma versão prometida de sua API disponível, mas existem outras abordagens, como BlueBirdJS, que abordam o problema semelhante.

O código que você escreveu acima pode ser reescrito livremente da seguinte maneira.

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

Observe que o código acima não funcionará diretamente se você importá-lo no AWS Lambda. Para Lambda, você precisará empacotar os módulos com a base de código também.

Mmansoor
fonte
Sim, promessas! Embora eu considere mover a context.done()chamada para um finallymétodo encadeado .
crftr
3

Eu encontrei muitas postagens na web sobre as várias maneiras de fazer a solicitação, mas nenhuma que realmente mostre como processar a resposta de forma síncrona no AWS Lambda.

Aqui está uma função lambda do Nó 6.10.3 que usa uma solicitação https, coleta e retorna o corpo completo da resposta e passa o controle para uma função não listada processBodycom os resultados. Acredito que http e https são intercambiáveis ​​neste código.

Estou usando o módulo de utilitário assíncrono , que é mais fácil de entender para iniciantes. Você precisará enviar isso ao seu AWS Stack para usá-lo (eu recomendo a estrutura sem servidor ).

Observe que os dados voltam em blocos, que são reunidos em uma variável global e, finalmente, o retorno de chamada é chamado quando os dados são endeditados.

'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
fonte
0

insira a descrição da imagem aqui

Adicione o código acima no gateway de API em Solicitação de integração GET> seção de mapeamento.

Sher Singh
fonte
-14

Sim, existem muitos motivos pelos quais você pode acessar o AWS Lambda like e o HTTP Endpoint.

A arquitetura do AWS Lambda

É um microsserviço. Executado dentro do EC2 com Amazon Linux AMI (versão 3.14.26–24.46.amzn1.x86_64) e executado com Node.js. A memória pode ser entre 128 MB e 1 GB. Quando a fonte de dados dispara o evento, os detalhes são passados ​​para uma função Lambda como parâmetro.

O que acontece?

O AWS Lambda é executado dentro de um contêiner e o código é carregado diretamente para esse contêiner com pacotes ou módulos. Por exemplo, NUNCA podemos fazer SSH para a máquina Linux executando sua função lambda. As únicas coisas que podemos monitorar são os logs, com CloudWatchLogs e a exceção que veio do tempo de execução.

A AWS se encarrega de iniciar e encerrar os contêineres para nós, e apenas executa o código. Então, mesmo que você use require ('http'), não vai funcionar, pois o local onde esse código roda não foi feito para isso.

Jonathanbaraldi
fonte
5
Você pode ter entendido mal o meu problema. Eu sei que o código Lambda está sendo executado em um contêiner e sei que não consigo acessar a máquina subjacente. Nem estou tentando entrar, meu código está tentando sair, ou seja, acessar pontos de extremidade externos e o Lambda pode fazer isso muito bem. O problema era algo totalmente diferente, como indiquei em minha própria resposta.
awendt