为什么此 HTTP 请求不适用于 AWS Lambda?

Why is this HTTP request not working on AWS Lambda?

我正在开始使用 AWS Lambda,我正在尝试从我的处理程序函数中请求外部服务。根据this answer, HTTP requests should work just fine, and I haven't found any documentation that says otherwise. (In fact, people have posted code that use the Twilio API to send SMS.)

我的处理程序代码是:

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

并且我在 CloudWatch 日志中看到以下 4 行:

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

我希望那里有另一行:

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

但那是缺失的。如果我在本地机器上的节点中使用没有处理程序包装器的基本部分,代码将按预期工作。

我正在使用的 inputfile.txt 用于 invoke-async 调用是这样的:

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

似乎完全跳过了执行请求的处理程序代码部分。我从 request lib 开始,然后回退到使用普通的 http 来创建一个最小的示例。我还尝试请求 URL 我控制的服务来检查日志,但没有收到任何请求。

我完全被难住了。 Node and/or AWS Lambda 不执行 HTTP 请求有什么原因吗?

当然是我理解错了问题。 As AWS themselves put it:

For those encountering nodejs for the first time in Lambda, a common error is forgetting that callbacks execute asynchronously and calling context.done() in the original handler when you really meant to wait for another callback (such as an S3.PUT operation) to complete, forcing the function to terminate with its work incomplete.

我在触发任何请求回调之前调用 context.done,导致我的函数提前终止。

工作代码是这样的:

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

更新: 从 2017 年开始 AWS 已弃用旧的 Nodejs 0.10,现在只有较新的 4.3 运行-time 可用(应更新旧功能)。这次 运行 时间对处理函数进行了一些更改。新的处理程序现在有 3 个参数。

function(event, context, callback)

虽然您仍然会在上下文参数上找到 succeeddonefail,AWS 建议改用 callback 函数或 null 默认返回。

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

可以在 http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

找到完整的文档

是的,实际上有很多原因可以让您访问 AWS Lambda 和 HTTP 端点。

A​​WS Lambda的架构

这是一个微服务。 运行 在 EC2 中使用 Amazon Linux AMI(版本 3.14.26–24.46.amzn1.x86_64)和 运行s with Node.js。内存可以在 128mb 和 1gb 之间。当数据源触发事件时,详细信息将作为参数传递给 Lambda 函数。

发生了什么?

A​​WS Lambda 运行在一个容器里面,代码直接和包或者模块一起上传到这个容器。例如,我们永远无法为 linux 机器 运行 使用您的 lambda 函数执行 SSH。我们唯一可以监控的是日志、CloudWatchLogs 和来自 运行 时间的异常。

A​​WS 负责为我们启动和终止容器,并且只是 运行 代码。所以,即使你使用 require('http'),它也不会工作,因为这段代码 运行s 的地方不是为此而设计的。

是的,答案很完美。我将只显示我的工作代码...我在 [=19= 之后有 context.succeed('Blah'); 行](); 行。将它移动到我在下面显示的位置解决了所有问题。

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

我遇到了同样的问题,然后我意识到在 NodeJS 中编程实际上不同于 Python 或 Java,因为它基于 JavaScript。我将尝试使用简单的概念,因为可能有一些新朋友会对这个问题感兴趣或可能会问这个问题。

让我们看下面的代码:

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

每当您调用 http 包 (1) 中的方法时,它都会被创建为事件,并且此事件会获得单独的事件。 'get' 函数 (2) 实际上是这个单独事件的起点。

现在,(3) 处的函数将在一个单独的事件中执行,您的代码将继续执行路径并直接跳转到 (4) 并完成它,因为没有更多事情要做。

但是在 (2) 处触发的事件仍在某处执行,并且需要自己的甜蜜时间才能完成。很奇怪,对吧?好吧,不,不是。这就是 NodeJS 的工作方式,您必须围绕这个概念进行思考,这一点非常重要。这是 JavaScript Promises 提供帮助的地方。

您可以阅读有关 JavaScript Promises here 的更多信息。简而言之,您需要一个 JavaScript Promise 来保持内联代码的执行,并且不会产生新的/额外的线程。

大多数常见的 NodeJS 包都有其 API 的 Promised 版本可用,但还有其他方法(如 BlueBirdJS)可以解决类似的问题。

您在上面编写的代码可以松散地重写如下。

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

请注意,如果您将上面的代码导入到 AWS Lambda 中,将无法直接运行。对于 Lambda,您还需要将模块与代码库一起打包。

我在网络上发现了很多关于执行请求的各种方式的帖子,但 none 实际上展示了如何在 AWS Lambda 上同步处理响应。

这是一个 Node 6.10.3 lambda 函数,它使用 https 请求,收集并 returns 响应的完整主体,并将控制权传递给未列出的函数 processBody 和结果。我相信 http 和 https 在此代码中可以互换。

我正在使用 async utility module, which is easier to understand for newbies. You'll need to push that to your AWS Stack to use it (I recommend the serverless framework).

请注意,数据以块的形式返回,这些块聚集在一个全局变量中,最后在数据 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);
        });
    }
};

使用节点的 Http 请求的简单工作示例。

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

我在 Node 10.X 版本上遇到过这个问题。 下面是我的工作代码。

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

在 API 网关中添加以上代码 GET-Integration 请求 > 映射部分。

使用 promises with resolve reject。它对我有用!

现代Async/Await示例

您需要防止 lambda 在 https 请求完成之前完成。它也使具有多个请求的代码更易于阅读。

const https = require('https');

// Helper that turns https.request into a promise
function httpsRequest(options) {
    return new Promise((resolve, reject) => {
        const req = https.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);
        });
        
        req.end();
    });
}

// Lambda starts executing here
exports.handler = async event => {
    // --- GET example request  
    var options = {
        method: 'GET',
        hostname: 'postman-echo.com',
        path: encodeURI('/get?foo1=bar1'),
        headers: {
        },
    };

    try {
        const getBody = await httpsRequest(options);
        // The console.log below will not run until the GET request above finishes
        console.log('GET completed successfully! Response body:', getBody);
    } catch (err) {
        console.error('GET request failed, error:', err);
    }

    // --- POST example request  
    var options = {
        method: 'POST',
        hostname: 'postman-echo.com',
        path: encodeURI('/hi/there?hand=wave'),
        headers: {
        },
    };

    try {
        const postBody = await httpsRequest(options);
        // The console.log below will not run until the POST request above finishes
        console.log('POST response body:', postBody);
    } catch (err) {
        console.error('POST request failed, error:', err);
    }
};