¿Por qué esta solicitud HTTP no funciona en AWS Lambda?

89

Estoy comenzando con AWS Lambda y estoy intentando solicitar un servicio externo desde mi función de controlador. De acuerdo con esta respuesta , las solicitudes HTTP deberían funcionar bien y no he encontrado ninguna documentación que indique lo contrario. (De hecho, la gente ha publicado código que usa la API de Twilio para enviar SMS ).

Mi código de controlador es:

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

y veo las siguientes 4 líneas en mis registros de 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

Esperaría otra línea allí:

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

pero eso falta. Si estoy usando la parte esencial sin el contenedor del controlador en el nodo de mi máquina local, el código funciona como se esperaba.

El inputfile.txtque estoy usando es para la invoke-asyncllamada es este:

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

Parece que la parte del código del controlador que realiza la solicitud se omite por completo. Comencé con la biblioteca de solicitudes y volví a usar plain httppara crear un ejemplo mínimo. También intenté solicitar una URL de un servicio que controlo para verificar los registros y no ingresaron solicitudes.

Estoy totalmente perplejo. ¿Hay alguna razón por la que Node y / o AWS Lambda no ejecutarían la solicitud HTTP?

awendt
fuente
Creo que esto podría deberse a que falta un agente de usuario en su solicitud HTTP.
Ma'moon Al-Akash
4
En el momento de escribir este artículo, esta es actualmente la pregunta principal en el foro Lambda de los foros de AWS. Me está volviendo loco y también a un montón de otras personas.
Nostradamus
@Nostradamus Agradezco cualquier comentario adicional, correcciones y votos a favor.
Envíelos
1
Probé todo, desde el ejemplo de Twillo hasta algunos ejemplos predeterminados enviados con el paquete de ejemplo de nodo de Alexa y también su método context.done (). http POST no funciona. ¿Es posible publicar una muestra completa de su código de solicitud POST?
chheplo

Respuestas:

79

Por supuesto, estaba entendiendo mal el problema. Como lo expresaron los propios AWS :

Para aquellos que se encuentran con nodejs por primera vez en Lambda, un error común es olvidar que las devoluciones de llamada se ejecutan de forma asíncrona y llamar context.done()al controlador original cuando realmente querían esperar a que se completara otra devolución de llamada (como una operación S3.PUT), lo que obliga a la función para terminar con su trabajo incompleto.

Estaba llamando context.donemucho antes de que se disparara cualquier devolución de llamada para la solicitud, lo que provocó la terminación de mi función antes de tiempo.

El código de trabajo es 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);
}

Actualización: a partir de 2017, AWS ha desaprobado el antiguo Nodejs 0.10 y ahora solo está disponible el tiempo de ejecución 4.3 más reciente (las funciones antiguas deben actualizarse). Este tiempo de ejecución introdujo algunos cambios en la función del controlador. El nuevo controlador tiene ahora 3 parámetros.

function(event, context, callback)

Aunque todavía encontrará el parámetro de contexto succeed, doney failen el, AWS sugiere usar la callbackfunción en su lugar o nullse devuelve de forma predeterminada.

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

La documentación completa se puede encontrar en http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

awendt
fuente
4
Entonces, ¿cómo haces que funcione tu código de controlador? Tengo entendido que es necesario eliminar context.done () para que se llame a la función de devolución de llamada. pero tu código todavía no funciona para mí. :(
mabeiyi
3
La context.done()llamada debe trasladarse a las devoluciones de llamada (para casos de éxito y error).
awendt
2
aún no tenía su problema, pero es bueno tenerlo en cuenta a medida que avanzo con lambda.
David
¿Alguna idea sobre cómo puedo invocar una API en mi sistema local desde Lambda?
Amit Kumar Ghosh
2
accesorios para actualizar una pregunta de 2015 con actualizaciones de 2017!
Ace
19

Ejemplo de trabajo simple de solicitud Http usando 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
fuente
Gracias por esto. Esta es la mejor respuesta que veo en esta página en 2019, ahora que Lambda está usando la sintaxis de espera.
Camiseta Taneem
3
Esto me llevó más de una hora encontrar la mejor respuesta, ya que las bibliotecas, node-fetch requestetc., no están disponibles en Lambda de forma predeterminada.
Alex C
Gran parte del código de muestra parece estar roto ahora. Este es un código de muestra funcional a marzo de 2020, utilizando AWS Lambda con Node.js 12.x
Muhammad Yussuf
¿Alguien puede explicar cómo realizar solicitudes POST con datos dentro de funciones lambda?
Pavindu
11

Sí, la respuesta de Awendt es perfecta. Solo mostraré mi código de trabajo ... Tenía el context.succeed ('Blah'); línea justo después de reqPost.end (); línea. Moverlo a donde muestro a continuación resolvió todo.

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

Me enfrenté a este problema en la versión Node 10.X. a continuación está mi código de trabajo.

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

Tuve el mismo problema y luego me di cuenta de que la programación en NodeJS es en realidad diferente a Python o Java, ya que está basada en JavaScript. Trataré de usar conceptos simples, ya que puede haber algunas personas nuevas que estén interesadas o puedan llegar a esta pregunta.

Veamos el siguiente 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)
}

Siempre que realiza una llamada a un método en el paquete http (1), se crea como evento y este evento lo obtiene por separado. La función 'obtener' (2) es en realidad el punto de partida de este evento separado.

Ahora, la función en (3) se ejecutará en un evento separado, y su código continuará ejecutando la ruta y saltará directamente a (4) y terminará, porque no hay nada más que hacer.

Pero el evento disparado en (2) todavía se está ejecutando en algún lugar y tomará su propio tiempo para terminar. Bastante extraño, ¿verdad? Bueno, no, no lo es. Así es como funciona NodeJS y es muy importante que entiendas este concepto. Este es el lugar donde JavaScript Promises viene a ayudar.

Puede leer más sobre JavaScript Promises aquí . En pocas palabras, necesitaría una promesa de JavaScript para mantener la ejecución del código en línea y no generará subprocesos nuevos o adicionales.

La mayoría de los paquetes comunes de NodeJS tienen disponible una versión prometida de su API, pero existen otros enfoques como BlueBirdJS que abordan un problema similar.

El código que había escrito anteriormente se puede reescribir libremente de la siguiente manera.

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

Tenga en cuenta que el código anterior no funcionará directamente si lo va a importar en AWS Lambda. Para Lambda, también deberá empaquetar los módulos con el código base.

mmansoor
fuente
¡Sí, promesas! Aunque consideraría mover la context.done()llamada a un finallymétodo encadenado .
crftr
3

Encontré muchas publicaciones en la web sobre las diversas formas de realizar la solicitud, pero ninguna que realmente muestre cómo procesar la respuesta sincrónicamente en AWS Lambda.

Aquí hay una función lambda del Nodo 6.10.3 que usa una solicitud https, recopila y devuelve el cuerpo completo de la respuesta y pasa el control a una función no listada processBodycon los resultados. Creo que http y https son intercambiables en este código.

Estoy usando el módulo de utilidad async , que es más fácil de entender para los principiantes. Deberá enviarlo a su AWS Stack para usarlo (recomiendo el marco sin servidor ).

Tenga en cuenta que los datos vuelven en fragmentos, que se recopilan en una variable global, y finalmente se llama a la devolución de llamada cuando los datos se han 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);
        });
    }
};
Zodman
fuente
0

ingrese la descripción de la imagen aquí

Agregue el código anterior en la puerta de enlace API en GET-Integration Request> sección de mapeo.

Sher Singh
fuente
-14

Sí, de hecho, existen muchas razones por las que puede acceder a AWS Lambda like y HTTP Endpoint.

La arquitectura de AWS Lambda

Es un microservicio. Se ejecuta dentro de EC2 con Amazon Linux AMI (versión 3.14.26–24.46.amzn1.x86_64) y se ejecuta con Node.js. La memoria puede ser de entre 128 MB y 1 GB. Cuando la fuente de datos activa el evento, los detalles se pasan a una función Lambda como parámetro.

¿Qué ocurre?

AWS Lambda se ejecuta dentro de un contenedor y el código se carga directamente en este contenedor con paquetes o módulos. Por ejemplo, NUNCA podemos hacer SSH para la máquina Linux que ejecuta su función lambda. Lo único que podemos monitorear son los registros, con CloudWatchLogs y la excepción que vino del tiempo de ejecución.

AWS se encarga del lanzamiento y la terminación de los contenedores por nosotros, y simplemente ejecuta el código. Entonces, incluso si usa require ('http'), no funcionará, porque el lugar donde se ejecuta este código no fue creado para esto.

jonathanbaraldi
fuente
5
Puede que hayas entendido mal mi problema. Sé que el código Lambda se ejecuta en un contenedor y sé que no puedo acceder a la máquina subyacente. Tampoco estoy tratando de ingresar, mi código está tratando de salir, es decir, acceder a puntos finales externos, y Lambda puede hacerlo bastante bien. El problema era algo completamente diferente, como señalé en mi propia respuesta.
awendt